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">