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
This commit is contained in:
2024-06-25 20:55:14 +05:30
parent eea631bc5e
commit fdf7cf4928
25 changed files with 909 additions and 69 deletions

View File

@@ -15,10 +15,18 @@
android:supportsRtl="true"
android:theme="@style/Theme.Woka"
tools:targetApi="31">
<activity
android:name=".players.PlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:exported="false"
android:launchMode="singleTask"
android:screenOrientation="sensorLandscape"
android:supportsPictureInPicture="true"
android:theme="@style/FullScreenTheme"/>
<activity
android:name=".webseries.views.SeasonActivity"
android:exported="false"
android:screenOrientation="portrait" />
android:screenOrientation="portrait" />
<activity
android:name=".webseries.views.WebSeriesActivity"
android:exported="false"

View File

@@ -98,6 +98,7 @@ class LiveStreamPlayerActivity : AppCompatActivity(), FullscreenHandler {
}
override fun onAllowRotationChanged(allowRotation: Boolean) {}
override fun onAllowFullscreenPortraitChanged(allowFullscreenPortrait: Boolean) {}
override fun updateLayoutParams(layoutParams: ViewGroup.LayoutParams?) {}

View File

@@ -0,0 +1,87 @@
package com.woka.players
import android.os.Bundle
import android.view.ViewGroup
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.jwplayer.pub.api.JWPlayer
import com.jwplayer.pub.api.configuration.PlayerConfig
import com.jwplayer.pub.api.fullscreen.FullscreenHandler
import com.jwplayer.pub.api.media.playlists.PlaylistItem
import com.woka.databinding.ActivityPlayerBinding
class PlayerActivity : AppCompatActivity(), FullscreenHandler {
companion object{
const val EXTRA_PLAY_LIST = "episode_key"
const val EXTRA_EPISODE_INDEX = "episode_index"
}
private lateinit var binding: ActivityPlayerBinding
private lateinit var player: JWPlayer
private var playList: List<PlaylistItem>? = 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) {}
}

View File

@@ -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
}
})

View File

@@ -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<ApiResponse<SeasonDataResponse>>
@POST("episode_listing")
suspend fun episodeListing(@Body formBody: FormBody): Response<ApiResponse<EpisodeResponseData>>
@POST("teaser_listing")
suspend fun teaserListing(@Body formBody: FormBody): Response<ApiResponse<TeaserResponseData>>
}

View File

@@ -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<ApiResult<SeasonDataResponse>>
get() = _seasonDataLiveData
private var seasonDataMap = HashMap<Int, SeasonDataResponse>()
// seasons data for every show. where {key -> "showId_categoryId"}
var seasonDataMap = HashMap<String, SeasonDataResponse>()
init {
loadContinueWatchData()
// episode listing data
private val _episodeDataLiveData = MutableLiveData<ApiResult<EpisodeResponseData>>()
val episodeDataLiveData: LiveData<ApiResult<EpisodeResponseData>>
get() = _episodeDataLiveData
// episode data for every season. where {key -> "showId_seasonId"}
private var episodeDataMap = HashMap<String, EpisodeResponseData>()
// episodes playlist, where {key -> "showId_seasonId_categoryId"}
var episodesPlaylistMap = HashMap<String, ArrayList<PlaylistItem>>()
// teaser listing data
private val _teaserDataLiveData = MutableLiveData<ApiResult<TeaserResponseData>>()
val teaserDataLiveData: LiveData<ApiResult<TeaserResponseData>>
get() = _teaserDataLiveData
// teaser data for every season. where {key -> "showId_seasonId"}
private var teaserDataMap = HashMap<String, TeaserResponseData>()
// teasers playlist, where {key -> "showId_seasonId_categoryId"}
var teasersPlaylistMap = HashMap<String, ArrayList<PlaylistItem>>()
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<PlaylistItem>()
val englishPlayList = ArrayList<PlaylistItem>()
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<PlaylistItem>()
val englishPlayList = ArrayList<PlaylistItem>()
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))

View File

@@ -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<EpisodeData>
): ListAdapter<EpisodeData, EpisodeAdapter.EpisodeViewHolder>(config) {
inner class EpisodeViewHolder(val binding: EpisodeViewHolderBinding): RecyclerView.ViewHolder(binding.root)
companion object{
private val DIFF_UTIL = object : DiffUtil.ItemCallback<EpisodeData>(){
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)
}
}
}
}
}

View File

@@ -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<TeaserData>
): ListAdapter<TeaserData, TeaserAdapter.EpisodeViewHolder>(config) {
inner class EpisodeViewHolder(val binding: EpisodeViewHolderBinding): RecyclerView.ViewHolder(binding.root)
companion object{
private val DIFF_UTIL = object : DiffUtil.ItemCallback<TeaserData>(){
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)
}
}
}
}

View File

@@ -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?
)

View File

@@ -0,0 +1,20 @@
package com.woka.webseries.models.episodedata
data class EpisodeData(
val content_more_details: List<ContentMoreDetail?>?,
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<Any?>?,
val watch_shows_master_id: Int?
)

View File

@@ -0,0 +1,6 @@
package com.woka.webseries.models.episodedata
data class EpisodeResponseData(
val result: List<EpisodeData?>?,
val total_records: Int?
)

View File

@@ -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?
)

View File

@@ -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?
)

View File

@@ -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?
)

View File

@@ -0,0 +1,21 @@
package com.woka.webseries.models.teaserdata
data class TeaserData(
val content_more_details: List<ContentMoreDetail?>?,
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<Any?>?,
val watch_shows_master_id: Int?
)

View File

@@ -0,0 +1,6 @@
package com.woka.webseries.models.teaserdata
data class TeaserResponseData(
val result: List<TeaserData?>?,
val total_records: Int?
)

View File

@@ -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("<br>", " "), Html.FROM_HTML_MODE_LEGACY)
}
}else{
moreDetailsList[0]?.let {moreDetails ->
seasonTitle.text = moreDetails.title?.uppercase()
seasonDescription.text = Html.fromHtml(moreDetails.description?.replace("<br>", " "), 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?) {}

View File

@@ -1,23 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
<item android:state_focused="true" android:state_selected="true">
<shape android:shape="rectangle">
<stroke android:width="3dp" android:color="#5e1fc4"/>
<corners android:radius="25dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_selected="true">
<shape android:shape="rectangle">
<stroke android:width="3dp" android:color="@color/color_primary"/>
<stroke android:width="10dp" android:color="@color/color_primary"/>
<corners android:radius="25dp"/>
<solid android:color="#5e1fc4"/>
</shape>
</item>
<item android:state_selected="false">
<shape android:shape="rectangle">
<stroke android:width="10dp" android:color="@color/color_primary"/>
<corners android:radius="25dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<stroke android:width="3dp" android:color="#5e1fc4"/>
<corners android:radius="25dp"/>
</shape>
</item>
</selector>
</selector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<com.jwplayer.pub.view.JWPlayerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/player_view"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".players.PlayerActivity"/>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
@@ -19,7 +19,7 @@
android:background="@drawable/img_season_bg"
android:paddingHorizontal="15dp"
android:paddingTop="15dp"
android:paddingBottom="35dp"
android:paddingBottom="40dp"
android:orientation="vertical">
<ImageView
@@ -57,7 +57,7 @@
android:layout_margin="10dp"
>
<ImageView
<com.woka.utils.AdiImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -65,9 +65,12 @@
android:src="@android:color/darker_gray"
android:contentDescription="@string/masila"
android:scaleType="fitXY"
app:imageCornerRadius="5dp"
/>
<com.woka.utils.PressableCard
android:id="@+id/play_trailer"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_25"
@@ -143,23 +146,6 @@
/>
<TextView
android:id="@+id/seaon_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="1 HR 25 MIN"
android:fontFamily="@font/exo_2"
android:textColor="@color/color_primary"
android:layout_marginTop="5dp"
android:layout_marginStart="25dp"
app:layout_constraintStart_toEndOf="@id/episode_number"
app:layout_constraintTop_toBottomOf="@id/season_title"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -222,9 +208,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/season_description"
app:isHapticEnabled="true"
android:layout_marginTop="20dp">
app:isHapticEnabled="true">
<Button
android:layout_width="wrap_content"
@@ -387,8 +371,8 @@
<com.google.android.material.tabs.TabLayout
android:id="@+id/seasons_tab"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginStart="15dp"
android:layout_height="60dp"
android:layout_marginStart="5dp"
android:layout_marginTop="15dp"
app:tabBackground="@drawable/season_tab_bg"
app:tabGravity="start"
@@ -397,20 +381,94 @@
app:tabPaddingEnd="25dp"
app:tabPaddingStart="25dp"
app:tabRippleColor="@android:color/transparent"
app:tabSelectedTextColor="@color/color_primary"
app:tabTextColor="@color/white" />
app:tabSelectedTextColor="@color/white"
app:tabTextColor="@color/color_primary" />
<ProgressBar
android:id="@+id/progress_bar"
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/ep_shimmer"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_auto_start="true"
app:shimmer_highlight_alpha="0.5"
app:shimmer_base_alpha="0.6"
android:layout_marginTop="10dp"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/episode_view_holder"/>
<include layout="@layout/episode_view_holder"/>
<include layout="@layout/episode_view_holder"/>
<include layout="@layout/episode_view_holder"/>
</LinearLayout>
</ScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
<TextView
android:id="@+id/season_media_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminateTint="@color/white"
android:layout_gravity="center_horizontal"
tools:text="Episodes"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_episodes"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/episode_view_holder"
android:orientation="vertical"
android:layout_marginTop="15dp"
/>
<TextView
android:id="@+id/teaser_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="@string/teasers"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginStart="15dp"
android:layout_marginTop="10dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_teaser"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/episode_view_holder"
android:orientation="vertical"
android:layout_marginTop="15dp"
/>
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>

View File

@@ -272,10 +272,9 @@
<Spinner
android:id="@+id/category_spinner"
android:layout_width="match_parent"
android:layout_height="@dimen/_37sdp"
android:layout_height="@dimen/_40sdp"
android:popupBackground="@drawable/round_bg_5_white"
android:textAlignment="textStart"
/>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginBottom="15dp"
android:layout_marginRight="15dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="15dp"
app:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:weightSum="10">
<com.woka.utils.AdiImageView
android:id="@+id/ep_image"
android:layout_width="0dp"
android:layout_height="@dimen/_45sdp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_weight="3"
app:imageCornerRadius="5dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="6"
android:layout_gravity="top"
android:layout_marginVertical="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/ep_title"
tool:text="Episode name here"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:fontFamily="@font/exo_2_bold"
android:maxLines="2"
android:textSize="@dimen/_13ssp"
android:textColor="@color/color_primary" />
<TextView
android:id="@+id/ep_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/_10ssp"
tool:text="00:00:12"
android:fontFamily="@font/exo_2"
android:textColor="@color/color_primary" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="10dp"
android:visibility="gone"
tool:progress="100"
app:indicatorColor="#ce0000"
app:trackColor="#b2b2b2"
app:trackCornerRadius="5dp"
app:trackThickness="3dp" />
</LinearLayout>
<ImageView
android:id="@+id/play_btn"
android:layout_width="0dp"
android:layout_height="@dimen/_24ssp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:contentDescription="@string/play"
android:src="@drawable/ic_play_filled"
app:tint="@color/color_primary" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -4,8 +4,6 @@
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:paddingVertical="15dp"
android:paddingHorizontal="15dp"
xmlns:ools="http://schemas.android.com/apk/res-auto">
@@ -19,6 +17,8 @@
android:textColor="@color/black"
android:textSize="@dimen/_12ssp"
android:layout_margin="15dp"
android:textAlignment="textStart"
ools:layout_constraintStart_toStartOf="parent"
@@ -36,6 +36,8 @@
android:contentDescription="@string/image"
android:src="@drawable/ic_tick"
android:layout_marginEnd="15dp"
ools:layout_constraintEnd_toEndOf="parent"
ools:layout_constraintTop_toTopOf="parent"
ools:layout_constraintBottom_toBottomOf="parent"

View File

@@ -181,4 +181,6 @@
<string name="add">ADD</string>
<string name="rate">RATE</string>
<string name="share">SHARE</string>
<string name="couldnt_play_video">Couldn\'t play video</string>
<string name="teasers">Teasers</string>
</resources>