From c593a7550cf40db589f345df21e3deb19fe7c884 Mon Sep 17 00:00:00 2001 From: AdityaGaikwad Date: Tue, 16 Jul 2024 20:50:06 +0530 Subject: [PATCH] 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 --- app/src/main/AndroidManifest.xml | 13 +- .../woka/audiobooks/AudioBookApiService.kt | 4 +- .../woka/audiobooks/AudioBookRepository.kt | 151 +------ .../audiobooks/adapters/AudioBooksAdapter.kt | 25 +- .../adapters/ContinueAudioAdapter.kt | 22 +- .../viewmodels/AudioBookViewModel.kt | 81 ++++ .../audiobooks/views/AudioBooksActivity.kt | 142 +++++-- .../com/woka/home/fragments/Home1Fragment.kt | 3 +- .../java/com/woka/home/views/HomeActivity.kt | 3 + .../com/woka/karaoke/KaraokeRepository.kt | 77 +--- .../adapters/ContinueKaraokeAdapter.kt | 21 +- .../woka/karaoke/adapters/KaraokeAdapter.kt | 21 +- .../karaoke/viewmodels/KaraokeViewModel.kt | 65 ++- .../com/woka/karaoke/views/KaraokeActivity.kt | 182 ++++---- .../com/woka/userPreference/UserPreference.kt | 2 - .../java/com/woka/webseries/Repository.kt | 189 ++++++++ .../com/woka/webseries/WebSeriesApiService.kt | 4 + .../adapters/WebSeriesShowAdapter.kt | 22 +- .../com/woka/webseries/viewmodel/ViewModel.kt | 111 +++++ .../woka/webseries/views/SeriesActivity.kt | 37 ++ .../views/fragments/WebSeriesFragment.kt | 402 ++++++++++++++++++ .../com/woka/wokagames/GamesRepository.kt | 82 +--- .../woka/wokagames/adapters/GamesAdapter.kt | 15 + .../wokagames/viewmodels/GamesViewModel.kt | 53 +++ .../com/woka/wokagames/views/GamesActivity.kt | 108 +++-- .../main/res/layout/activity_audio_books.xml | 36 ++ app/src/main/res/layout/activity_games.xml | 36 ++ app/src/main/res/layout/activity_series.xml | 7 + .../main/res/layout/fragment_web_series.xml | 346 +++++++++++++++ 29 files changed, 1808 insertions(+), 452 deletions(-) create mode 100644 app/src/main/java/com/woka/audiobooks/viewmodels/AudioBookViewModel.kt create mode 100644 app/src/main/java/com/woka/webseries/Repository.kt create mode 100644 app/src/main/java/com/woka/webseries/viewmodel/ViewModel.kt create mode 100644 app/src/main/java/com/woka/webseries/views/SeriesActivity.kt create mode 100644 app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt create mode 100644 app/src/main/java/com/woka/wokagames/viewmodels/GamesViewModel.kt create mode 100644 app/src/main/res/layout/activity_series.xml create mode 100644 app/src/main/res/layout/fragment_web_series.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aaa7c99..c4cb747 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,8 @@ xmlns:tools="http://schemas.android.com/tools"> - - + + + + android:supportsPictureInPicture="true" /> + android:screenOrientation="portrait" /> > + suspend fun audioBookListing(@Body formBody: FormBody): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/woka/audiobooks/AudioBookRepository.kt b/app/src/main/java/com/woka/audiobooks/AudioBookRepository.kt index 4f3f30e..fff45bd 100644 --- a/app/src/main/java/com/woka/audiobooks/AudioBookRepository.kt +++ b/app/src/main/java/com/woka/audiobooks/AudioBookRepository.kt @@ -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>() - val audioBooksLiveData: LiveData> - get() = _audioBooksLiveData - - private var audioBooksData: AudioBooksResponse? = null - - // continue watch audio books data - private val _continueAudioBooksLiveData = MutableLiveData>() - val continueAudioBooksLiveData: LiveData> - 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 { + 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 { + 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/audiobooks/adapters/AudioBooksAdapter.kt b/app/src/main/java/com/woka/audiobooks/adapters/AudioBooksAdapter.kt index 5c8e859..c3ccc7d 100644 --- a/app/src/main/java/com/woka/audiobooks/adapters/AudioBooksAdapter.kt +++ b/app/src/main/java/com/woka/audiobooks/adapters/AudioBooksAdapter.kt @@ -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(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 { diff --git a/app/src/main/java/com/woka/audiobooks/adapters/ContinueAudioAdapter.kt b/app/src/main/java/com/woka/audiobooks/adapters/ContinueAudioAdapter.kt index 77961c4..d381cc2 100644 --- a/app/src/main/java/com/woka/audiobooks/adapters/ContinueAudioAdapter.kt +++ b/app/src/main/java/com/woka/audiobooks/adapters/ContinueAudioAdapter.kt @@ -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(DIFF_CONFIG) { + private var onContinueBookChanged: (id: Int, AudioBookData) -> Unit): ListAdapter(DIFF_CONFIG) { companion object{ private val DIFF_UTIL = object : DiffUtil.ItemCallback(){ @@ -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 { diff --git a/app/src/main/java/com/woka/audiobooks/viewmodels/AudioBookViewModel.kt b/app/src/main/java/com/woka/audiobooks/viewmodels/AudioBookViewModel.kt new file mode 100644 index 0000000..424cb84 --- /dev/null +++ b/app/src/main/java/com/woka/audiobooks/viewmodels/AudioBookViewModel.kt @@ -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>>() + val audioBookLiveData: LiveData>> + get() = _audioBookData + + private val audioBookList = ArrayList() + + private var nextPageToLoad: Int = 0 + private var quantityPerPage: Int = 6 + var lastPage = false + + private val _audioContinueLiveData = MutableLiveData>>() + val audioContinueLiveData: LiveData>> + 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)) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt b/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt index d1d0749..50cf1b4 100644 --- a/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt +++ b/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt @@ -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 diff --git a/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt b/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt index 2dacd15..98d74c1 100644 --- a/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt +++ b/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt @@ -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)) } } diff --git a/app/src/main/java/com/woka/home/views/HomeActivity.kt b/app/src/main/java/com/woka/home/views/HomeActivity.kt index 927278f..e028c53 100644 --- a/app/src/main/java/com/woka/home/views/HomeActivity.kt +++ b/app/src/main/java/com/woka/home/views/HomeActivity.kt @@ -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() diff --git a/app/src/main/java/com/woka/karaoke/KaraokeRepository.kt b/app/src/main/java/com/woka/karaoke/KaraokeRepository.kt index cd0ca46..3105e34 100644 --- a/app/src/main/java/com/woka/karaoke/KaraokeRepository.kt +++ b/app/src/main/java/com/woka/karaoke/KaraokeRepository.kt @@ -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>() - val continueKaraokeLiveData: LiveData> - get() = _continueKaraokeLiveData - - private var continueKaraokeData: ContinueKaraokeResponse? = null - suspend fun loadKaraokeSongs(pageNo: Int, quantity: Int): ApiResult { 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 { + 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/karaoke/adapters/ContinueKaraokeAdapter.kt b/app/src/main/java/com/woka/karaoke/adapters/ContinueKaraokeAdapter.kt index 622cf46..cca0bc7 100644 --- a/app/src/main/java/com/woka/karaoke/adapters/ContinueKaraokeAdapter.kt +++ b/app/src/main/java/com/woka/karaoke/adapters/ContinueKaraokeAdapter.kt @@ -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(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 { diff --git a/app/src/main/java/com/woka/karaoke/adapters/KaraokeAdapter.kt b/app/src/main/java/com/woka/karaoke/adapters/KaraokeAdapter.kt index 9b83b36..9926880 100644 --- a/app/src/main/java/com/woka/karaoke/adapters/KaraokeAdapter.kt +++ b/app/src/main/java/com/woka/karaoke/adapters/KaraokeAdapter.kt @@ -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(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 { diff --git a/app/src/main/java/com/woka/karaoke/viewmodels/KaraokeViewModel.kt b/app/src/main/java/com/woka/karaoke/viewmodels/KaraokeViewModel.kt index a8c0219..c3653fa 100644 --- a/app/src/main/java/com/woka/karaoke/viewmodels/KaraokeViewModel.kt +++ b/app/src/main/java/com/woka/karaoke/viewmodels/KaraokeViewModel.kt @@ -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>>() - val karaokeLiveData: LiveData>> + private val _karaokeLiveData = MutableLiveData>>() + val karaokeLiveData: LiveData>> get() = _karaokeLiveData - var nextPageToLoad: Int = 0 - private var quantityPerPage: Int = 10 - private var lastPage = false + private val karaokeList = ArrayList() - fun loadKaraokeSongs(currentItemCount: Int){ + private var nextPageToLoad: Int = 0 + private var quantityPerPage: Int = 6 + var lastPage = false + + private val _karaokeContinueLiveData = MutableLiveData>>() + val karaokeContinueLiveData: LiveData>> + 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)) + } } } } diff --git a/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt b/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt index 1f8102a..46d8141 100644 --- a/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt +++ b/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt @@ -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() diff --git a/app/src/main/java/com/woka/userPreference/UserPreference.kt b/app/src/main/java/com/woka/userPreference/UserPreference.kt index fd714a9..336c895 100644 --- a/app/src/main/java/com/woka/userPreference/UserPreference.kt +++ b/app/src/main/java/com/woka/userPreference/UserPreference.kt @@ -117,7 +117,5 @@ class UserPreference(val context: Context) { private fun clearData(){ WebSeriesRepository.clearData() MyListRepository.clearData() - AudioBookRepository.clearData() - KaraokeRepository.clearData() } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/Repository.kt b/app/src/main/java/com/woka/webseries/Repository.kt new file mode 100644 index 0000000..95d3a5c --- /dev/null +++ b/app/src/main/java/com/woka/webseries/Repository.kt @@ -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 { + return handleApiCall { + apiService.categoryListing( + FormBody.Builder() + .add("module_id", "7") + .build() + ) + } + } + + suspend fun loadTeaserData(showId: Int, seasonId: Int): ApiResult { + 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 { + 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 { + return handleApiCall { + apiService.seasonListing( + FormBody.Builder() + .add("watch_show_id", "$showId") + .add("category_id", categoryId) + .build() + ) + } + } + + suspend fun loadContinueWatchData(): ApiResult { + return handleApiCall { + userActionApiService.continueWatchingShowListing( + FormBody.Builder() + .add("post_type", "3") + .build() + ) + } + } + + suspend fun loadWebSeries(categoryId: String, pageNo: Int, quantity: Int): ApiResult { + 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) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt b/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt index ce67eb8..841fbea 100644 --- a/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt +++ b/app/src/main/java/com/woka/webseries/WebSeriesApiService.kt @@ -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> + + @POST("category_listing") + suspend fun categoryListing(@Body body: FormBody): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/adapters/WebSeriesShowAdapter.kt b/app/src/main/java/com/woka/webseries/adapters/WebSeriesShowAdapter.kt index 226bd04..c02436e 100644 --- a/app/src/main/java/com/woka/webseries/adapters/WebSeriesShowAdapter.kt +++ b/app/src/main/java/com/woka/webseries/adapters/WebSeriesShowAdapter.kt @@ -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 } } diff --git a/app/src/main/java/com/woka/webseries/viewmodel/ViewModel.kt b/app/src/main/java/com/woka/webseries/viewmodel/ViewModel.kt new file mode 100644 index 0000000..713e5e2 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/viewmodel/ViewModel.kt @@ -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>() + val showCategoryLiveData: LiveData> + get() = _showCategoryLiveData + + var categoryPagingData = HashMap() + + fun loadCategories() { + viewModelScope.launch { + _showCategoryLiveData.postValue(ApiResult.Loading()) + _showCategoryLiveData.postValue(repository.loadCategories()) + } + } + + // continue watching + private val _continueWatchLiveData = MutableLiveData>() + val continueWatchLiveData: LiveData> + get() = _continueWatchLiveData + + fun loadContinueWatching() { + viewModelScope.launch { + _continueWatchLiveData.postValue(ApiResult.Loading()) + _continueWatchLiveData.postValue(repository.loadContinueWatchData()) + } + } + + // show data + private val _webSeriesLiveData = + MutableLiveData>>() + val webSeriesLiveData: LiveData>> + get() = _webSeriesLiveData + + // map of series data where key is category + private var webSeriesData = HashMap>() + + 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++ + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/views/SeriesActivity.kt b/app/src/main/java/com/woka/webseries/views/SeriesActivity.kt new file mode 100644 index 0000000..184c900 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/views/SeriesActivity.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt b/app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt new file mode 100644 index 0000000..f92ec30 --- /dev/null +++ b/app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt @@ -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( + "
", + " " + ), Html.FROM_HTML_MODE_LEGACY + ) + } + }else{ + moreDetailsList[0].let {data -> + title.text = data.title + description.text = Html.fromHtml( + data.description?.replace( + "
", + " " + ), Html.FROM_HTML_MODE_LEGACY + ) + } + } + }else{ + title.text = episodeData.episode_title + description.text = Html.fromHtml( + episodeData.episode_description?.replace( + "
", + " " + ), 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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/wokagames/GamesRepository.kt b/app/src/main/java/com/woka/wokagames/GamesRepository.kt index 27a8452..2a43aac 100644 --- a/app/src/main/java/com/woka/wokagames/GamesRepository.kt +++ b/app/src/main/java/com/woka/wokagames/GamesRepository.kt @@ -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>() - val gamesLiveData: LiveData> - 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 { + 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 - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/wokagames/adapters/GamesAdapter.kt b/app/src/main/java/com/woka/wokagames/adapters/GamesAdapter.kt index 51ea1d0..c2aec91 100644 --- a/app/src/main/java/com/woka/wokagames/adapters/GamesAdapter.kt +++ b/app/src/main/java/com/woka/wokagames/adapters/GamesAdapter.kt @@ -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( @@ -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 } diff --git a/app/src/main/java/com/woka/wokagames/viewmodels/GamesViewModel.kt b/app/src/main/java/com/woka/wokagames/viewmodels/GamesViewModel.kt new file mode 100644 index 0000000..82056e6 --- /dev/null +++ b/app/src/main/java/com/woka/wokagames/viewmodels/GamesViewModel.kt @@ -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>>() + val gamesLiveData: LiveData>> + get() = _gamesLiveData + + private val gamesList = ArrayList() + + 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++ + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt b/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt index 375b336..9947cb5 100644 --- a/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt +++ b/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt @@ -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 + ) + } } } } diff --git a/app/src/main/res/layout/activity_audio_books.xml b/app/src/main/res/layout/activity_audio_books.xml index ed1666a..6822f38 100644 --- a/app/src/main/res/layout/activity_audio_books.xml +++ b/app/src/main/res/layout/activity_audio_books.xml @@ -274,6 +274,42 @@ tools:listitem="@layout/show_view_holder" /> + + +