From fdf7cf4928cd1ba0f3d22fb64202ef62242eb36f Mon Sep 17 00:00:00 2001 From: AdityaGaikwad Date: Tue, 25 Jun 2024 20:55:14 +0530 Subject: [PATCH] Seasons tab ui Integrated Episode Api listing and caching PlayerActivity created for playing any listed passed Created and cached playlist for episodes for every season of show Updated jwplayer to latest v4.17.0 Integrated teasers api and cached data Created and cached playlist for teasers for every season of show --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 10 +- .../woka/players/LiveStreamPlayerActivity.kt | 1 + .../java/com/woka/players/PlayerActivity.kt | 87 +++++++++ .../main/java/com/woka/utils/AdiImageView.kt | 5 + .../com/woka/webseries/WebSeriesApiService.kt | 8 + .../com/woka/webseries/WebSeriesRepository.kt | 179 +++++++++++++++++- .../woka/webseries/adapters/EpisodeAdapter.kt | 79 ++++++++ .../woka/webseries/adapters/TeaserAdapter.kt | 77 ++++++++ .../models/episodedata/ContentMoreDetail.kt | 13 ++ .../models/episodedata/EpisodeData.kt | 20 ++ .../models/episodedata/EpisodeResponseData.kt | 6 + .../models/episodedata/SeasonData.kt | 7 + .../models/teaserdata/ContentMoreDetail.kt | 13 ++ .../webseries/models/teaserdata/SeasonData.kt | 7 + .../webseries/models/teaserdata/TeaserData.kt | 21 ++ .../models/teaserdata/TeaserResponseData.kt | 6 + .../woka/webseries/views/SeasonActivity.kt | 177 ++++++++++++++++- app/src/main/res/drawable/season_tab_bg.xml | 25 +-- app/src/main/res/layout/activity_player.xml | 7 + app/src/main/res/layout/activity_season.xml | 122 ++++++++---- .../main/res/layout/activity_web_series.xml | 3 +- .../main/res/layout/episode_view_holder.xml | 88 +++++++++ .../main/res/layout/spinner_view_holder.xml | 6 +- app/src/main/res/values/strings.xml | 2 + 25 files changed, 909 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/com/woka/players/PlayerActivity.kt create mode 100644 app/src/main/java/com/woka/webseries/adapters/EpisodeAdapter.kt create mode 100644 app/src/main/java/com/woka/webseries/adapters/TeaserAdapter.kt create mode 100644 app/src/main/java/com/woka/webseries/models/episodedata/ContentMoreDetail.kt create mode 100644 app/src/main/java/com/woka/webseries/models/episodedata/EpisodeData.kt create mode 100644 app/src/main/java/com/woka/webseries/models/episodedata/EpisodeResponseData.kt create mode 100644 app/src/main/java/com/woka/webseries/models/episodedata/SeasonData.kt create mode 100644 app/src/main/java/com/woka/webseries/models/teaserdata/ContentMoreDetail.kt create mode 100644 app/src/main/java/com/woka/webseries/models/teaserdata/SeasonData.kt create mode 100644 app/src/main/java/com/woka/webseries/models/teaserdata/TeaserData.kt create mode 100644 app/src/main/java/com/woka/webseries/models/teaserdata/TeaserResponseData.kt create mode 100644 app/src/main/res/layout/activity_player.xml create mode 100644 app/src/main/res/layout/episode_view_holder.xml diff --git a/app/build.gradle b/app/build.gradle index a7c3aae..19c2a95 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,8 +47,8 @@ android { } } -ext.jwPlayerVersion = '4.6.0' -ext.exoplayerVersion = '2.19.1' +ext.jwPlayerVersion = '4.17.0' +ext.exoplayerVersion = '1.1.1' dependencies { implementation "com.facebook.shimmer:shimmer:0.5.0" @@ -83,6 +83,11 @@ dependencies { implementation "com.jwplayer:jwplayer-core:$jwPlayerVersion" implementation "com.jwplayer:jwplayer-common:$jwPlayerVersion" + // exoplayer2 + implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1' + implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1' + implementation libs.androidx.core.ktx implementation libs.androidx.appcompat implementation libs.material diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9c3775b..205593b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,10 +15,18 @@ android:supportsRtl="true" android:theme="@style/Theme.Woka" tools:targetApi="31"> + + android:screenOrientation="portrait" /> ? = null + private var playIndex: Int = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + binding = ActivityPlayerBinding.inflate(layoutInflater) + setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + val windowInsetsController = + WindowCompat.getInsetsController(window, window.decorView) + windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) + + playList = intent.getParcelableArrayListExtra(EXTRA_PLAY_LIST) + playIndex = intent.getIntExtra(EXTRA_EPISODE_INDEX, 0) + + setUpPlayer() + + } + + private fun setUpPlayer() { + if (playList == null) return + player = binding.playerView.getPlayer(this) + + player.setFullscreenHandler(this) + + player.setFullscreen(true, false) + + // to keep up the screen om when video is being played + KeepScreenOnHandler(player, window) + + val config = PlayerConfig.Builder() + .playlist(playList) + .build() + + player.setup(config) + player.playlistItem(playIndex) + } + + override fun onFullscreenRequested() {} + + override fun onFullscreenExitRequested() { + player.stop() + val windowInsetsController = + WindowCompat.getInsetsController(window, window.decorView) + windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) + + finish() + } + + override fun onAllowRotationChanged(allowRotation: Boolean) {} + override fun onAllowFullscreenPortraitChanged(allowFullscreenPortrait: Boolean) {} + + override fun updateLayoutParams(layoutParams: ViewGroup.LayoutParams?) {} + + override fun setUseFullscreenLayoutFlags(flags: Boolean) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/utils/AdiImageView.kt b/app/src/main/java/com/woka/utils/AdiImageView.kt index 2569950..cc45540 100644 --- a/app/src/main/java/com/woka/utils/AdiImageView.kt +++ b/app/src/main/java/com/woka/utils/AdiImageView.kt @@ -15,6 +15,7 @@ import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.woka.R +import kotlin.reflect.KFunction class AdiImageView: FrameLayout { @@ -29,6 +30,9 @@ class AdiImageView: FrameLayout { private var progressView: ProgressBar? = null private var cardView: CardView? = null + // variables + var onLoadSuccessListener: (() -> Unit)? = null + constructor(context: Context?) : super(context!!) constructor(context: Context?, attrs: AttributeSet?) : super( context!!, attrs @@ -93,6 +97,7 @@ class AdiImageView: FrameLayout { isFirstResource: Boolean ): Boolean { progressView?.hide() + onLoadSuccessListener?.invoke() return false } }) diff --git a/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt b/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt index ba4cb92..ce67eb8 100644 --- a/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt +++ b/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt @@ -2,7 +2,9 @@ package com.woka.webseries import com.woka.networking.ApiResponse import com.woka.webseries.models.WebSeriesResponse +import com.woka.webseries.models.episodedata.EpisodeResponseData import com.woka.webseries.models.seasondata.SeasonDataResponse +import com.woka.webseries.models.teaserdata.TeaserResponseData import okhttp3.FormBody import retrofit2.Response import retrofit2.http.Body @@ -15,4 +17,10 @@ interface WebSeriesApiService { @POST("season_listing") suspend fun seasonListing(@Body formBody: FormBody): Response> + + @POST("episode_listing") + suspend fun episodeListing(@Body formBody: FormBody): Response> + + @POST("teaser_listing") + suspend fun teaserListing(@Body formBody: FormBody): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/WebSeriesRepository.kt b/app/src/main/java/com/woka/webseries/WebSeriesRepository.kt index 9178dd0..e214cf7 100644 --- a/app/src/main/java/com/woka/webseries/WebSeriesRepository.kt +++ b/app/src/main/java/com/woka/webseries/WebSeriesRepository.kt @@ -2,6 +2,7 @@ package com.woka.webseries import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.jwplayer.pub.api.media.playlists.PlaylistItem import com.woka.home.mylist.MyFavApiService import com.woka.home.mylist.MyListRepository import com.woka.home.mylist.models.BookmarkedShowData @@ -12,8 +13,11 @@ import com.woka.networking.RetrofitHelper import com.woka.webseries.models.ContinueEpisodeResponse import com.woka.webseries.models.ShowData import com.woka.webseries.models.WebSeriesResponse +import com.woka.webseries.models.episodedata.EpisodeData +import com.woka.webseries.models.episodedata.EpisodeResponseData import com.woka.webseries.models.seasondata.SeasonData import com.woka.webseries.models.seasondata.SeasonDataResponse +import com.woka.webseries.models.teaserdata.TeaserResponseData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -54,6 +58,10 @@ object WebSeriesRepository { var continueWatchData: ContinueEpisodeResponse? = null + init { + loadContinueWatchData() + } + /* flag to load a new data whenever necessary for eg. when user has signed in with new account and the apps hold the older repository @@ -65,15 +73,174 @@ object WebSeriesRepository { val seasonDataLiveData: LiveData> get() = _seasonDataLiveData - private var seasonDataMap = HashMap() + // seasons data for every show. where {key -> "showId_categoryId"} + var seasonDataMap = HashMap() - init { - loadContinueWatchData() + // episode listing data + private val _episodeDataLiveData = MutableLiveData>() + val episodeDataLiveData: LiveData> + get() = _episodeDataLiveData + + // episode data for every season. where {key -> "showId_seasonId"} + private var episodeDataMap = HashMap() + // episodes playlist, where {key -> "showId_seasonId_categoryId"} + var episodesPlaylistMap = HashMap>() + + // teaser listing data + private val _teaserDataLiveData = MutableLiveData>() + val teaserDataLiveData: LiveData> + get() = _teaserDataLiveData + + // teaser data for every season. where {key -> "showId_seasonId"} + private var teaserDataMap = HashMap() + // teasers playlist, where {key -> "showId_seasonId_categoryId"} + var teasersPlaylistMap = HashMap>() + + fun loadTeaserData(showId: Int, seasonId: Int){ + if (teaserDataMap.containsKey("${showId}_${seasonId}")){ + _teaserDataLiveData.postValue(ApiResult.Success(data = teaserDataMap["${showId}_${seasonId}"])) + return + } + CoroutineScope(Dispatchers.IO).launch { + _teaserDataLiveData.postValue(ApiResult.Loading()) + val response = RetrofitHelper.handleApiCall { + apiService.teaserListing( + FormBody.Builder() + .add("watch_show_master_id", "$showId") + .add("season_master_id", "$seasonId") + .build() + ) + } + + when (response){ + is ApiResult.Error -> { + _teaserDataLiveData.postValue(response) + } + is ApiResult.Loading -> {} + is ApiResult.Success -> { + response.data?.let {teaserData -> + teaserDataMap["${showId}_${seasonId}"] = teaserData + } + + _teaserDataLiveData.postValue(response) + + // creating a playable playlist + teaserDataMap["${showId}_${seasonId}"]?.result?.filterNotNull()?.let { + val hindiPlayList = ArrayList() + val englishPlayList = ArrayList() + + for (teaser in it){ + teaser.content_more_details?.let {moreDetailsList -> + if (moreDetailsList.isNotEmpty()){ + if (moreDetailsList.size > 1){ + moreDetailsList[1]?.let {moreDetail -> + hindiPlayList.add( + PlaylistItem.Builder() + .file(moreDetail.content_hd_url) + .title(moreDetail.title) + .image(teaser.thumbnail_path) + .build() + ) + } + } + + moreDetailsList[0]?.let {moreDetail -> + englishPlayList.add( + PlaylistItem.Builder() + .file(moreDetail.content_hd_url) + .title(moreDetail.title) + .image(teaser.thumbnail_path) + .build() + ) + } + } + } + } + + teasersPlaylistMap["${showId}_${seasonId}_18"] = hindiPlayList + teasersPlaylistMap["${showId}_${seasonId}_1"] = englishPlayList + } + } + } + } + } + + fun loadEpisodeData(showId: Int, seasonId: Int){ + if (episodeDataMap.containsKey("${showId}_${seasonId}")){ + _episodeDataLiveData.postValue(ApiResult.Success(data = episodeDataMap["${showId}_${seasonId}"])) + return + } + CoroutineScope(Dispatchers.IO).launch { + _episodeDataLiveData.postValue(ApiResult.Loading()) + val response = RetrofitHelper.handleApiCall { + apiService.episodeListing( + FormBody.Builder() + .add("watch_show_master_id", "$showId") + .add("season_master_id", "$seasonId") + .build() + ) + } + + when (response){ + is ApiResult.Error -> { + _episodeDataLiveData.postValue(response) + } + is ApiResult.Loading -> {} + is ApiResult.Success -> { + response.data?.let {episodeData -> + episodeDataMap["${showId}_${seasonId}"] = episodeData + } + + _episodeDataLiveData.postValue(response) + + // creating a playable playlist + episodeDataMap["${showId}_${seasonId}"]?.result?.filterNotNull()?.let { + val hindiPlayList = ArrayList() + val englishPlayList = ArrayList() + + for (episode in it){ + episode.content_more_details?.let {moreDetailsList -> + if (moreDetailsList.isNotEmpty()){ + if (moreDetailsList.size > 1){ + moreDetailsList[1]?.let {moreDetail -> + hindiPlayList.add( + PlaylistItem.Builder() + .file(moreDetail.content_hd_url) + .title(moreDetail.title) + .image(episode.thumbnail_path) + .build() + ) + } + } + + moreDetailsList[0]?.let {moreDetail -> + englishPlayList.add( + PlaylistItem.Builder() + .file(moreDetail.content_hd_url) + .title(moreDetail.title) + .image(episode.thumbnail_path) + .build() + ) + } + } + } + } + + episodesPlaylistMap["${showId}_${seasonId}_18"] = hindiPlayList + episodesPlaylistMap["${showId}_${seasonId}_1"] = englishPlayList + } + } + } + } + } + + fun clearEpisodeData(){ + _episodeDataLiveData.postValue(ApiResult.Loading()) } fun loadSeasonListing(showId: Int, categoryId: String){ - if (seasonDataMap.containsKey(showId)){ - _seasonDataLiveData.postValue(ApiResult.Success(data = seasonDataMap[showId])) + if (seasonDataMap.containsKey("${showId}_${categoryId}")){ + _seasonDataLiveData.postValue(ApiResult.Success(data = seasonDataMap["${showId}_${categoryId}"])) return } CoroutineScope(Dispatchers.IO).launch { @@ -92,7 +259,7 @@ object WebSeriesRepository { is ApiResult.Loading -> {} is ApiResult.Success -> { response.data?.let {seasonList -> - seasonDataMap[showId] = seasonList + seasonDataMap["${showId}_${categoryId}"] = seasonList } _seasonDataLiveData.postValue(ApiResult.Success(response.data)) diff --git a/app/src/main/java/com/woka/webseries/adapters/EpisodeAdapter.kt b/app/src/main/java/com/woka/webseries/adapters/EpisodeAdapter.kt new file mode 100644 index 0000000..f14a38c --- /dev/null +++ b/app/src/main/java/com/woka/webseries/adapters/EpisodeAdapter.kt @@ -0,0 +1,79 @@ +package com.woka.webseries.adapters; + + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.woka.WokaApp.Companion.userPrefs +import com.woka.databinding.EpisodeViewHolderBinding +import com.woka.webseries.models.episodedata.EpisodeData +import java.util.concurrent.Executors + +class EpisodeAdapter private constructor(val context: Context, + config: AsyncDifferConfig +): ListAdapter(config) { + + inner class EpisodeViewHolder(val binding: EpisodeViewHolderBinding): RecyclerView.ViewHolder(binding.root) + + companion object{ + + private val DIFF_UTIL = object : DiffUtil.ItemCallback(){ + override fun areItemsTheSame(oldItem: EpisodeData, newItem: EpisodeData): Boolean = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: EpisodeData, newItem: EpisodeData): Boolean { + return oldItem == newItem + } + } + + private val DIFF_CONFIG = AsyncDifferConfig.Builder(DIFF_UTIL) + .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor()) + .build() + } + + constructor(context: Context): this(context, DIFF_CONFIG) + + var onEpisodeClicked: ((position: Int) -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeViewHolder { + return EpisodeViewHolder( + EpisodeViewHolderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) { + val episode = getItem(holder.absoluteAdapterPosition) + + holder.binding.apply { + episode.thumbnail_path?.let { + epImage.loadImage(it) + } + epDuration.text = "${episode.episode_duration}" + episode.content_more_details?.let {moreDetailsList -> + if (moreDetailsList.isNotEmpty()){ + if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){ + moreDetailsList[1]?.let {moreDetail -> + epTitle.text = moreDetail.title + } + }else{ + moreDetailsList[0]?.let {moreDetail -> + epTitle.text = moreDetail.title + } + } + } + } + + card.setOnClickListener { + episode.id?.let { episodeId -> + onEpisodeClicked?.invoke(holder.absoluteAdapterPosition) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/adapters/TeaserAdapter.kt b/app/src/main/java/com/woka/webseries/adapters/TeaserAdapter.kt new file mode 100644 index 0000000..55e069b --- /dev/null +++ b/app/src/main/java/com/woka/webseries/adapters/TeaserAdapter.kt @@ -0,0 +1,77 @@ +package com.woka.webseries.adapters; + + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.woka.WokaApp.Companion.userPrefs +import com.woka.databinding.EpisodeViewHolderBinding +import com.woka.webseries.models.teaserdata.TeaserData +import java.util.concurrent.Executors + +class TeaserAdapter private constructor(val context: Context, + config: AsyncDifferConfig +): ListAdapter(config) { + + inner class EpisodeViewHolder(val binding: EpisodeViewHolderBinding): RecyclerView.ViewHolder(binding.root) + + companion object{ + + private val DIFF_UTIL = object : DiffUtil.ItemCallback(){ + override fun areItemsTheSame(oldItem: TeaserData, newItem: TeaserData): Boolean = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: TeaserData, newItem: TeaserData): Boolean { + return oldItem == newItem + } + } + + private val DIFF_CONFIG = AsyncDifferConfig.Builder(DIFF_UTIL) + .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor()) + .build() + } + + constructor(context: Context): this(context, DIFF_CONFIG) + + var onEpisodeClicked: ((position: Int) -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeViewHolder { + return EpisodeViewHolder( + EpisodeViewHolderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) { + val episode = getItem(holder.absoluteAdapterPosition) + + holder.binding.apply { + episode.thumbnail_path?.let { + epImage.loadImage(it) + } + epDuration.text = "${episode.teaser_duration}" + episode.content_more_details?.let {moreDetailsList -> + if (moreDetailsList.isNotEmpty()){ + if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){ + moreDetailsList[1]?.let {moreDetail -> + epTitle.text = moreDetail.title + } + }else{ + moreDetailsList[0]?.let {moreDetail -> + epTitle.text = moreDetail.title + } + } + } + } + + card.setOnClickListener { + onEpisodeClicked?.invoke(holder.absoluteAdapterPosition) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/episodedata/ContentMoreDetail.kt b/app/src/main/java/com/woka/webseries/models/episodedata/ContentMoreDetail.kt new file mode 100644 index 0000000..d716c12 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/episodedata/ContentMoreDetail.kt @@ -0,0 +1,13 @@ +package com.woka.webseries.models.episodedata + +data class ContentMoreDetail( + val content_hd_url: String?, + val content_id: Int?, + val content_sd_url: String?, + val description: String?, + val id: Int?, + val language_master_id: Int?, + val post_type: Int?, + val tags_keywords: String?, + val title: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeData.kt b/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeData.kt new file mode 100644 index 0000000..78b7c73 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeData.kt @@ -0,0 +1,20 @@ +package com.woka.webseries.models.episodedata + +data class EpisodeData( + val content_more_details: List?, + val episode_description: String?, + val episode_duration: String?, + val episode_number: Int?, + val episode_title: String?, + val episode_url: String?, + val id: Int?, + val language_master_id: Int?, + val release_date: String?, + val season_data: SeasonData?, + val season_master_id: Int?, + val tags_keyword: String?, + val thumbnail_img_url: String?, + val thumbnail_path: String?, + val user_video_view: List?, + val watch_shows_master_id: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeResponseData.kt b/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeResponseData.kt new file mode 100644 index 0000000..72b7535 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/episodedata/EpisodeResponseData.kt @@ -0,0 +1,6 @@ +package com.woka.webseries.models.episodedata + +data class EpisodeResponseData( + val result: List?, + val total_records: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/episodedata/SeasonData.kt b/app/src/main/java/com/woka/webseries/models/episodedata/SeasonData.kt new file mode 100644 index 0000000..58a254b --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/episodedata/SeasonData.kt @@ -0,0 +1,7 @@ +package com.woka.webseries.models.episodedata + +data class SeasonData( + val id: Int?, + val season_number: String?, + val watch_shows_master_id: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/teaserdata/ContentMoreDetail.kt b/app/src/main/java/com/woka/webseries/models/teaserdata/ContentMoreDetail.kt new file mode 100644 index 0000000..f002955 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/teaserdata/ContentMoreDetail.kt @@ -0,0 +1,13 @@ +package com.woka.webseries.models.teaserdata + +data class ContentMoreDetail( + val content_hd_url: String?, + val content_id: Int?, + val content_sd_url: String?, + val description: String?, + val id: Int?, + val language_master_id: Int?, + val post_type: Int?, + val tags_keywords: String?, + val title: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/teaserdata/SeasonData.kt b/app/src/main/java/com/woka/webseries/models/teaserdata/SeasonData.kt new file mode 100644 index 0000000..58762c7 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/teaserdata/SeasonData.kt @@ -0,0 +1,7 @@ +package com.woka.webseries.models.teaserdata + +data class SeasonData( + val id: Int?, + val season_number: String?, + val watch_shows_master_id: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserData.kt b/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserData.kt new file mode 100644 index 0000000..c422645 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserData.kt @@ -0,0 +1,21 @@ +package com.woka.webseries.models.teaserdata + +data class TeaserData( + val content_more_details: List?, + val id: Int?, + val language_master_id: Int?, + val release_date: String?, + val season_data: SeasonData?, + val season_master_id: Int?, + val serial_number: Int?, + val tags_keyword: String?, + val teaser_description: String?, + val teaser_duration: String?, + val teaser_number: Int?, + val teaser_title: String?, + val teaser_url: String?, + val thumbnail_img_url: String?, + val thumbnail_path: String?, + val user_video_view: List?, + val watch_shows_master_id: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserResponseData.kt b/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserResponseData.kt new file mode 100644 index 0000000..b7f2987 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/models/teaserdata/TeaserResponseData.kt @@ -0,0 +1,6 @@ +package com.woka.webseries.models.teaserdata + +data class TeaserResponseData( + val result: List?, + val total_records: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/views/SeasonActivity.kt b/app/src/main/java/com/woka/webseries/views/SeasonActivity.kt index e47253a..487fb36 100644 --- a/app/src/main/java/com/woka/webseries/views/SeasonActivity.kt +++ b/app/src/main/java/com/woka/webseries/views/SeasonActivity.kt @@ -3,20 +3,30 @@ package com.woka.webseries.views import android.content.Intent import android.graphics.Color import android.os.Bundle +import android.text.Html import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import com.bumptech.glide.Glide import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import com.jwplayer.pub.api.media.playlists.PlaylistItem import com.woka.R +import com.woka.WokaApp.Companion.userPrefs import com.woka.databinding.ActivitySeasonBinding import com.woka.networking.ApiResult +import com.woka.players.PlayerActivity +import com.woka.players.PlayerActivity.Companion.EXTRA_EPISODE_INDEX +import com.woka.players.PlayerActivity.Companion.EXTRA_PLAY_LIST +import com.woka.utils.ProgressView +import com.woka.utils.hide import com.woka.utils.isNetworkConnected import com.woka.utils.lightStatusBar +import com.woka.utils.show import com.woka.utils.toast import com.woka.webseries.WebSeriesRepository +import com.woka.webseries.adapters.EpisodeAdapter +import com.woka.webseries.adapters.TeaserAdapter import com.woka.webseries.models.ShowData import kotlin.math.max @@ -36,6 +46,13 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { private var showPosition: Int = -1 + private lateinit var progressView: ProgressView + + private lateinit var episodeAdapter: EpisodeAdapter + private lateinit var teaserAdapter: TeaserAdapter + + private var selectedSeasonId = -1 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -61,6 +78,12 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { return } + episodeAdapter = EpisodeAdapter(this) + teaserAdapter = TeaserAdapter(this) + + progressView = ProgressView(this) + progressView.show(getString(R.string.please_wait)) + WebSeriesRepository.webSeriesData[categoryId]?.show_data?.let { for (show in it){ if (showId == show?.id){ @@ -77,17 +100,16 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { setObservers() WebSeriesRepository.loadSeasonListing(showId, categoryId!!) + } + override fun onDestroy() { + super.onDestroy() + WebSeriesRepository.clearEpisodeData() } private fun initViews() { if (showData != null && categoryId != null) { binding.apply { - showData!!.thumbnail_path?.let { - Glide.with(applicationContext) - .load(it) - .into(image) - } likeCount.text = "${showData!!.likes_count}" @@ -95,6 +117,15 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { likeSeason.isSelected = showData!!.is_liked?:false + binding.seasonsTab.addOnTabSelectedListener(this@SeasonActivity) + + image.onLoadSuccessListener = {playTrailer.show()} + + rvEpisodes.adapter = episodeAdapter + episodeAdapter.onEpisodeClicked = ::onEpisodeClicked + + rvTeaser.adapter = teaserAdapter + teaserAdapter.onEpisodeClicked = ::onTeaserClicked } } } @@ -161,10 +192,16 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { private fun setObservers(){ WebSeriesRepository.seasonDataLiveData.observe(this){ + binding.seasonsTab.removeAllTabs() when(it){ - is ApiResult.Error -> {} - is ApiResult.Loading -> {} + is ApiResult.Error -> { + progressView.hide() + } + is ApiResult.Loading -> { + progressView.show() + } is ApiResult.Success -> { + progressView.hide() it.data?.result?.let {seasonList -> for (season in seasonList){ if (season == null) continue @@ -174,9 +211,131 @@ class SeasonActivity : AppCompatActivity(), OnTabSelectedListener { } } } + + WebSeriesRepository.episodeDataLiveData.observe(this){ + when(it){ + is ApiResult.Error -> { + binding.epShimmer.hide() + binding.rvEpisodes.hide() + binding.seasonMediaType.hide() + } + is ApiResult.Loading -> { + binding.epShimmer.show() + binding.rvEpisodes.hide() + binding.seasonMediaType.hide() + } + is ApiResult.Success -> { + it.data?.result?.filterNotNull()?.let {episodeList -> + + if (episodeList.isNotEmpty()){ + episodeAdapter.submitList(episodeList){ + binding.epShimmer.hide() + binding.rvEpisodes.show() + binding.seasonMediaType.show() + } + }else{ + binding.epShimmer.hide() + binding.seasonMediaType.hide() + binding.rvEpisodes.hide() + } + } + } + } + } + + WebSeriesRepository.teaserDataLiveData.observe(this){ + when(it){ + is ApiResult.Error -> { + binding.rvTeaser.hide() + binding.teaserTxt.hide() + } + is ApiResult.Loading -> { + binding.rvTeaser.hide() + binding.teaserTxt.hide() + } + is ApiResult.Success -> { + it.data?.result?.filterNotNull()?.let {episodeList -> + + if (episodeList.isNotEmpty()){ + teaserAdapter.submitList(episodeList){ + binding.rvTeaser.show() + binding.teaserTxt.show() + } + }else{ + binding.teaserTxt.hide() + binding.rvTeaser.hide() + } + } + } + } + } } - override fun onTabSelected(p0: TabLayout.Tab?) {} + private fun loadSeasonData() { + binding.apply { + if (seasonsTab.selectedTabPosition >= 0){ + WebSeriesRepository.seasonDataMap["${showId}_${categoryId}"]?.result?.let {seasonList -> + if (seasonsTab.selectedTabPosition < seasonList.size){ + seasonList[seasonsTab.selectedTabPosition]?.let {seasonData -> + + // loading episode data + seasonData.id?.let { + selectedSeasonId = it + WebSeriesRepository.loadEpisodeData(showId, it) + WebSeriesRepository.loadTeaserData(showId, it) + } + + seasonYear.text = "${seasonData.release_year}" + val noOfEpisodes = "${seasonData.no_of_episodes} ${seasonData.media_type}" + episodeNumber.text = noOfEpisodes + + seasonMediaType.text = "${seasonData.media_type}" + + seasonData.thumbnail_path?.let { + image.loadImage(it) + } + + seasonData.season_more_details?.let {moreDetailsList -> + if (moreDetailsList.isNotEmpty()){ + if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){ + moreDetailsList[1]?.let {moreDetails -> + seasonTitle.text = moreDetails.title?.uppercase() + seasonDescription.text = Html.fromHtml(moreDetails.description?.replace("
", " "), Html.FROM_HTML_MODE_LEGACY) + } + }else{ + moreDetailsList[0]?.let {moreDetails -> + seasonTitle.text = moreDetails.title?.uppercase() + seasonDescription.text = Html.fromHtml(moreDetails.description?.replace("
", " "), Html.FROM_HTML_MODE_LEGACY) + } + } + } + } + + } + } + } + } + } + } + + private fun onEpisodeClicked(position: Int){ + startActivity(Intent(this, PlayerActivity::class.java).apply { + putParcelableArrayListExtra(EXTRA_PLAY_LIST, WebSeriesRepository.episodesPlaylistMap["${showId}_${selectedSeasonId}_$categoryId"]) + putExtra(EXTRA_EPISODE_INDEX, position) + }) + } + + private fun onTeaserClicked(position: Int){ + startActivity(Intent(this, PlayerActivity::class.java).apply { + putParcelableArrayListExtra(EXTRA_PLAY_LIST, WebSeriesRepository.teasersPlaylistMap["${showId}_${selectedSeasonId}_$categoryId"]) + putExtra(EXTRA_EPISODE_INDEX, position) + }) + } + + override fun onTabSelected(p0: TabLayout.Tab?) { + binding.playTrailer.hide() + loadSeasonData() + } override fun onTabUnselected(p0: TabLayout.Tab?) {} diff --git a/app/src/main/res/drawable/season_tab_bg.xml b/app/src/main/res/drawable/season_tab_bg.xml index efc0c84..2027107 100644 --- a/app/src/main/res/drawable/season_tab_bg.xml +++ b/app/src/main/res/drawable/season_tab_bg.xml @@ -1,23 +1,18 @@ - - - - - - - - + + + + + + + + - - - - - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml new file mode 100644 index 0000000..3eeac5d --- /dev/null +++ b/app/src/main/res/layout/activity_player.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_season.xml b/app/src/main/res/layout/activity_season.xml index 20a146c..6956159 100644 --- a/app/src/main/res/layout/activity_season.xml +++ b/app/src/main/res/layout/activity_season.xml @@ -1,5 +1,5 @@ - - - - + app:isHapticEnabled="true">