MVVM structure for all:

Karaoke, audiobooks, games (Completed)

Started implementing new ui structure for web series (Single activity and multiple fragments)

Completed first web series fragment

Completed implementing mvvm for web series
This commit is contained in:
2024-07-16 20:50:06 +05:30
parent c828a6ae00
commit c593a7550c
29 changed files with 1808 additions and 452 deletions

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".WokaApp"
@@ -17,18 +17,21 @@
android:supportsRtl="true"
android:theme="@style/Theme.Woka"
tools:targetApi="31">
<activity
android:name=".webseries.views.SeriesActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".karaoke.player.KaraokePlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode|fontScale"
android:exported="false"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:screenOrientation="portrait"
/>
android:supportsPictureInPicture="true" />
<activity
android:name=".karaoke.views.KaraokeActivity"
android:exported="false"
android:screenOrientation="portrait"/>
android:screenOrientation="portrait" />
<activity
android:name=".wokagames.playerr.GamePlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"

View File

@@ -2,11 +2,13 @@ package com.woka.audiobooks
import com.woka.audiobooks.models.audiodata.AudioBooksResponse
import com.woka.networking.ApiResponse
import okhttp3.FormBody
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
interface AudioBookApiService {
@POST("listen_audio_listing")
suspend fun audioBookListing(): Response<ApiResponse<AudioBooksResponse>>
suspend fun audioBookListing(@Body formBody: FormBody): Response<ApiResponse<AudioBooksResponse>>
}

View File

@@ -1,7 +1,5 @@
package com.woka.audiobooks
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.woka.audiobooks.models.audiodata.AudioBookData
import com.woka.audiobooks.models.audiodata.AudioBooksResponse
import com.woka.audiobooks.models.continuedata.ContinueAudioResponse
@@ -12,7 +10,6 @@ import com.woka.networking.ApiResult
import com.woka.networking.RetrofitHelper
import com.woka.networking.RetrofitHelper.handleApiCall
import com.woka.userdata.UserActionApiService
import com.woka.utils.TAG
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -25,75 +22,25 @@ object AudioBookRepository {
private val apiService = RetrofitHelper.getRetrofit().create(AudioBookApiService::class.java)
private val userActionApiService = RetrofitHelper.getRetrofit().create(UserActionApiService::class.java)
// live data
// audio books data (loose caching)
private val _audioBooksLiveData = MutableLiveData<ApiResult<AudioBooksResponse>>()
val audioBooksLiveData: LiveData<ApiResult<AudioBooksResponse>>
get() = _audioBooksLiveData
private var audioBooksData: AudioBooksResponse? = null
// continue watch audio books data
private val _continueAudioBooksLiveData = MutableLiveData<ApiResult<ContinueAudioResponse>>()
val continueAudioBooksLiveData: LiveData<ApiResult<ContinueAudioResponse>>
get() = _continueAudioBooksLiveData
private var continueAudioData: ContinueAudioResponse? = null
fun loadAudioBooks(){
if (audioBooksData != null){
_audioBooksLiveData.postValue(ApiResult.Success(audioBooksData))
return
}
CoroutineScope(Dispatchers.IO).launch{
_audioBooksLiveData.postValue(ApiResult.Loading())
val response = handleApiCall{
apiService.audioBookListing()
}
when (response){
is ApiResult.Error -> _audioBooksLiveData.postValue(ApiResult.Error(response.errorMessage, response.error))
is ApiResult.Loading -> _audioBooksLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
response.data?.let {
audioBooksData = it
_audioBooksLiveData.postValue(ApiResult.Success(it))
}?:{
_audioBooksLiveData.postValue(ApiResult.Error())
}
}
}
suspend fun loadAudioBooks(pageNo: Int, quantity: Int): ApiResult<AudioBooksResponse> {
return handleApiCall {
apiService.audioBookListing(
FormBody.Builder()
.add("api_version", "v2")
.add("start", "$pageNo")
.add("limit", "$quantity")
.build()
)
}
}
fun loadContinueListening(){
if (continueAudioData != null){
_continueAudioBooksLiveData.postValue(ApiResult.Success(continueAudioData))
return
}
CoroutineScope(Dispatchers.IO).launch {
val response = handleApiCall {
userActionApiService.continueAudioBookListing(
FormBody.Builder()
.add("post_type", PostType.AUDIO_BOOKS.value)
.build()
)
}
when (response){
is ApiResult.Error -> {}
is ApiResult.Loading -> {}
is ApiResult.Success -> {
response.data?.let {
continueAudioData = it
}
}
}
_continueAudioBooksLiveData.postValue(response)
suspend fun loadContinueLiveData(): ApiResult<ContinueAudioResponse> {
return handleApiCall {
userActionApiService.continueAudioBookListing(
FormBody.Builder()
.add("post_type", PostType.AUDIO_BOOKS.value)
.build()
)
}
}
@@ -122,48 +69,6 @@ object AudioBookRepository {
}
private fun changeLikeLocally(id: String, isLiked: Boolean){
// changing in audio books locally
audioBooksData?.audio_data?.let {audioBooks ->
for (audio in audioBooks){
var found = false
audio?.let {data ->
if ("${data.id}" == id){
data.is_liked = isLiked
data.likes_count?.let { count ->
data.likes_count = if (isLiked) count + 1
else max(0, count - 1)
}
found = true
}
}
if (found) break
}
}
// continue audio books data update
continueAudioData?.result?.let {
for (audio in it){
var found = false
audio?.let {data ->
if ("${data.id}" == id){
data.is_liked = isLiked
data.likes_count?.let { count ->
data.likes_count = if (isLiked) count + 1
else max(0, count - 1)
}
found = true
}
}
if (found) break
}
}
// changing in fav list locally
MyListRepository.myFavData.result?.audio_data?.let {
for (audioData in it){
@@ -208,29 +113,5 @@ object AudioBookRepository {
favAudioData.removeIf{it.id == audioBookData.id}
}
}
audioBooksData?.audio_data?.let {
for (audio in it){
if (audio?.id == audioBookData.id){
audio?.mark_as_favourite = addToBookmark
break
}
}
}
continueAudioData?.result?.let {
for (audio in it){
if (audio?.id == audioBookData.id){
audio?.mark_as_favourite = addToBookmark
break
}
}
}
}
fun clearData(){
audioBooksData = null
continueAudioData = null
}
}

View File

@@ -16,11 +16,12 @@ import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
import kotlin.math.max
class AudioBooksAdapter(
private val context: Context,
private var onBookClicked: (AudioBookData) -> Unit,
private var onBookChanged: (id: Int) -> Unit
private var onBookChanged: (id: Int, AudioBookData) -> Unit
): ListAdapter<AudioBookData, AudioBooksAdapter.AudioBookViewHolder>(ASYNC_DIFF_UTIL) {
inner class AudioBookViewHolder(val binding: ShowViewHolderBinding): ViewHolder(binding.root)
@@ -96,10 +97,24 @@ class AudioBooksAdapter(
!like.isSelected
)
audioBook.likes_count?.let {
audioBook.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
audioBook.is_liked = !like.isSelected
like.isSelected = !like.isSelected
likeCount.text = "${audioBook.likes_count}"
audioBook.id?.let(onBookChanged)
audioBook.id?.let{
onBookChanged(it, audioBook)
}
}
fav.isSelected = audioBook.mark_as_favourite == true
@@ -115,8 +130,12 @@ class AudioBooksAdapter(
!fav.isSelected
)
audioBook.mark_as_favourite = !fav.isSelected
fav.isSelected = audioBook.mark_as_favourite == true
audioBook.id?.let(onBookChanged)
audioBook.id?.let{
onBookChanged(it, audioBook)
}
}
root.setOnClickListener {

View File

@@ -17,10 +17,11 @@ import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
import kotlin.math.max
class ContinueAudioAdapter(private val context: Context,
private var onBookClicked: (ContinueAudioData) -> Unit,
private var onContinueBookChanged: (id: Int) -> Unit): ListAdapter<ContinueAudioData, FavoriteViewHolder>(DIFF_CONFIG) {
private var onContinueBookChanged: (id: Int, AudioBookData) -> Unit): ListAdapter<ContinueAudioData, FavoriteViewHolder>(DIFF_CONFIG) {
companion object{
private val DIFF_UTIL = object : DiffUtil.ItemCallback<ContinueAudioData>(){
@@ -91,10 +92,22 @@ class ContinueAudioAdapter(private val context: Context,
!like.isSelected
)
audioBook.likes_count?.let {
audioBook.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
audioBook.is_liked = !like.isSelected
like.isSelected = !like.isSelected
likeCount.text = "${audioBook.likes_count}"
audioBook?.id?.let(onContinueBookChanged)
audioBook?.id?.let{onContinueBookChanged(it, AudioBookData(audioBook))}
}
fav.isSelected = audioBook.mark_as_favourite == true
@@ -110,8 +123,11 @@ class ContinueAudioAdapter(private val context: Context,
!fav.isSelected
)
audioBook.mark_as_favourite = !fav.isSelected
fav.isSelected = audioBook.mark_as_favourite == true
audioBook?.id?.let(onContinueBookChanged)
audioBook?.id?.let{onContinueBookChanged(it, AudioBookData(audioBook))}
}
root.setOnClickListener {

View File

@@ -0,0 +1,81 @@
package com.woka.audiobooks.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woka.audiobooks.AudioBookRepository
import com.woka.audiobooks.models.audiodata.AudioBookData
import com.woka.audiobooks.models.continuedata.ContinueAudioData
import com.woka.networking.ApiResult
import kotlinx.coroutines.launch
class AudioBookViewModel: ViewModel() {
private val _audioBookData = MutableLiveData<ApiResult<MutableList<AudioBookData>>>()
val audioBookLiveData: LiveData<ApiResult<MutableList<AudioBookData>>>
get() = _audioBookData
private val audioBookList = ArrayList<AudioBookData>()
private var nextPageToLoad: Int = 0
private var quantityPerPage: Int = 6
var lastPage = false
private val _audioContinueLiveData = MutableLiveData<ApiResult<List<ContinueAudioData>>>()
val audioContinueLiveData: LiveData<ApiResult<List<ContinueAudioData>>>
get() = _audioContinueLiveData
fun loadKaraokeSongs() {
viewModelScope.launch {
_audioBookData.postValue(ApiResult.Loading())
when (val value = AudioBookRepository.loadAudioBooks(nextPageToLoad, quantityPerPage)) {
is ApiResult.Error -> _audioBookData.postValue(
ApiResult.Error(
value.errorMessage,
value.error
)
)
is ApiResult.Loading -> _audioBookData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
value.data?.let {
it.audio_data?.filterNotNull()?.let { newList ->
audioBookList.addAll(newList)
lastPage = audioBookList.size == it.total_records
_audioBookData.postValue(ApiResult.Success(audioBookList))
nextPageToLoad++
}
}
}
}
}
}
fun loadContinueData(){
viewModelScope.launch {
_audioContinueLiveData.postValue(ApiResult.Loading())
when (val value = AudioBookRepository.loadContinueLiveData()) {
is ApiResult.Error -> _audioContinueLiveData.postValue(
ApiResult.Error(
value.errorMessage,
value.error
)
)
is ApiResult.Loading -> _audioContinueLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
value.data?.let {
it.result?.filterNotNull()?.let { newList ->
_audioContinueLiveData.postValue(ApiResult.Success(newList))
}
}
}
}
}
}
}

View File

@@ -11,32 +11,37 @@ import android.view.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.jwplayer.pub.api.media.playlists.PlaylistItem
import com.woka.R
import com.woka.WokaApp
import com.woka.WokaApp.Companion.userPrefs
import com.woka.audiobooks.AudioBookRepository
import com.woka.audiobooks.adapters.AudioBooksAdapter
import com.woka.audiobooks.adapters.ContinueAudioAdapter
import com.woka.audiobooks.models.audiodata.AudioBookData
import com.woka.audiobooks.models.continuedata.ContinueAudioData
import com.woka.audiobooks.viewmodels.AudioBookViewModel
import com.woka.databinding.ActivityAudioBooksBinding
import com.woka.databinding.DialogModuleShowerBinding
import com.woka.networking.ApiResult
import com.woka.players.models.VideoPlayList
import com.woka.players.views.PlayerActivity
import com.woka.userPreference.UserType
import com.woka.utils.WokaBaseActivity
import com.woka.utils.hide
import com.woka.utils.setVisibility
import com.woka.utils.show
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import kotlin.math.max
class AudioBooksActivity : WokaBaseActivity() {
private lateinit var binding: ActivityAudioBooksBinding
private lateinit var viewModel: AudioBookViewModel
private lateinit var audioBookAdapter: AudioBooksAdapter
private lateinit var continueAudioAdapter: ContinueAudioAdapter
@@ -59,6 +64,8 @@ class AudioBooksActivity : WokaBaseActivity() {
navigationBarColor = getColor(R.color.color_primary_dark)
}
viewModel = ViewModelProvider(this)[AudioBookViewModel::class.java]
audioBookAdapter = AudioBooksAdapter(this, ::onBookClicked, ::onBookChanged)
continueAudioAdapter =
ContinueAudioAdapter(this, ::onContinueBookClicked, ::onContinueBookChanged)
@@ -75,7 +82,9 @@ class AudioBooksActivity : WokaBaseActivity() {
setObservers()
AudioBookRepository.loadAudioBooks()
if (!viewModel.audioBookLiveData.isInitialized){
viewModel.loadKaraokeSongs()
}
}
private fun initViews() {
@@ -120,52 +129,81 @@ class AudioBooksActivity : WokaBaseActivity() {
}
retryBtn.setOnClickListener {
AudioBookRepository.loadAudioBooks()
viewModel.loadKaraokeSongs()
}
loadMoreBtn.setOnClickListener {
viewModel.loadKaraokeSongs()
}
}
}
private fun setObservers() {
AudioBookRepository.audioBooksLiveData.observe(this) {
when (it) {
viewModel.audioBookLiveData.observe(this){
when(it){
is ApiResult.Error -> {
binding.shimmer.hide()
binding.rvAudioBooks.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
if (audioBookAdapter.currentList.size == 0) {
// none of the data is yet loaded
binding.rvAudioBooks.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
binding.errorView.show()
binding.errorView.show()
} else {
// error in loading more
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.retry)
binding.loadMoreBtn.show()
}
}
is ApiResult.Loading -> {
binding.shimmer.show()
binding.rvAudioBooks.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
binding.errorView.hide()
if (audioBookAdapter.currentList.size == 0) {
// loading first data
binding.shimmer.show()
binding.errorView.hide()
} else {
// loading more data
binding.loadMoreProgress.show()
binding.loadMoreBtn.hide()
}
}
is ApiResult.Success -> {
it.data?.audio_data?.filterNotNull()?.let { audioBookData ->
if (audioBookData.isNotEmpty()) {
binding.shimmer.hide()
binding.errorView.hide()
AudioBookRepository.loadContinueListening()
loadTrailerData(audioBookData[0])
it.data?.let {newList ->
if (newList.isNotEmpty()){
binding.rvAudioBooks.show()
binding.listenTxt.show()
binding.trailerView.show()
audioBookAdapter.submitList(audioBookData)
binding.errorView.hide()
binding.shimmer.hide()
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.load_more)
binding.loadMoreBtn.setVisibility(!viewModel.lastPage)
if (audioBookAdapter.currentList.isEmpty()){
// first data load
if (userPrefs?.userType != UserType.GUEST && !viewModel.audioContinueLiveData.isInitialized){
viewModel.loadContinueData()
}
loadTrailerData(newList[0])
audioBookAdapter.submitList(newList)
}else{
// loaded more data
audioBookAdapter.notifyItemRangeInserted(
audioBookAdapter.currentList.size,
newList.size
)
}
}
}
}
}
}
AudioBookRepository.continueAudioBooksLiveData.observe(this) {
viewModel.audioContinueLiveData.observe(this) {
when (it) {
is ApiResult.Error -> {
binding.continueListenTxt.hide()
@@ -174,12 +212,12 @@ class AudioBooksActivity : WokaBaseActivity() {
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.filterNotNull()?.let { continueData ->
if (continueData.isNotEmpty()) {
it.data?.let { continueList ->
if (continueList.isNotEmpty()) {
binding.continueListenTxt.show()
binding.rvContinueListen.show()
continueAudioAdapter.submitList(continueData.toMutableList())
continueAudioAdapter.submitList(continueList)
} else {
binding.continueListenTxt.hide()
binding.rvContinueListen.hide()
@@ -201,7 +239,7 @@ class AudioBooksActivity : WokaBaseActivity() {
audioBookData.content_more_details?.let { moreDetailsList ->
trailerName.text = if (moreDetailsList.isNotEmpty()) {
if (WokaApp.userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
moreDetailsList[1]?.title
} else {
moreDetailsList[0]?.title
@@ -238,18 +276,32 @@ class AudioBooksActivity : WokaBaseActivity() {
showAudioDialog(AudioBookData(audioBookData))
}
private fun onBookChanged(id: Int) {
private fun onBookChanged(id: Int, audioBookData: AudioBookData) {
// updating continue book list
val position = continueAudioAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < continueAudioAdapter.currentList.size) {
val currKaraoke = continueAudioAdapter.currentList[position]
// updating possible data changes
currKaraoke.is_liked = audioBookData.is_liked
currKaraoke.likes_count = audioBookData.likes_count
currKaraoke.mark_as_favourite = audioBookData.mark_as_favourite
continueAudioAdapter.notifyItemChanged(position)
}
}
private fun onContinueBookChanged(id: Int) {
private fun onContinueBookChanged(id: Int, audioBookData: AudioBookData) {
// updating book list
val position = audioBookAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < audioBookAdapter.currentList.size) {
val currKaraoke = audioBookAdapter.currentList[position]
// updating possible data changes
currKaraoke.is_liked = audioBookData.is_liked
currKaraoke.likes_count = audioBookData.likes_count
currKaraoke.mark_as_favourite = audioBookData.mark_as_favourite
audioBookAdapter.notifyItemChanged(position)
}
}
@@ -323,7 +375,7 @@ class AudioBooksActivity : WokaBaseActivity() {
if (moreDetailsList.isNotEmpty()) {
if (WokaApp.userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
moreDetailsList[1]?.let { data ->
title.text = data.title
description.text = Html.fromHtml(
@@ -364,9 +416,21 @@ class AudioBooksActivity : WokaBaseActivity() {
!like.isSelected
)
audioBookData.likes_count?.let {
audioBookData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
audioBookData.is_liked = !like.isSelected
audioBookData.id?.let {
onBookChanged(it)
onContinueBookChanged(it)
onBookChanged(it, audioBookData)
onContinueBookChanged(it, audioBookData)
}
like.isSelected = !like.isSelected
likeCount.text = "${audioBookData.likes_count}"
@@ -378,9 +442,11 @@ class AudioBooksActivity : WokaBaseActivity() {
!fav.isSelected
)
audioBookData.mark_as_favourite = !fav.isSelected
audioBookData.id?.let {
onBookChanged(it)
onContinueBookChanged(it)
onBookChanged(it, audioBookData)
onContinueBookChanged(it, audioBookData)
}
fav.isSelected = !fav.isSelected

View File

@@ -33,6 +33,7 @@ import com.woka.userPreference.UserType
import com.woka.utils.hide
import com.woka.utils.scaleAnimate
import com.woka.utils.show
import com.woka.webseries.views.SeriesActivity
import com.woka.webseries.views.WebSeriesActivity
import com.woka.wokagames.views.GamesActivity
@@ -179,7 +180,7 @@ class Home1Fragment : Fragment(), Listener {
webSeries.setOnClickListener {
activity?.let {
startActivity(Intent(it, WebSeriesActivity::class.java))
startActivity(Intent(it, SeriesActivity::class.java))
}
}

View File

@@ -464,6 +464,7 @@ class HomeActivity : WokaBaseActivity(),
.replace(R.id.fc_home, Home2Fragment.newInstance())
.runOnCommit {
binding.notifications.show()
binding.notiCountView.show()
window.lightStatusBar()
binding.root.backgroundTintList = ColorStateList.valueOf(getColor(R.color.color_primary))
@@ -477,6 +478,7 @@ class HomeActivity : WokaBaseActivity(),
.replace(R.id.fc_home, Home1Fragment.newInstance())
.runOnCommit {
binding.notifications.show()
binding.notiCountView.show()
window.lightStatusBar()
binding.root.backgroundTintList = null
@@ -508,6 +510,7 @@ class HomeActivity : WokaBaseActivity(),
window.lightStatusBar()
binding.notifications.hide()
binding.notiCountView.hide()
binding.root.background = ContextCompat.getDrawable(this, R.drawable.grad_my_list)
}
.commit()

View File

@@ -1,7 +1,5 @@
package com.woka.karaoke
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.woka.home.mylist.MyListRepository
import com.woka.home.mylist.models.FavKaraokeData
import com.woka.home.mylist.models.PostType
@@ -24,13 +22,6 @@ object KaraokeRepository {
private val apiService = RetrofitHelper.getRetrofit().create(KaraokeApiService::class.java)
private val userActionApiService = RetrofitHelper.getRetrofit().create(UserActionApiService::class.java)
// continue sing karaoke data
private val _continueKaraokeLiveData = MutableLiveData<ApiResult<ContinueKaraokeResponse>>()
val continueKaraokeLiveData: LiveData<ApiResult<ContinueKaraokeResponse>>
get() = _continueKaraokeLiveData
private var continueKaraokeData: ContinueKaraokeResponse? = null
suspend fun loadKaraokeSongs(pageNo: Int, quantity: Int): ApiResult<KaraokeResponse> {
return handleApiCall {
apiService.karaokeListing(
@@ -43,32 +34,13 @@ object KaraokeRepository {
}
}
fun loadContinueKaraoke(){
if (continueKaraokeData != null){
_continueKaraokeLiveData.postValue(ApiResult.Success(continueKaraokeData))
return
}
CoroutineScope(Dispatchers.IO).launch {
val response = handleApiCall {
userActionApiService.continueKaraokeListing(
FormBody.Builder()
.add("post_type", PostType.KARAOKE.value)
.build()
)
}
when (response){
is ApiResult.Error -> {}
is ApiResult.Loading -> {}
is ApiResult.Success -> {
response.data?.let {
continueKaraokeData = it
}
}
}
_continueKaraokeLiveData.postValue(response)
suspend fun loadContinueLiveData(): ApiResult<ContinueKaraokeResponse> {
return handleApiCall {
userActionApiService.continueKaraokeListing(
FormBody.Builder()
.add("post_type", PostType.KARAOKE.value)
.build()
)
}
}
@@ -97,28 +69,6 @@ object KaraokeRepository {
}
private fun changeLikeLocally(id: String, isLiked: Boolean){
// continue Karaoke data update
continueKaraokeData?.result?.let {
for (audio in it){
var found = false
audio?.let {data ->
if ("${data.id}" == id){
data.is_liked = isLiked
data.likes_count?.let { count ->
data.likes_count = if (isLiked) count + 1
else max(0, count - 1)
}
found = true
}
}
if (found) break
}
}
// changing in fav list locally
MyListRepository.myFavData.result?.sing_karaoke_data?.let {
for (audioData in it){
@@ -164,18 +114,5 @@ object KaraokeRepository {
}
}
continueKaraokeData?.result?.let {
for (audio in it){
if (audio?.id == karaoke.id){
audio?.mark_as_favourite = addToBookmark
break
}
}
}
}
fun clearData(){
continueKaraokeData = null
}
}

View File

@@ -17,11 +17,12 @@ import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
import kotlin.math.max
class ContinueKaraokeAdapter(
private val context: Context,
private var onKaraokeClicked: (KaraokeData) -> Unit,
private var onKaraokeChanged: (id: Int, isFromContinue: Boolean) -> Unit
private var onKaraokeChanged: (id: Int, isFromContinue: Boolean, KaraokeData) -> Unit
): ListAdapter<ContinueKaraokeData, ContinueKaraokeAdapter.AudioBookViewHolder>(ASYNC_DIFF_UTIL) {
inner class AudioBookViewHolder(val binding: FavViewHolderBinding): ViewHolder(binding.root)
@@ -97,6 +98,18 @@ class ContinueKaraokeAdapter(
!like.isSelected
)
karaokeData.likes_count?.let {
karaokeData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
karaokeData.is_liked = !like.isSelected
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
@@ -105,7 +118,7 @@ class ContinueKaraokeAdapter(
like.isSelected = it
}
karaokeData.id?.let{onKaraokeChanged(it, true)}
karaokeData.id?.let{onKaraokeChanged(it, true, KaraokeData(karaokeData))}
}
fav.isSelected = karaokeData.mark_as_favourite == true
@@ -121,9 +134,11 @@ class ContinueKaraokeAdapter(
!fav.isSelected
)
karaokeData.mark_as_favourite = !fav.isSelected
fav.isSelected = karaokeData.mark_as_favourite == true
karaokeData.id?.let{onKaraokeChanged(it, true)}
karaokeData.id?.let{onKaraokeChanged(it, true, KaraokeData(karaokeData))}
}
root.setOnClickListener {

View File

@@ -16,11 +16,12 @@ import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
import kotlin.math.max
class KaraokeAdapter(
private val context: Context,
private var onKaraokeClicked: (KaraokeData) -> Unit,
private var onKaraokeChanged: (id: Int, isContinue: Boolean) -> Unit
private var onKaraokeChanged: (id: Int, isContinue: Boolean, KaraokeData) -> Unit
): ListAdapter<KaraokeData, KaraokeAdapter.AudioBookViewHolder>(ASYNC_DIFF_UTIL) {
inner class AudioBookViewHolder(val binding: ShowViewHolderBinding): ViewHolder(binding.root)
@@ -96,6 +97,18 @@ class KaraokeAdapter(
!like.isSelected
)
karaokeData.likes_count?.let {
karaokeData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
karaokeData.is_liked = !like.isSelected
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
@@ -104,7 +117,7 @@ class KaraokeAdapter(
like.isSelected = it
}
karaokeData.id?.let{onKaraokeChanged(it, false)}
karaokeData.id?.let{onKaraokeChanged(it, false, karaokeData)}
}
fav.isSelected = karaokeData.mark_as_favourite == true
@@ -120,9 +133,11 @@ class KaraokeAdapter(
!fav.isSelected
)
karaokeData.mark_as_favourite = !fav.isSelected
fav.isSelected = karaokeData.mark_as_favourite == true
karaokeData.id?.let{onKaraokeChanged(it, false)}
karaokeData.id?.let{onKaraokeChanged(it, false, karaokeData)}
}
root.setOnClickListener {

View File

@@ -5,28 +5,73 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.continuesing.ContinueKaraokeData
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.networking.ApiResult
import kotlinx.coroutines.launch
class KaraokeViewModel: ViewModel() {
class KaraokeViewModel : ViewModel() {
private val _karaokeLiveData = MutableLiveData<ApiResult<List<KaraokeData>>>()
val karaokeLiveData: LiveData<ApiResult<List<KaraokeData>>>
private val _karaokeLiveData = MutableLiveData<ApiResult<MutableList<KaraokeData>>>()
val karaokeLiveData: LiveData<ApiResult<MutableList<KaraokeData>>>
get() = _karaokeLiveData
var nextPageToLoad: Int = 0
private var quantityPerPage: Int = 10
private var lastPage = false
private val karaokeList = ArrayList<KaraokeData>()
fun loadKaraokeSongs(currentItemCount: Int){
private var nextPageToLoad: Int = 0
private var quantityPerPage: Int = 6
var lastPage = false
private val _karaokeContinueLiveData = MutableLiveData<ApiResult<List<ContinueKaraokeData>>>()
val karaokeContinueLiveData: LiveData<ApiResult<List<ContinueKaraokeData>>>
get() = _karaokeContinueLiveData
fun loadKaraokeSongs() {
viewModelScope.launch {
when (val value = KaraokeRepository.loadKaraokeSongs(nextPageToLoad, quantityPerPage)){
is ApiResult.Error -> _karaokeLiveData.postValue(ApiResult.Error(value.errorMessage, value.error))
_karaokeLiveData.postValue(ApiResult.Loading())
when (val value = KaraokeRepository.loadKaraokeSongs(nextPageToLoad, quantityPerPage)) {
is ApiResult.Error -> _karaokeLiveData.postValue(
ApiResult.Error(
value.errorMessage,
value.error
)
)
is ApiResult.Loading -> _karaokeLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
value.data?.karaoke_data?.filterNotNull()?.let {
value.data?.let {
it.karaoke_data?.filterNotNull()?.let { newList ->
karaokeList.addAll(newList)
lastPage = karaokeList.size == it.total_records
_karaokeLiveData.postValue(ApiResult.Success(karaokeList))
nextPageToLoad++
}
}
}
}
}
}
fun loadContinueData(){
viewModelScope.launch {
_karaokeContinueLiveData.postValue(ApiResult.Loading())
when (val value = KaraokeRepository.loadContinueLiveData()) {
is ApiResult.Error -> _karaokeContinueLiveData.postValue(
ApiResult.Error(
value.errorMessage,
value.error
)
)
is ApiResult.Loading -> _karaokeContinueLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
value.data?.let {
it.result?.filterNotNull()?.let { newList ->
_karaokeContinueLiveData.postValue(ApiResult.Success(newList))
}
}
}
}

View File

@@ -16,7 +16,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.woka.R
import com.woka.WokaApp
import com.woka.WokaApp.Companion.userPrefs
import com.woka.databinding.ActivityKaraokeBinding
import com.woka.databinding.DialogModuleShowerBinding
@@ -37,6 +36,7 @@ import com.woka.utils.show
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import kotlin.math.max
class KaraokeActivity : WokaBaseActivity() {
@@ -83,6 +83,10 @@ class KaraokeActivity : WokaBaseActivity() {
clickEvents()
setObservers()
if (!viewModel.karaokeLiveData.isInitialized){
viewModel.loadKaraokeSongs()
}
}
private fun initViews() {
@@ -132,11 +136,11 @@ class KaraokeActivity : WokaBaseActivity() {
}
retryBtn.setOnClickListener {
viewModel.loadKaraokeSongs()
}
loadMoreBtn.setOnClickListener {
viewModel.loadKaraokeSongs()
}
}
}
@@ -152,7 +156,7 @@ class KaraokeActivity : WokaBaseActivity() {
karaokeData.content_more_details?.let { moreDetailsList ->
trailerName.text = if (moreDetailsList.isNotEmpty()) {
if (WokaApp.userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
moreDetailsList[1]?.title
} else {
moreDetailsList[0]?.title
@@ -214,7 +218,7 @@ class KaraokeActivity : WokaBaseActivity() {
if (moreDetailsList.isNotEmpty()) {
if (WokaApp.userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
moreDetailsList[1]?.let { data ->
title.text = data.title
description.text = Html.fromHtml(
@@ -255,9 +259,21 @@ class KaraokeActivity : WokaBaseActivity() {
!like.isSelected
)
karaokeData.likes_count?.let {
karaokeData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
karaokeData.is_liked = !like.isSelected
karaokeData.id?.let {
onKaraokeChanged(it, false)
onKaraokeChanged(it, true)
onKaraokeChanged(it, false, karaokeData)
onKaraokeChanged(it, true, karaokeData)
}
like.isSelected = !like.isSelected
likeCount.text = "${karaokeData.likes_count}"
@@ -269,9 +285,11 @@ class KaraokeActivity : WokaBaseActivity() {
!fav.isSelected
)
karaokeData.mark_as_favourite = !fav.isSelected
karaokeData.id?.let {
onKaraokeChanged(it, false)
onKaraokeChanged(it, true)
onKaraokeChanged(it, false, karaokeData)
onKaraokeChanged(it, true, karaokeData)
}
fav.isSelected = !fav.isSelected
@@ -286,17 +304,31 @@ class KaraokeActivity : WokaBaseActivity() {
}
}
private fun onKaraokeChanged(id: Int, isFromContinue: Boolean) {
private fun onKaraokeChanged(id: Int, isFromContinue: Boolean, karaokeData: KaraokeData) {
if (isFromContinue) {
// updating karaoke list
val position = karaokeAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < karaokeAdapter.currentList.size) {
val currKaraoke = karaokeAdapter.currentList[position]
// updating possible data changes
currKaraoke.is_liked = karaokeData.is_liked
currKaraoke.likes_count = karaokeData.likes_count
currKaraoke.mark_as_favourite = karaokeData.mark_as_favourite
karaokeAdapter.notifyItemChanged(position)
}
} else {
// updating continue karaoke list
val continuePos = continueKaraokeAdapter.currentList.indexOfFirst { it.id == id }
if (continuePos >= 0 && continuePos < continueKaraokeAdapter.currentList.size) {
val currKaraoke = continueKaraokeAdapter.currentList[continuePos]
// updating possible data changes
currKaraoke.is_liked = karaokeData.is_liked
currKaraoke.likes_count = karaokeData.likes_count
currKaraoke.mark_as_favourite = karaokeData.mark_as_favourite
continueKaraokeAdapter.notifyItemChanged(continuePos)
}
}
@@ -308,67 +340,71 @@ class KaraokeActivity : WokaBaseActivity() {
}
private fun setObservers() {
// viewModel.karaokeLiveData.observe(this) {
// when (it) {
// is ApiResult.Error -> {
// binding.shimmer.hide()
// if (karaokeAdapter.currentList.size == 0) {
// binding.rvKaraoke.hide()
// binding.singTxt.hide()
// binding.trailerView.hide()
//
// binding.errorView.show()
// } else {
// // error in loading more
// binding.loadMoreProgress.hide()
// binding.loadMoreBtn.show()
// }
// }
//
// is ApiResult.Loading -> {
// if (karaokeAdapter.currentList.size == 0) {
// binding.shimmer.show()
//
// binding.rvKaraoke.hide()
// binding.singTxt.hide()
// binding.trailerView.hide()
// binding.errorView.hide()
// } else {
// // error in loading more
// binding.loadMoreProgress.show()
// binding.loadMoreBtn.hide()
// }
// }
//
// is ApiResult.Success -> {
// it.data?.let { data ->
// binding.loadMoreProgress.hide()
//
// data.karaoke_data?.filterNotNull()?.let { karaokeData ->
// if (karaokeData.isNotEmpty()) {
// binding.shimmer.hide()
// binding.errorView.hide()
//
// if (userPrefs?.userType != UserType.GUEST) {
// KaraokeRepository.loadContinueKaraoke()
// }
//
// loadTrailerData(karaokeData[0])
// binding.rvKaraoke.show()
// binding.singTxt.show()
//
// karaokeAdapter.submitList(karaokeData)
// }
//
// }
//
// binding.loadMoreBtn.setVisibility(karaokeAdapter.currentList.size != data.total_records)
// }
// }
// }
// }
viewModel.karaokeLiveData.observe(this){
when(it){
is ApiResult.Error -> {
binding.shimmer.hide()
if (karaokeAdapter.currentList.size == 0) {
// none of the data is yet loaded
binding.rvKaraoke.hide()
binding.singTxt.hide()
binding.trailerView.hide()
KaraokeRepository.continueKaraokeLiveData.observe(this) {
binding.errorView.show()
} else {
// error in loading more
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.retry)
binding.loadMoreBtn.show()
}
}
is ApiResult.Loading -> {
if (karaokeAdapter.currentList.size == 0) {
// loading first data
binding.shimmer.show()
binding.errorView.hide()
} else {
// loading more data
binding.loadMoreProgress.show()
binding.loadMoreBtn.hide()
}
}
is ApiResult.Success -> {
it.data?.let {newList ->
if (newList.isNotEmpty()){
binding.rvKaraoke.show()
binding.singTxt.show()
binding.trailerView.show()
binding.errorView.hide()
binding.shimmer.hide()
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.load_more)
binding.loadMoreBtn.setVisibility(!viewModel.lastPage)
if (karaokeAdapter.currentList.isEmpty()){
// first data load
if (userPrefs?.userType != UserType.GUEST && !viewModel.karaokeContinueLiveData.isInitialized){
viewModel.loadContinueData()
}
loadTrailerData(newList[0])
karaokeAdapter.submitList(newList)
}else{
// loaded more data
karaokeAdapter.notifyItemRangeInserted(
karaokeAdapter.currentList.size,
newList.size
)
}
}
}
}
}
}
viewModel.karaokeContinueLiveData.observe(this) {
when (it) {
is ApiResult.Error -> {
binding.continueSingTxt.hide()
@@ -377,12 +413,12 @@ class KaraokeActivity : WokaBaseActivity() {
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.filterNotNull()?.let { continueData ->
if (continueData.isNotEmpty()) {
it.data?.let { continueList ->
if (continueList.isNotEmpty()) {
binding.continueSingTxt.show()
binding.rvContinueSing.show()
continueKaraokeAdapter.submitList(continueData)
continueKaraokeAdapter.submitList(continueList)
} else {
binding.continueSingTxt.hide()
binding.rvContinueSing.hide()

View File

@@ -117,7 +117,5 @@ class UserPreference(val context: Context) {
private fun clearData(){
WebSeriesRepository.clearData()
MyListRepository.clearData()
AudioBookRepository.clearData()
KaraokeRepository.clearData()
}
}

View File

@@ -0,0 +1,189 @@
package com.woka.webseries
import com.woka.home.mylist.MyListRepository
import com.woka.home.mylist.models.BookmarkedShowData
import com.woka.home.mylist.models.PostType
import com.woka.modules.categorymodels.CategoriesResponse
import com.woka.networking.ApiResult
import com.woka.networking.RetrofitHelper
import com.woka.networking.RetrofitHelper.handleApiCall
import com.woka.userdata.UserActionApiService
import com.woka.webseries.models.ContinueEpisodeResponse
import com.woka.webseries.models.ShowData
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.FormBody
import kotlin.math.max
object Repository {
// api services
private val apiService = RetrofitHelper.getRetrofit().create(WebSeriesApiService::class.java)
private val userActionApiService = RetrofitHelper.getRetrofit().create(UserActionApiService::class.java)
suspend fun loadCategories(): ApiResult<CategoriesResponse> {
return handleApiCall {
apiService.categoryListing(
FormBody.Builder()
.add("module_id", "7")
.build()
)
}
}
suspend fun loadTeaserData(showId: Int, seasonId: Int): ApiResult<TeaserResponseData> {
return handleApiCall{
apiService.teaserListing(
FormBody.Builder()
.add("watch_show_master_id", "$showId")
.add("season_master_id", "$seasonId")
.build()
)
}
}
suspend fun loadEpisodeData(showId: Int, seasonId: Int): ApiResult<EpisodeResponseData> {
return handleApiCall {
apiService.episodeListing(
FormBody.Builder()
.add("watch_show_master_id", "$showId")
.add("season_master_id", "$seasonId")
.build()
)
}
}
suspend fun loadSeasonListing(showId: Int, categoryId: String): ApiResult<SeasonDataResponse> {
return handleApiCall {
apiService.seasonListing(
FormBody.Builder()
.add("watch_show_id", "$showId")
.add("category_id", categoryId)
.build()
)
}
}
suspend fun loadContinueWatchData(): ApiResult<ContinueEpisodeResponse> {
return handleApiCall {
userActionApiService.continueWatchingShowListing(
FormBody.Builder()
.add("post_type", "3")
.build()
)
}
}
suspend fun loadWebSeries(categoryId: String, pageNo: Int, quantity: Int): ApiResult<WebSeriesResponse> {
return handleApiCall {
apiService.getWebSeries(
FormBody.Builder()
.add("category_id", categoryId)
.add("api_version", "v2")
.add("start", "$pageNo")
.add("limit", "$quantity")
.build()
)
}
}
fun likeUnLikeShow(postId: String, likeIt: Boolean){
CoroutineScope(Dispatchers.IO).launch {
handleApiCall {
if (likeIt){
userActionApiService.likePost(
FormBody.Builder()
.add("post_id", postId)
.add("post_type", PostType.WEB_SERIES.value)
.build()
)
}else{
userActionApiService.unLikePost(
FormBody.Builder()
.add("post_id", postId)
.add("post_type", PostType.WEB_SERIES.value)
.build()
)
}
}
}
changeLikeLocally(postId, likeIt)
}
private fun changeLikeLocally(id: String, isLiked: Boolean){
// changing in fav list locally
MyListRepository.myFavData.result?.show_data?.let {
for (show in it){
if ("${show.id}" == id){
show.is_liked = isLiked
show.likes_count?.let { count ->
show.likes_count = if (isLiked) count + 1
else max(0, count - 1)
}
break
}
}
}
}
fun updateFavShow(showData: ShowData, addToBookmark: Boolean, categoryId: String){
CoroutineScope(Dispatchers.IO).launch {
handleApiCall {
if (addToBookmark){
userActionApiService.addToFav(
FormBody.Builder()
.add("post_id", "${showData.id}")
.add("post_type", PostType.WEB_SERIES.value)
.add("category_id", categoryId)
.build()
)
}else{
userActionApiService.removeFromFav(
FormBody.Builder()
.add("id", "${showData.id}")
.add("post_type", PostType.WEB_SERIES.value)
.add("category_id", categoryId)
.build()
)
}
}
}
MyListRepository.myFavData.result?.show_data?.let {
var found = false
var showFound: BookmarkedShowData? = null
for (show in it){
if (showData.id == show.id){
if (addToBookmark){
show.addAsBookMark(categoryId)
}else{
show.removeAsBookMark(categoryId)
}
showFound = show
found = true
break
}
}
if (!found && addToBookmark){
MyListRepository.myFavData.result?.show_data?.add(BookmarkedShowData(showData, categoryId))
}
if (found){
showFound?.let {bookMarkShowData ->
if (!bookMarkShowData.isBookMarked()){
it.remove(bookMarkShowData)
}
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
package com.woka.webseries
import com.woka.modules.categorymodels.CategoriesResponse
import com.woka.networking.ApiResponse
import com.woka.webseries.models.WebSeriesResponse
import com.woka.webseries.models.episodedata.EpisodeResponseData
@@ -23,4 +24,7 @@ interface WebSeriesApiService {
@POST("teaser_listing")
suspend fun teaserListing(@Body formBody: FormBody): Response<ApiResponse<TeaserResponseData>>
@POST("category_listing")
suspend fun categoryListing(@Body body: FormBody): Response<ApiResponse<CategoriesResponse>>
}

View File

@@ -12,7 +12,7 @@ import com.woka.databinding.ShowViewHolderBinding
import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import com.woka.webseries.WebSeriesRepository
import com.woka.webseries.Repository
import com.woka.webseries.models.ShowData
import kotlin.math.max
@@ -84,12 +84,20 @@ class WebSeriesShowAdapter(
return@setOnClickListener
}
if (like.isSelected){
WebSeriesRepository.likeUnLikeShow("${showData.id}", false)
}else{
WebSeriesRepository.likeUnLikeShow("${showData.id}", true)
Repository.likeUnLikeShow("${showData.id}", !like.isSelected)
showData.likes_count?.let {
showData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
showData.is_liked = !like.isSelected
like.isSelected = !like.isSelected
showData.likes_count?.let {
likeCount.text = "$it"
@@ -105,12 +113,14 @@ class WebSeriesShowAdapter(
}
categoryId?.let {
WebSeriesRepository.updateFavShow(
Repository.updateFavShow(
showData,
!fav.isSelected,
it
)
showData.addAsBookMark(it)
fav.isSelected = !fav.isSelected
}
}

View File

@@ -0,0 +1,111 @@
package com.woka.webseries.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woka.modules.categorymodels.CategoriesResponse
import com.woka.networking.ApiResult
import com.woka.utils.TAG
import com.woka.webseries.Repository
import com.woka.webseries.models.ContinueEpisodeResponse
import com.woka.webseries.models.ShowData
import kotlinx.coroutines.launch
class ViewModel : ViewModel() {
private val repository = Repository
// categories listing
data class PagingData(
var nextPageToLoad: Int = 0, var quantityPerPage: Int = 6,
var lastPage: Boolean = false
)
private val _showCategoryLiveData = MutableLiveData<ApiResult<CategoriesResponse>>()
val showCategoryLiveData: LiveData<ApiResult<CategoriesResponse>>
get() = _showCategoryLiveData
var categoryPagingData = HashMap<String, PagingData>()
fun loadCategories() {
viewModelScope.launch {
_showCategoryLiveData.postValue(ApiResult.Loading())
_showCategoryLiveData.postValue(repository.loadCategories())
}
}
// continue watching
private val _continueWatchLiveData = MutableLiveData<ApiResult<ContinueEpisodeResponse>>()
val continueWatchLiveData: LiveData<ApiResult<ContinueEpisodeResponse>>
get() = _continueWatchLiveData
fun loadContinueWatching() {
viewModelScope.launch {
_continueWatchLiveData.postValue(ApiResult.Loading())
_continueWatchLiveData.postValue(repository.loadContinueWatchData())
}
}
// show data
private val _webSeriesLiveData =
MutableLiveData<ApiResult<MutableList<ShowData>>>()
val webSeriesLiveData: LiveData<ApiResult<MutableList<ShowData>>>
get() = _webSeriesLiveData
// map of series data where key is category
private var webSeriesData = HashMap<String, MutableList<ShowData>>()
fun loadWebSeries(categoryId: String){
if (webSeriesData.containsKey(categoryId) && webSeriesData[categoryId]?.isNotEmpty() == true){
_webSeriesLiveData.postValue(ApiResult.Success(webSeriesData[categoryId]))
}else{
loadMoreWebSeries(categoryId)
}
}
fun loadMoreWebSeries(categoryId: String) {
viewModelScope.launch {
_webSeriesLiveData.postValue(ApiResult.Loading())
val pagingData = if (categoryPagingData.containsKey(categoryId)){
categoryPagingData[categoryId]!!
}else{
val pagingData = PagingData()
categoryPagingData[categoryId] = pagingData
pagingData
}
when (val response = repository.loadWebSeries(categoryId, pagingData.nextPageToLoad, pagingData.quantityPerPage)) {
is ApiResult.Error -> {
_webSeriesLiveData.postValue(
ApiResult.Error(
response.errorMessage,
response.error
)
)
}
is ApiResult.Loading -> {
_webSeriesLiveData.postValue(ApiResult.Loading())
}
is ApiResult.Success -> {
response.data?.let { data ->
data.show_data?.filterNotNull()?.let { newList ->
val currentList = webSeriesData.getOrDefault(categoryId, ArrayList())
currentList.addAll(newList)
webSeriesData[categoryId] = currentList
pagingData.lastPage = webSeriesData[categoryId]?.size == data.total_records
_webSeriesLiveData.postValue(ApiResult.Success(webSeriesData[categoryId]))
pagingData.nextPageToLoad++
}
}
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
package com.woka.webseries.views
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.woka.R
import com.woka.databinding.ActivitySeriesBinding
import com.woka.utils.WokaBaseActivity
import com.woka.webseries.views.fragments.WebSeriesFragment
class SeriesActivity : WokaBaseActivity() {
private lateinit var binding: ActivitySeriesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivitySeriesBinding.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
}
window.navigationBarColor = getColor(R.color.color_primary_dark)
supportFragmentManager.beginTransaction()
.add(R.id.fcv_web_series, WebSeriesFragment.newInstance())
.runOnCommit {
window.setBackgroundDrawable(AppCompatResources.getDrawable(this, R.drawable.gradient_web_series))
}
.commit()
}
}

View File

@@ -0,0 +1,402 @@
package com.woka.webseries.views.fragments
import android.app.Dialog
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.InsetDrawable
import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.AdapterView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.jwplayer.pub.api.media.playlists.PlaylistItem
import com.woka.R
import com.woka.WokaApp.Companion.userPrefs
import com.woka.databinding.DialogContinueEpisodeBinding
import com.woka.databinding.FragmentWebSeriesBinding
import com.woka.networking.ApiResult
import com.woka.players.models.VideoPlayList
import com.woka.players.models.VideoViewRequestData
import com.woka.players.views.PlayerActivity
import com.woka.players.views.PlayerActivity.Companion.EXTRA_PLAY_INDEX
import com.woka.players.views.PlayerActivity.Companion.EXTRA_PLAY_LIST
import com.woka.userPreference.UserType
import com.woka.utils.hide
import com.woka.utils.setVisibility
import com.woka.utils.show
import com.woka.webseries.adapters.ContinueEpisodeAdapter
import com.woka.webseries.adapters.SpinnerAdapter
import com.woka.webseries.adapters.WebSeriesShowAdapter
import com.woka.webseries.models.ContinueEpisodeData
import com.woka.webseries.models.ShowData
import com.woka.webseries.viewmodel.ViewModel
import java.util.Collections
class WebSeriesFragment private constructor(): Fragment() {
private lateinit var binding: FragmentWebSeriesBinding
private lateinit var viewModel: ViewModel
private lateinit var showAdapter: WebSeriesShowAdapter
private lateinit var continueWatchAdapter: ContinueEpisodeAdapter
private var catSpinnerAdapter: SpinnerAdapter? = null
private lateinit var episodeDialogBinding: DialogContinueEpisodeBinding
private lateinit var episodeDialog: Dialog
companion object {
fun newInstance() = WebSeriesFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentWebSeriesBinding.inflate(inflater, container, false)
viewModel = ViewModelProvider(this)[ViewModel::class.java]
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
initEpisodeDialog()
clickEvents()
setObservers()
if (!viewModel.showCategoryLiveData.isInitialized){
viewModel.loadCategories()
}
}
private fun clickEvents() {
binding.apply {
toolbar.backBtn.setOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed()
}
retryBtn.setOnClickListener {
binding.shimmer.show()
binding.errorView.hide()
viewModel.loadCategories()
}
loadMoreBtn.setOnClickListener {
catSpinnerAdapter?.selectedCategoryType?.let {
viewModel.loadMoreWebSeries(it)
}
}
}
}
private fun initViews(){
// adapters
activity?.let {
showAdapter = WebSeriesShowAdapter(it, ::onShowClicked)
continueWatchAdapter = ContinueEpisodeAdapter(it)
}
binding.apply {
adjustMasilaImage()
toolbar.title.text = getString(R.string.web_series)
rvWebSeries.adapter = showAdapter
rvContinueWatch.adapter = continueWatchAdapter
continueWatchAdapter.onEpisodeClicked = ::onContinueEpisodeClicked
((rvWebSeries.itemAnimator) as SimpleItemAnimator).supportsChangeAnimations = false
((rvContinueWatch.itemAnimator) as SimpleItemAnimator).supportsChangeAnimations = false
}
}
private fun adjustMasilaImage() {
// making space for masila image
binding.apply {
topPinnedView.post {
val imgParams = masilaImage.layoutParams as CollapsingToolbarLayout.LayoutParams
imgParams.setMargins(0, 0, 0, topPinnedView.height)
masilaImage.layoutParams = imgParams
}
}
}
private fun initEpisodeDialog(){
episodeDialogBinding = DialogContinueEpisodeBinding.inflate(layoutInflater)
episodeDialog = Dialog(requireContext())
episodeDialog.setContentView(episodeDialogBinding.root)
try {
val back = ColorDrawable(Color.TRANSPARENT)
val inset = InsetDrawable(back, 50)
episodeDialog.window!!.setBackgroundDrawable(inset)
} catch (e: Exception) {
// do nothing
}
try {
val layoutParams = episodeDialog.window!!.attributes
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
layoutParams.horizontalMargin = 0F
episodeDialog.window!!.setAttributes(layoutParams)
} catch (e: Exception) {
// do nothing
}
episodeDialogBinding.close.setOnClickListener { episodeDialog.dismiss() }
}
private fun loadShowData(){
catSpinnerAdapter?.selectedCategoryType?.let {
showAdapter.submitListShowList(Collections.emptyList(), it)
viewModel.loadWebSeries(it)
}
}
private fun setObservers(){
viewModel.showCategoryLiveData.observe(viewLifecycleOwner){
when(it){
is ApiResult.Error -> {
binding.errorView.show()
binding.shimmer.hide()
}
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.let {catList ->
binding.shimmer.hide()
binding.trailerView.show()
adjustMasilaImage()
binding.spinnerCard.show()
binding.selectLangTxt.show()
activity?.let {activity ->
catSpinnerAdapter = SpinnerAdapter(activity, catList.filterNotNull())
binding.categorySpinner.setAdapter(catSpinnerAdapter)
loadShowData()
viewModel.loadContinueWatching()
}
}
}
}
}
viewModel.continueWatchLiveData.observe(viewLifecycleOwner){
when (it){
is ApiResult.Error -> {
binding.rvContinueWatch.hide()
binding.continueWatchTxt.hide()
}
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.let {continueWatchList ->
if (continueWatchList.isNotEmpty()){
binding.rvContinueWatch.show()
binding.continueWatchTxt.show()
continueWatchAdapter.submitList(continueWatchList.asReversed())
}else{
binding.rvContinueWatch.hide()
binding.continueWatchTxt.hide()
}
}
}
}
}
viewModel.webSeriesLiveData.observe(viewLifecycleOwner){
when (it){
is ApiResult.Error -> {
binding.shimmerShowData.hide()
if (showAdapter.showList.isNotEmpty()){
// load more
binding.loadMoreBtn.text = getString(R.string.retry)
binding.loadMoreBtn.show()
}else{
// first time load
binding.rvWebSeries.hide()
binding.shimmerShowData.hide()
binding.loadMoreBtn.hide()
}
}
is ApiResult.Loading -> {
binding.shimmerShowData.show()
binding.loadMoreBtn.hide()
binding.rvWebSeries.setVisibility(showAdapter.showList.isNotEmpty())
}
is ApiResult.Success -> {
catSpinnerAdapter?.selectedCategoryType?.let { categoryType ->
it.data?.let {showList ->
binding.rvWebSeries.show()
binding.shimmerShowData.hide()
binding.loadMoreBtn.text = getString(R.string.load_more)
binding.loadMoreBtn.setVisibility(viewModel.categoryPagingData[categoryType]?.lastPage == false)
if (showAdapter.showList.isNotEmpty()){
// loaded more data
showAdapter.notifyItemRangeInserted(
showAdapter.showList.size,
showList.size
)
}else{
// new category data load
showAdapter.submitListShowList(showList, categoryType)
}
if (userPrefs?.userType != UserType.GUEST && !viewModel.continueWatchLiveData.isInitialized){
viewModel.loadContinueWatching()
}
}
}
}
}
}
binding.categorySpinner.onItemSelectedListener = object :
AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (position != catSpinnerAdapter?.currentSelection){
catSpinnerAdapter?.let {
it.selectPosition(position)
loadShowData()
}
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
private fun onShowClicked(showData: ShowData, position: Int, categoryId: String){
}
private fun onContinueEpisodeClicked(episodeData: ContinueEpisodeData){
episodeDialogBinding.apply {
episodeData.content_more_details?.let {moreDetailsList ->
episodeData.thumbnail_path?.let {
image.loadImage(it)
}
if (moreDetailsList.isNotEmpty()){
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){
moreDetailsList[1].let {data ->
title.text = data.title
description.text = Html.fromHtml(
data.description?.replace(
"<br>",
" "
), Html.FROM_HTML_MODE_LEGACY
)
}
}else{
moreDetailsList[0].let {data ->
title.text = data.title
description.text = Html.fromHtml(
data.description?.replace(
"<br>",
" "
), Html.FROM_HTML_MODE_LEGACY
)
}
}
}else{
title.text = episodeData.episode_title
description.text = Html.fromHtml(
episodeData.episode_description?.replace(
"<br>",
" "
), Html.FROM_HTML_MODE_LEGACY
)
}
watchCard.setOnClickListener {
episodeData.content_more_details.let {moreDetailsList ->
val videoPlayList = VideoPlayList(ArrayList(), ArrayList())
if (moreDetailsList.isNotEmpty()){
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){
videoPlayList.playlist.add(
PlaylistItem.Builder()
.file(moreDetailsList[1].content_hd_url)
.title(moreDetailsList[1].title)
.image(moreDetailsList[1].content_hd_url)
.build()
)
videoPlayList.videoViewRequestDataList?.add(
VideoViewRequestData(
"${episodeData.id}",
"3",
null,
category_id = "18"
)
)
}else{
videoPlayList.playlist.add(
PlaylistItem.Builder()
.file(moreDetailsList[0].content_hd_url)
.title(moreDetailsList[0].title)
.image(moreDetailsList[0].content_hd_url)
.build()
)
videoPlayList.videoViewRequestDataList?.add(
VideoViewRequestData(
"${episodeData.id}",
"3",
null,
"1"
)
)
}
}
activity?.let {
startActivity(Intent(it, PlayerActivity::class.java)
.apply {
putExtra(EXTRA_PLAY_LIST, videoPlayList)
putExtra(EXTRA_PLAY_INDEX, 0)
})
}
}
}
close.setOnClickListener {
episodeDialog.dismiss()
}
episodeDialog.show()
}
}
}
}

View File

@@ -1,11 +1,6 @@
package com.woka.wokagames
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.woka.audiobooks.AudioBookRepository
import com.woka.audiobooks.models.audiodata.AudioBookData
import com.woka.home.mylist.MyListRepository
import com.woka.home.mylist.models.FavAudioBookData
import com.woka.home.mylist.models.FavGameData
import com.woka.home.mylist.models.PostType
import com.woka.networking.ApiResult
@@ -25,43 +20,15 @@ object GamesRepository {
private val apiService = RetrofitHelper.getRetrofit().create(WokaGamesApiService::class.java)
private val userActionApiService = RetrofitHelper.getRetrofit().create(UserActionApiService::class.java)
// live data
// audio books data (loose caching)
private val _gamesLiveData = MutableLiveData<ApiResult<GamesResponse>>()
val gamesLiveData: LiveData<ApiResult<GamesResponse>>
get() = _gamesLiveData
private var gamesData: GamesResponse? = null
fun loadGames(){
if (gamesData != null){
_gamesLiveData.postValue(ApiResult.Success(gamesData))
return
}
CoroutineScope(Dispatchers.IO).launch{
_gamesLiveData.postValue(ApiResult.Loading())
val response = RetrofitHelper.handleApiCall {
apiService.gamesListing(
FormBody.Builder()
.build()
)
}
when (response){
is ApiResult.Error -> _gamesLiveData.postValue(ApiResult.Error(response.errorMessage, response.error))
is ApiResult.Loading -> _gamesLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
response.data?.let {
gamesData = it
_gamesLiveData.postValue(ApiResult.Success(it))
}?:{
_gamesLiveData.postValue(ApiResult.Error())
}
}
}
suspend fun loadGames(pageNo: Int, quantity: Int): ApiResult<GamesResponse> {
return handleApiCall {
apiService.gamesListing(
FormBody.Builder()
.add("api_version", "v2")
.add("start", "$pageNo")
.add("limit", "$quantity")
.build()
)
}
}
@@ -90,27 +57,6 @@ object GamesRepository {
}
private fun changeLikeLocally(id: String, isLiked: Boolean){
// changing in games locally
gamesData?.game_data?.let {audioBooks ->
for (audio in audioBooks){
var found = false
audio?.let {data ->
if ("${data.id}" == id){
data.is_liked = isLiked
data.likes_count?.let { count ->
data.likes_count = if (isLiked) count + 1
else max(0, count - 1)
}
found = true
}
}
if (found) break
}
}
// changing in fav list locally
MyListRepository.myFavData.result?.game_data?.let {
for (audioData in it){
@@ -155,15 +101,5 @@ object GamesRepository {
favAudioData.removeIf{it.id == gameData.id}
}
}
gamesData?.game_data?.let {
for (game in it){
if (game?.id == gameData.id){
game?.mark_as_favourite = addToBookmark
break
}
}
}
}
}

View File

@@ -16,6 +16,7 @@ import com.woka.utils.toast
import com.woka.wokagames.GamesRepository
import com.woka.wokagames.models.listing.GameData
import java.util.concurrent.Executors
import kotlin.math.max
class GamesAdapter(var context: Context,
private var onGameClicked: (GameData) -> Unit): ListAdapter<GameData, GamesAdapter.GameViewHolder>(
@@ -93,6 +94,18 @@ class GamesAdapter(var context: Context,
!like.isSelected
)
gameData.likes_count?.let {
gameData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
gameData.is_liked = !like.isSelected
like.isSelected = !like.isSelected
likeCount.text = "${gameData.likes_count}"
}
@@ -110,6 +123,8 @@ class GamesAdapter(var context: Context,
!fav.isSelected
)
gameData.mark_as_favourite = !fav.isSelected
fav.isSelected = gameData.mark_as_favourite == true
}

View File

@@ -0,0 +1,53 @@
package com.woka.wokagames.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woka.networking.ApiResult
import com.woka.wokagames.GamesRepository
import com.woka.wokagames.models.listing.GameData
import kotlinx.coroutines.launch
class GamesViewModel: ViewModel() {
private val _gamesLiveData = MutableLiveData<ApiResult<MutableList<GameData>>>()
val gamesLiveData: LiveData<ApiResult<MutableList<GameData>>>
get() = _gamesLiveData
private val gamesList = ArrayList<GameData>()
private var nextPageToLoad: Int = 0
private var quantityPerPage: Int = 6
var lastPage = false
fun loadGames() {
viewModelScope.launch {
_gamesLiveData.postValue(ApiResult.Loading())
when (val value = GamesRepository.loadGames(nextPageToLoad, quantityPerPage)) {
is ApiResult.Error -> _gamesLiveData.postValue(
ApiResult.Error(
value.errorMessage,
value.error
)
)
is ApiResult.Loading -> _gamesLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
value.data?.let {
it.game_data?.filterNotNull()?.let { newList ->
gamesList.addAll(newList)
lastPage = gamesList.size == it.total_records
_gamesLiveData.postValue(ApiResult.Success(gamesList))
nextPageToLoad++
}
}
}
}
}
}
}

View File

@@ -12,6 +12,7 @@ import android.view.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.woka.R
@@ -21,6 +22,7 @@ import com.woka.databinding.DialogModuleShowerBinding
import com.woka.networking.ApiResult
import com.woka.utils.WokaBaseActivity
import com.woka.utils.hide
import com.woka.utils.setVisibility
import com.woka.utils.show
import com.woka.wokagames.GamesRepository
import com.woka.wokagames.adapters.GamesAdapter
@@ -28,13 +30,16 @@ import com.woka.wokagames.models.listing.GameData
import com.woka.wokagames.playerr.GamePlayerActivity
import com.woka.wokagames.playerr.GamePlayerActivity.Companion.EXTRA_GAME_PLAYER_DATA
import com.woka.wokagames.playerr.GamePlayerData
import com.woka.wokagames.viewmodels.GamesViewModel
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import kotlin.math.max
class GamesActivity : WokaBaseActivity() {
private lateinit var binding: ActivityGamesBinding
private lateinit var viewModel: GamesViewModel
private lateinit var gameAdapter: GamesAdapter
@@ -56,6 +61,8 @@ class GamesActivity : WokaBaseActivity() {
navigationBarColor = getColor(R.color.color_primary_dark)
}
viewModel = ViewModelProvider(this)[GamesViewModel::class.java]
gameAdapter = GamesAdapter(this, ::onGameClicked)
dialogBinding = DialogModuleShowerBinding.inflate(layoutInflater)
@@ -70,7 +77,9 @@ class GamesActivity : WokaBaseActivity() {
setObservers()
GamesRepository.loadGames()
if (!viewModel.gamesLiveData.isInitialized){
viewModel.loadGames()
}
}
private fun initViews() {
@@ -115,7 +124,11 @@ class GamesActivity : WokaBaseActivity() {
}
retryBtn.setOnClickListener {
GamesRepository.loadGames()
viewModel.loadGames()
}
loadMoreBtn.setOnClickListener {
viewModel.loadGames()
}
}
}
@@ -239,8 +252,20 @@ class GamesActivity : WokaBaseActivity() {
!like.isSelected
)
gameData.likes_count?.let {
gameData.likes_count = if (like.isSelected){
// unlike
max(0, it - 1)
}else{
// like
it + 1
}
}
gameData.is_liked = !like.isSelected
gameData.id?.let {
onGameChanged(it)
onGameChanged(it, gameData)
}
like.isSelected = !like.isSelected
likeCount.text = "${gameData.likes_count}"
@@ -252,8 +277,10 @@ class GamesActivity : WokaBaseActivity() {
!fav.isSelected
)
gameData.mark_as_favourite = !fav.isSelected
gameData.id?.let {
onGameChanged(it)
onGameChanged(it, gameData)
}
fav.isSelected = !fav.isSelected
@@ -268,46 +295,75 @@ class GamesActivity : WokaBaseActivity() {
}
}
private fun onGameChanged(id: Int) {
private fun onGameChanged(id: Int, gameData: GameData) {
// updating continue book list
val position = gameAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < gameAdapter.currentList.size) {
val currKaraoke = gameAdapter.currentList[position]
// updating possible data changes
currKaraoke.is_liked = gameData.is_liked
currKaraoke.likes_count = gameData.likes_count
currKaraoke.mark_as_favourite = gameData.mark_as_favourite
gameAdapter.notifyItemChanged(position)
}
}
private fun setObservers(){
GamesRepository.gamesLiveData.observe(this) {
when (it) {
viewModel.gamesLiveData.observe(this){
when(it){
is ApiResult.Error -> {
binding.shimmer.hide()
binding.rvGames.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
if (gameAdapter.currentList.size == 0) {
// none of the data is yet loaded
binding.rvGames.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
binding.errorView.show()
binding.errorView.show()
} else {
// error in loading more
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.retry)
binding.loadMoreBtn.show()
}
}
is ApiResult.Loading -> {
binding.shimmer.show()
binding.rvGames.hide()
binding.listenTxt.hide()
binding.trailerView.hide()
binding.errorView.hide()
if (gameAdapter.currentList.size == 0) {
// loading first data
binding.shimmer.show()
binding.errorView.hide()
} else {
// loading more data
binding.loadMoreProgress.show()
binding.loadMoreBtn.hide()
}
}
is ApiResult.Success -> {
it.data?.game_data?.filterNotNull()?.let { gameData ->
if (gameData.isNotEmpty()) {
binding.shimmer.hide()
binding.errorView.hide()
loadTrailerData(gameData[0])
it.data?.let {newList ->
if (newList.isNotEmpty()){
binding.rvGames.show()
binding.listenTxt.show()
binding.trailerView.show()
gameAdapter.submitList(gameData)
binding.errorView.hide()
binding.shimmer.hide()
binding.loadMoreProgress.hide()
binding.loadMoreBtn.text = getString(R.string.load_more)
binding.loadMoreBtn.setVisibility(!viewModel.lastPage)
if (gameAdapter.currentList.isEmpty()){
// first data load
loadTrailerData(newList[0])
gameAdapter.submitList(newList)
}else{
// loaded more data
gameAdapter.notifyItemRangeInserted(
gameAdapter.currentList.size,
newList.size
)
}
}
}
}

View File

@@ -274,6 +274,42 @@
tools:listitem="@layout/show_view_holder"
/>
<ProgressBar
android:id="@+id/load_more_progress"
android:visibility="gone"
android:layout_width="25dp"
android:layout_height="25dp"
android:indeterminate="true"
android:indeterminateTint="@color/white"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="15dp"
/>
<Button
android:id="@+id/load_more_btn"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/load_more"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_12ssp"
android:paddingHorizontal="25dp"
android:layout_marginVertical="15dp"
android:background="@drawable/round_25"
android:backgroundTint="@color/night_status"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -246,6 +246,42 @@
tools:listitem="@layout/show_view_holder"
/>
<ProgressBar
android:id="@+id/load_more_progress"
android:visibility="gone"
android:layout_width="25dp"
android:layout_height="25dp"
android:indeterminate="true"
android:indeterminateTint="@color/white"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="15dp"
/>
<Button
android:id="@+id/load_more_btn"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/load_more"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_12ssp"
android:paddingHorizontal="25dp"
android:layout_marginVertical="15dp"
android:background="@drawable/round_25"
android:backgroundTint="@color/night_status"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fcv_web_series"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".webseries.views.SeriesActivity"/>

View File

@@ -0,0 +1,346 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient_web_series"
android:orientation="vertical"
tools:context=".webseries.views.WebSeriesActivity">
<include android:id="@+id/toolbar"
layout="@layout/layout_toolbar"/>
<RelativeLayout
android:id="@+id/shimmer"
android:visibility="visible"
android:background="@color/color_primary_dark"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:shimmer_auto_start="true"
app:shimmer_duration="1500"
app:shimmer_highlight_alpha="0.5"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/_160sdp"
android:background="@color/white_50"
/>
<View
android:layout_width="@dimen/_100sdp"
android:layout_height="@dimen/_37sdp"
android:background="@color/white_50"
android:layout_marginHorizontal="15dp"
android:layout_marginVertical="10dp"
/>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/_140sdp"
android:background="@color/white_50"
android:layout_marginHorizontal="15dp"
/>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/_140sdp"
android:background="@color/white_50"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="15dp"
/>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/_140sdp"
android:background="@color/white_50"
android:layout_marginVertical="15dp"
android:layout_marginHorizontal="15dp"
/>
</LinearLayout>
</ScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/error_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_primary_dark"
android:orientation="vertical"
android:gravity="center"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/something_went_wrong"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
/>
<Button
android:id="@+id/retry_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/retry"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:paddingHorizontal="25dp"
android:layout_marginTop="15dp"
android:background="@drawable/grad_btn_bg_4"
/>
</LinearLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/trailer_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">
<ImageView
android:id="@+id/masila_image"
android:layout_width="match_parent"
android:layout_height="@dimen/_160sdp"
android:contentDescription="@string/image"
android:src="@drawable/img_masila_full"
android:scaleType="fitXY"
app:layout_collapseMode="parallax"
android:layout_gravity="top"
/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/top_pinned_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_primary_dark"
app:layout_collapseMode="pin"
app:contentInsetStart="0dp"
app:contentInsetLeft="0dp"
app:contentInsetStartWithNavigation="0dp"
android:layout_gravity="bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp"
android:gravity="center"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/masila"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_16ssp"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/trailer"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_12ssp"
android:paddingHorizontal="25dp"
android:layout_marginTop="15dp"
android:background="@drawable/trailer_btn_bg"
/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_primary_dark"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/continue_watch_txt"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/continue_watching"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginHorizontal="15dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_continue_watch"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/continue_show_view_holder"
android:orientation="horizontal"
/>
<TextView
android:id="@+id/select_lang_txt"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_video_language"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginHorizontal="15dp"
/>
<androidx.cardview.widget.CardView
android:id="@+id/spinner_card"
android:visibility="gone"
android:layout_width="@dimen/_160sdp"
android:layout_height="@dimen/_40sdp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="10dp"
app:cardCornerRadius="5dp"
>
<Spinner
android:id="@+id/category_spinner"
android:layout_width="match_parent"
android:layout_height="@dimen/_40sdp"
android:popupBackground="@drawable/round_bg_5_white"
android:textAlignment="textStart"
/>
</androidx.cardview.widget.CardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_web_series"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/show_view_holder"
android:layout_marginTop="15dp"
/>
<Button
android:id="@+id/load_more_btn"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/load_more"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_12ssp"
android:paddingHorizontal="25dp"
android:layout_marginVertical="15dp"
android:background="@drawable/round_25"
android:backgroundTint="@color/night_status"
android:layout_gravity="center_horizontal"
/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer_show_data"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_base_alpha="0.8"
app:shimmer_auto_start="true"
app:shimmer_highlight_alpha="0.5"
app:shimmer_duration="1500"
android:layout_marginVertical="10dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/show_view_holder"/>
<include layout="@layout/show_view_holder"/>
<include layout="@layout/show_view_holder"/>
<include layout="@layout/show_view_holder"/>
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>