Created Karaoke module: UI and backend

Integrated karaoke listing api and data caching
Integrated Continue Karaoke api and data caching

Data syncing with adapters and MyListFragment

Dialog showing for karaoke.

Implemented Media3 Player view for KaraokePlayerActivity

Custom Player controller view - adding close button and title and hiding full screen and settings icon
Player setup, hiding user controller view
This commit is contained in:
2024-07-09 19:19:03 +05:30
parent 294afcb80a
commit 64626f7730
25 changed files with 1716 additions and 41 deletions

View File

@@ -15,13 +15,25 @@
android:supportsRtl="true"
android:theme="@style/Theme.Woka"
tools:targetApi="31">
<activity
android:name=".karaoke.player.KaraokePlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:exported="false"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:screenOrientation="portrait"
/>
<activity
android:name=".karaoke.views.KaraokeActivity"
android:exported="false"
android:screenOrientation="portrait"/>
<activity
android:name=".wokagames.playerr.GamePlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:exported="false"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:theme="@style/FullScreenTheme"/>
android:theme="@style/FullScreenTheme" />
<activity
android:name=".wokagames.views.GamesActivity"
android:exported="false"

View File

@@ -78,7 +78,7 @@ object AudioBookRepository {
val response = handleApiCall {
userActionApiService.continueAudioBookListing(
FormBody.Builder()
.add("post_type", "7")
.add("post_type", PostType.AUDIO_BOOKS.value)
.build()
)
}

View File

@@ -19,9 +19,8 @@ import com.woka.utils.toast
import java.util.concurrent.Executors
class ContinueAudioAdapter(private val context: Context,
config: AsyncDifferConfig<ContinueAudioData>,
private var onBookClicked: (ContinueAudioData) -> Unit,
private var onContinueBookChanged: (id: Int) -> Unit): ListAdapter<ContinueAudioData, FavoriteViewHolder>(config) {
private var onContinueBookChanged: (id: Int) -> Unit): ListAdapter<ContinueAudioData, FavoriteViewHolder>(DIFF_CONFIG) {
companion object{
private val DIFF_UTIL = object : DiffUtil.ItemCallback<ContinueAudioData>(){
@@ -39,10 +38,6 @@ class ContinueAudioAdapter(private val context: Context,
.build()
}
constructor(context: Context,
onBookClicked: (ContinueAudioData) -> Unit,
onContinueBookChanged: (id: Int) -> Unit): this(context, DIFF_CONFIG, onBookClicked, onContinueBookChanged)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteViewHolder {
return FavoriteViewHolder(
FavViewHolderBinding.inflate(

View File

@@ -25,6 +25,7 @@ import com.woka.home.views.FMActivity
import com.woka.home.viewmodels.HomeViewModel
import com.woka.home.views.MoreHomeActivity
import com.woka.home.models.TimePeriod
import com.woka.karaoke.views.KaraokeActivity
import com.woka.userdata.userDataModels.UserDataResponse
import com.woka.networking.ApiResult
import com.woka.players.views.LiveStreamPlayerActivity
@@ -193,6 +194,12 @@ class Home1Fragment : Fragment(), Listener {
startActivity(Intent(it, GamesActivity::class.java))
}
}
karaoke.setOnClickListener {
activity?.let {
startActivity(Intent(it, KaraokeActivity::class.java))
}
}
}
}

View File

@@ -29,12 +29,15 @@ import com.woka.databinding.FragmentMyListBinding
import com.woka.home.mylist.MyListRepository
import com.woka.home.mylist.adapters.FavAudioAdapter
import com.woka.home.mylist.adapters.FavGamesAdapter
import com.woka.home.mylist.adapters.KaraokeAdapter
import com.woka.home.mylist.adapters.FavKaraokeAdapter
import com.woka.home.mylist.adapters.WebSeriesAdapter
import com.woka.home.mylist.models.PostType
import com.woka.home.mylist.models.BookmarkedShowData
import com.woka.home.mylist.models.FavAudioBookData
import com.woka.home.mylist.models.FavGameData
import com.woka.home.mylist.models.FavKaraokeData
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.networking.ApiResult
import com.woka.players.models.VideoPlayList
import com.woka.players.views.PlayerActivity
@@ -59,7 +62,7 @@ class MyListFragment : Fragment() {
private lateinit var webSeriesEAdapter: WebSeriesAdapter
private lateinit var webSeriesHAdapter: WebSeriesAdapter
private lateinit var audioBooksAdapter: FavAudioAdapter
private lateinit var karaokeAdapter: KaraokeAdapter
private lateinit var karaokeAdapter: FavKaraokeAdapter
private lateinit var gamesAdapter: FavGamesAdapter
private var webShowIntentLauncher: ActivityResultLauncher<Intent>? = null
@@ -77,7 +80,7 @@ class MyListFragment : Fragment() {
webSeriesEAdapter = WebSeriesAdapter(requireContext(),"1", ::onListGotEmpty)
webSeriesHAdapter = WebSeriesAdapter(requireContext(),"18", ::onListGotEmpty)
audioBooksAdapter = FavAudioAdapter(requireContext(), ::onListGotEmpty, ::onAudioBookClicked)
karaokeAdapter = KaraokeAdapter(requireContext(), ::onListGotEmpty)
karaokeAdapter = FavKaraokeAdapter(requireContext(), ::onListGotEmpty, ::onKaraokeClicked)
gamesAdapter = FavGamesAdapter(requireContext(), ::onListGotEmpty, ::onGameClicked)
// dialogs
@@ -606,6 +609,128 @@ class MyListFragment : Fragment() {
}
}
// karaoke
private fun onKaraokeClicked(karaokeData: FavKaraokeData, position: Int) {
dialogBinding.apply {
karaokeData.content_more_details?.let { moreDetailsList ->
karaokeData.thumbnail_path?.let {
image.loadImage(it)
}
fav.isSelected = karaokeData.mark_as_favourite == true
like.isSelected = karaokeData.is_liked == true
likeCount.text = "${karaokeData.likes_count}"
year.text = try {
karaokeData.release_date?.let {
val cal = Calendar.getInstance()
cal.time =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).parse(
karaokeData.release_date
)!!
"${cal.get(Calendar.YEAR)}"
} ?: throw Exception()
} catch (e: Exception) {
"${karaokeData.release_date}"
}
if (moreDetailsList.isNotEmpty()) {
if (WokaApp.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 = karaokeData.title
description.text = Html.fromHtml(
karaokeData.description?.replace(
"<br>",
" "
), Html.FROM_HTML_MODE_LEGACY
)
}
activity?.let {
watchCard.backgroundTintList = ColorStateList.valueOf(it.getColor(R.color.game_grad_one))
}
watchCard.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_mic, 0, 0, 0)
watchCard.text = getString(R.string.sing_now)
watchCard.setOnClickListener {
}
like.setOnClickListener {
KaraokeRepository.likeUnLikeSong(
"${karaokeData.id}",
!like.isSelected
)
karaokeData.id?.let {
onKaraokeChanged(it)
}
like.isSelected = !like.isSelected
likeCount.text = "${karaokeData.likes_count}"
}
fav.setOnClickListener {
if (context?.isNetworkConnected() == false){
toast(getString(R.string.no_internet))
return@setOnClickListener
}
KaraokeRepository.updateFavShow(
KaraokeData(karaokeData),
!fav.isSelected
)
try {
karaokeAdapter.notifyItemRemoved(position)
} catch (e: Exception) {
// do nothing
}
if (karaokeAdapter.currentList.isEmpty()){
onListGotEmpty(PostType.KARAOKE, true)
}
moduleShowerDialog.dismiss()
}
close.setOnClickListener {
moduleShowerDialog.dismiss()
}
moduleShowerDialog.show()
}
}
}
private fun onKaraokeChanged(id: Int) {
// updating continue book list
val position = karaokeAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < karaokeAdapter.currentList.size) {
karaokeAdapter.notifyItemChanged(position)
}
}
companion object {
fun newInstance() = MyListFragment()
}

View File

@@ -1,34 +1,34 @@
package com.woka.home.mylist.adapters
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.bumptech.glide.Glide
import com.woka.R
import com.woka.databinding.FavViewHolderBinding
import com.woka.home.mylist.MyListRepository
import com.woka.home.mylist.models.FavKaraokeData
import com.woka.home.mylist.models.PostType
import com.woka.home.mylist.models.SingKaraokeData
import com.woka.utils.TAG
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.utils.isNetworkConnected
import com.woka.utils.toast
import java.util.concurrent.Executors
import kotlin.math.max
class KaraokeAdapter(private val context: Context,
config: AsyncDifferConfig<SingKaraokeData>,
private val onListEmptyListener: ((postType: PostType, isEng: Boolean) -> Unit)): ListAdapter<SingKaraokeData, FavoriteViewHolder>(config) {
class FavKaraokeAdapter(private val context: Context,
private val onListEmptyListener: ((postType: PostType, isEng: Boolean) -> Unit),
private var onKaraokeClicked: (FavKaraokeData, Int) -> Unit)
: ListAdapter<FavKaraokeData, FavoriteViewHolder>(DIFF_CONFIG) {
companion object{
private val DIFF_UTIL = object : DiffUtil.ItemCallback<SingKaraokeData>(){
override fun areItemsTheSame(oldItem: SingKaraokeData, newItem: SingKaraokeData): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: SingKaraokeData, newItem: SingKaraokeData): Boolean {
return oldItem.title == newItem.title &&
private val DIFF_UTIL = object : DiffUtil.ItemCallback<FavKaraokeData>(){
override fun areItemsTheSame(oldItem: FavKaraokeData, newItem: FavKaraokeData): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: FavKaraokeData, newItem: FavKaraokeData): Boolean {
return oldItem.thumbnail_path == newItem.thumbnail_path &&
oldItem.title == newItem.title &&
oldItem.is_liked == newItem.is_liked &&
oldItem.mark_as_favourite == newItem.mark_as_favourite &&
oldItem.likes_count == newItem.likes_count
}
}
@@ -38,8 +38,6 @@ class KaraokeAdapter(private val context: Context,
.build()
}
constructor(context: Context, onListEmptyListener: ((postType: PostType, isEng: Boolean) -> Unit)): this(context, DIFF_CONFIG, onListEmptyListener)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteViewHolder {
return FavoriteViewHolder(
FavViewHolderBinding.inflate(
@@ -74,7 +72,13 @@ class KaraokeAdapter(private val context: Context,
return@setOnClickListener
}
KaraokeRepository.likeUnLikeSong(
"${karaokeData.id}",
!like.isSelected
)
like.isSelected = !like.isSelected
likeCount.text = "${karaokeData.likes_count}"
}
fav.isSelected = true
@@ -85,6 +89,20 @@ class KaraokeAdapter(private val context: Context,
return@setOnClickListener
}
KaraokeRepository.updateFavShow(
KaraokeData(karaokeData),
!fav.isSelected
)
notifyItemRemoved(holder.absoluteAdapterPosition)
if (currentList.isEmpty()){
onListEmptyListener(PostType.KARAOKE, true)
}
}
root.setOnClickListener {
onKaraokeClicked(karaokeData, holder.absoluteAdapterPosition)
}
}
}

View File

@@ -0,0 +1,46 @@
package com.woka.home.mylist.models
import com.woka.karaoke.models.listing.ContentMoreDetail
import com.woka.karaoke.models.listing.KaraokeData
data class FavKaraokeData(
val age_range_master_id: String?,
val bookmark_category_ids: String?,
val bookmark_count: Int?,
val category_master_id: String?,
val content_more_details: List<ContentMoreDetail?>?,
val description: String?,
val duration: String?,
val gender_master_id: String?,
val id: Int?,
var is_liked: Boolean?,
val language_master_id: Int?,
var likes_count: Int?,
val mark_as_favourite: Boolean?,
val release_date: String?,
val thumbnail_path: String?,
val title: String?,
val video_url: String?,
val views_count: Int?
){
constructor(karaoke: KaraokeData): this(
karaoke.age_range_master_id,
karaoke.bookmark_category_ids,
karaoke.bookmark_count,
karaoke.category_master_id,
karaoke.content_more_details,
karaoke.description,
karaoke.duration,
karaoke.gender_master_id,
karaoke.id,
karaoke.is_liked,
karaoke.language_master_id,
karaoke.likes_count,
karaoke.mark_as_favourite,
karaoke.release_date,
karaoke.thumbnail_path,
karaoke.title,
karaoke.video_url,
karaoke.views_count
)
}

View File

@@ -4,6 +4,6 @@ data class Result(
val audio_data: MutableList<FavAudioBookData>?,
val game_data: MutableList<FavGameData>?,
val show_data: MutableList<BookmarkedShowData>?,
val sing_karaoke_data: MutableList<SingKaraokeData>?,
val sing_karaoke_data: MutableList<FavKaraokeData>?,
val video_data: MutableList<Any>?
)

View File

@@ -3,7 +3,10 @@ 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
import com.woka.karaoke.models.continuesing.ContinueKaraokeResponse
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.karaoke.models.listing.KaraokeResponse
import com.woka.networking.ApiResult
import com.woka.networking.RetrofitHelper
@@ -29,7 +32,14 @@ object KaraokeRepository {
private var karaokeData: KaraokeResponse? = null
fun loadGames(){
// continue sing karaoke data
private val _continueKaraokeLiveData = MutableLiveData<ApiResult<ContinueKaraokeResponse>>()
val continueKaraokeLiveData: LiveData<ApiResult<ContinueKaraokeResponse>>
get() = _continueKaraokeLiveData
private var continueKaraokeData: ContinueKaraokeResponse? = null
fun loadKaraokeSongs(){
if (karaokeData != null){
_karaokeLiveData.postValue(ApiResult.Success(karaokeData))
return
@@ -38,7 +48,7 @@ object KaraokeRepository {
CoroutineScope(Dispatchers.IO).launch{
_karaokeLiveData.postValue(ApiResult.Loading())
val response = RetrofitHelper.handleApiCall {
val response = handleApiCall {
apiService.karaokeListing(
FormBody.Builder()
.build()
@@ -60,7 +70,36 @@ object KaraokeRepository {
}
}
fun likeUnLikeGame(postId: String, likeIt: Boolean){
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)
}
}
fun likeUnLikeSong(postId: String, likeIt: Boolean){
CoroutineScope(Dispatchers.IO).launch {
handleApiCall {
if (likeIt){
@@ -106,6 +145,27 @@ object KaraokeRepository {
}
}
// 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){
@@ -120,4 +180,59 @@ object KaraokeRepository {
}
}
}
fun updateFavShow(karaoke: KaraokeData, addToBookmark: Boolean){
CoroutineScope(Dispatchers.IO).launch {
handleApiCall {
if (addToBookmark){
userActionApiService.addToFav(
FormBody.Builder()
.add("post_id", "${karaoke.id}")
.add("post_type", PostType.KARAOKE.value)
.build()
)
}else{
userActionApiService.removeFromFav(
FormBody.Builder()
.add("id", "${karaoke.id}")
.add("post_type", PostType.KARAOKE.value)
.build()
)
}
}
}
MyListRepository.myFavData.result?.sing_karaoke_data?.let {favAudioData ->
karaoke.mark_as_favourite = addToBookmark
if (addToBookmark){
favAudioData.add(FavKaraokeData(karaoke))
}else{
favAudioData.removeIf{it.id == karaoke.id}
}
}
karaokeData?.karaoke_data?.let {
for (audio in it){
if (audio?.id == karaoke.id){
audio?.mark_as_favourite = addToBookmark
break
}
}
}
continueKaraokeData?.result?.let {
for (audio in it){
if (audio?.id == karaoke.id){
audio?.mark_as_favourite = addToBookmark
break
}
}
}
}
fun clearData(){
karaokeData = null
continueKaraokeData = null
}
}

View File

@@ -0,0 +1,134 @@
package com.woka.karaoke.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.woka.R
import com.woka.WokaApp.Companion.userPrefs
import com.woka.databinding.FavViewHolderBinding
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.continuesing.ContinueKaraokeData
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
class ContinueKaraokeAdapter(
private val context: Context,
private var onKaraokeClicked: (KaraokeData) -> Unit,
private var onKaraokeChanged: (id: Int, isFromContinue: Boolean) -> Unit
): ListAdapter<ContinueKaraokeData, ContinueKaraokeAdapter.AudioBookViewHolder>(ASYNC_DIFF_UTIL) {
inner class AudioBookViewHolder(val binding: FavViewHolderBinding): ViewHolder(binding.root)
companion object{
val DIFF_UTIL = object : DiffUtil.ItemCallback<ContinueKaraokeData>(){
override fun areItemsTheSame(oldItem: ContinueKaraokeData, newItem: ContinueKaraokeData): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ContinueKaraokeData, newItem: ContinueKaraokeData): Boolean {
return oldItem.thumbnail_path == newItem.thumbnail_path &&
oldItem.title == newItem.title &&
oldItem.is_liked == newItem.is_liked &&
oldItem.mark_as_favourite == newItem.mark_as_favourite &&
oldItem.likes_count == newItem.likes_count
}
}
val ASYNC_DIFF_UTIL = AsyncDifferConfig.Builder(DIFF_UTIL)
.setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
.build()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioBookViewHolder {
return AudioBookViewHolder(
FavViewHolderBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: AudioBookViewHolder, position: Int) {
val karaokeData = getItem(holder.absoluteAdapterPosition)
holder.binding.apply {
karaokeData.thumbnail_path?.let {
image.loadImage(it)
}
karaokeData.content_more_details?.let {moreDetailsList ->
title.text = if (moreDetailsList.isNotEmpty()){
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){
moreDetailsList[1]?.title
}else{
moreDetailsList[0]?.title
}
}else{
karaokeData.title
}
}
like.show()
likeCount.show()
fav.show()
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
karaokeData.is_liked?.let {
like.isSelected = it
}
like.setOnClickListener {
if (!context.isNetworkConnected()){
context.toast(context.getString(R.string.no_internet))
return@setOnClickListener
}
KaraokeRepository.likeUnLikeSong(
"${karaokeData.id}",
!like.isSelected
)
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
karaokeData.is_liked?.let {
like.isSelected = it
}
karaokeData.id?.let{onKaraokeChanged(it, true)}
}
fav.isSelected = karaokeData.mark_as_favourite == true
fav.setOnClickListener {
if (!context.isNetworkConnected()){
context.toast(context.getString(R.string.no_internet))
return@setOnClickListener
}
KaraokeRepository.updateFavShow(
KaraokeData(karaokeData),
!fav.isSelected
)
fav.isSelected = karaokeData.mark_as_favourite == true
karaokeData.id?.let{onKaraokeChanged(it, true)}
}
root.setOnClickListener {
onKaraokeClicked(KaraokeData(karaokeData))
}
}
}
}

View File

@@ -0,0 +1,133 @@
package com.woka.karaoke.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.woka.R
import com.woka.WokaApp.Companion.userPrefs
import com.woka.databinding.ShowViewHolderBinding
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.utils.isNetworkConnected
import com.woka.utils.show
import com.woka.utils.toast
import java.util.concurrent.Executors
class KaraokeAdapter(
private val context: Context,
private var onKaraokeClicked: (KaraokeData) -> Unit,
private var onKaraokeChanged: (id: Int, isContinue: Boolean) -> Unit
): ListAdapter<KaraokeData, KaraokeAdapter.AudioBookViewHolder>(ASYNC_DIFF_UTIL) {
inner class AudioBookViewHolder(val binding: ShowViewHolderBinding): ViewHolder(binding.root)
companion object{
val DIFF_UTIL = object : DiffUtil.ItemCallback<KaraokeData>(){
override fun areItemsTheSame(oldItem: KaraokeData, newItem: KaraokeData): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: KaraokeData, newItem: KaraokeData): Boolean {
return oldItem.thumbnail_path == newItem.thumbnail_path &&
oldItem.title == newItem.title &&
oldItem.is_liked == newItem.is_liked &&
oldItem.mark_as_favourite == newItem.mark_as_favourite &&
oldItem.likes_count == newItem.likes_count
}
}
val ASYNC_DIFF_UTIL = AsyncDifferConfig.Builder(DIFF_UTIL)
.setBackgroundThreadExecutor(Executors.newSingleThreadExecutor())
.build()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioBookViewHolder {
return AudioBookViewHolder(
ShowViewHolderBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: AudioBookViewHolder, position: Int) {
val karaokeData = getItem(holder.absoluteAdapterPosition)
holder.binding.apply {
karaokeData.thumbnail_path?.let {
image.loadImage(it)
}
karaokeData.content_more_details?.let {moreDetailsList ->
title.text = if (moreDetailsList.isNotEmpty()){
if (userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1){
moreDetailsList[1]?.title
}else{
moreDetailsList[0]?.title
}
}else{
karaokeData.title
}
}
like.show()
likeCount.show()
fav.show()
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
karaokeData.is_liked?.let {
like.isSelected = it
}
like.setOnClickListener {
if (!context.isNetworkConnected()){
context.toast(context.getString(R.string.no_internet))
return@setOnClickListener
}
KaraokeRepository.likeUnLikeSong(
"${karaokeData.id}",
!like.isSelected
)
karaokeData.likes_count?.let {
likeCount.text = "$it"
}
karaokeData.is_liked?.let {
like.isSelected = it
}
karaokeData.id?.let{onKaraokeChanged(it, false)}
}
fav.isSelected = karaokeData.mark_as_favourite == true
fav.setOnClickListener {
if (!context.isNetworkConnected()){
context.toast(context.getString(R.string.no_internet))
return@setOnClickListener
}
KaraokeRepository.updateFavShow(
karaokeData,
!fav.isSelected
)
fav.isSelected = karaokeData.mark_as_favourite == true
karaokeData.id?.let{onKaraokeChanged(it, false)}
}
root.setOnClickListener {
onKaraokeClicked(karaokeData)
}
}
}
}

View File

@@ -1,11 +1,12 @@
package com.woka.home.mylist.models
package com.woka.karaoke.models.continuesing
data class SingKaraokeData(
import com.woka.karaoke.models.listing.ContentMoreDetail
data class ContinueKaraokeData(
val age_range_master_id: String?,
val bookmark_category_ids: String?,
val bookmark_count: Int?,
val category_master_id: String?,
// val content_more_details: List<ContentMoreDetail>?,
val content_more_details: List<ContentMoreDetail?>?,
val description: String?,
val duration: String?,
val gender_master_id: String?,
@@ -13,10 +14,12 @@ data class SingKaraokeData(
var is_liked: Boolean?,
val language_master_id: Int?,
var likes_count: Int?,
val mark_as_favourite: Boolean?,
var mark_as_favourite: Boolean?,
val release_date: String?,
val show_data_count: Any?,
val thumbnail_path: String?,
val title: String?,
val user_video_view: List<Any?>?,
val video_url: String?,
val views_count: Int?
)

View File

@@ -0,0 +1,6 @@
package com.woka.karaoke.models.continuesing
data class ContinueKaraokeResponse(
val result: List<ContinueKaraokeData?>?,
val total_records: Int?
)

View File

@@ -1,24 +1,82 @@
package com.woka.karaoke.models.listing
import com.woka.home.mylist.models.FavKaraokeData
import com.woka.karaoke.models.continuesing.ContinueKaraokeData
data class KaraokeData(
val age_range_data: List<Any?>?,
val age_range_master_id: String?,
val bookmark_category_ids: String?,
val bookmark_count: Int?,
val category_data: List<Any?>?,
val category_master_id: String?,
val content_more_details: List<ContentMoreDetail?>?,
val description: String?,
val duration: String?,
val gender_data: List<Any?>?,
val gender_master_id: String?,
val id: Int?,
var is_liked: Boolean?,
val language_master_id: Int?,
var likes_count: Int?,
val mark_as_favourite: Boolean?,
var mark_as_favourite: Boolean?,
val release_date: String?,
val show_data_count: Any?,
val thumbnail_path: String?,
val title: String?,
val user_video_view: List<Any?>?,
val video_url: String?,
val views_count: Int?
)
val views_count: Int?,
val age_range_data: List<Any?>?,
val category_data: List<Any?>?,
val gender_data: List<Any?>?
){
constructor(favKaraoke: FavKaraokeData): this(
favKaraoke.age_range_master_id,
favKaraoke.bookmark_category_ids,
favKaraoke.bookmark_count,
favKaraoke.category_master_id,
favKaraoke.content_more_details,
favKaraoke.description,
favKaraoke.duration,
favKaraoke.gender_master_id,
favKaraoke.id,
favKaraoke.is_liked,
favKaraoke.language_master_id,
favKaraoke.likes_count,
favKaraoke.mark_as_favourite,
favKaraoke.release_date,
null,
favKaraoke.thumbnail_path,
favKaraoke.title,
null,
favKaraoke.video_url,
favKaraoke.views_count,
null,
null,
null
)
constructor(continueKaraoke: ContinueKaraokeData): this(
continueKaraoke.age_range_master_id,
null,
continueKaraoke.bookmark_count,
continueKaraoke.category_master_id,
continueKaraoke.content_more_details,
continueKaraoke.description,
continueKaraoke.duration,
continueKaraoke.gender_master_id,
continueKaraoke.id,
continueKaraoke.is_liked,
continueKaraoke.language_master_id,
continueKaraoke.likes_count,
continueKaraoke.mark_as_favourite,
continueKaraoke.release_date,
continueKaraoke.show_data_count,
continueKaraoke.thumbnail_path,
continueKaraoke.title,
continueKaraoke.user_video_view,
continueKaraoke.video_url,
continueKaraoke.views_count,
null,
null,
null
)
}

View File

@@ -0,0 +1,84 @@
package com.woka.karaoke.player
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import com.woka.R
import com.woka.databinding.ActivityKaraokePlayerrBinding
import com.woka.utils.WokaBaseActivity
class KaraokePlayerActivity : WokaBaseActivity() {
companion object {
const val EXTRA_KARAOKE_DATA = "extra_karaoke_data"
}
private lateinit var binding: ActivityKaraokePlayerrBinding
private var karaokePlayerData: KaraokePlayerData? = null
private var player: ExoPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityKaraokePlayerrBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val windowInsetsController =
WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
karaokePlayerData = intent.getParcelableExtra(EXTRA_KARAOKE_DATA)
player = ExoPlayer.Builder(this).build()
binding.playerView.player = player
playVideo()
initViews()
clickEvents()
}
override fun onDestroy() {
super.onDestroy()
player?.stop()
player?.release()
}
private fun initViews(){
binding.apply {
playerView.findViewById<TextView>(R.id.player_controller_title).text = karaokePlayerData?.title
}
}
private fun clickEvents(){
binding.apply {
playerView.findViewById<ImageView>(R.id.player_controller_close_btn).setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
}
private fun playVideo() {
if (karaokePlayerData?.karaokeVideoUrl == null) return
player?.setMediaItem(MediaItem.fromUri(karaokePlayerData?.karaokeVideoUrl!!))
player?.prepare()
player?.play()
}
}

View File

@@ -0,0 +1,10 @@
package com.woka.karaoke.player
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class KaraokePlayerData(
val karaokeVideoUrl: String,
val title: String?
): Parcelable

View File

@@ -0,0 +1,361 @@
package com.woka.karaoke.views
import android.app.Dialog
import android.content.Intent
import android.content.res.ColorStateList
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.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.woka.R
import com.woka.WokaApp
import com.woka.databinding.ActivityKaraokeBinding
import com.woka.databinding.DialogModuleShowerBinding
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.adapters.ContinueKaraokeAdapter
import com.woka.karaoke.adapters.KaraokeAdapter
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.karaoke.player.KaraokePlayerActivity
import com.woka.karaoke.player.KaraokePlayerActivity.Companion.EXTRA_KARAOKE_DATA
import com.woka.karaoke.player.KaraokePlayerData
import com.woka.networking.ApiResult
import com.woka.utils.WokaBaseActivity
import com.woka.utils.hide
import com.woka.utils.show
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class KaraokeActivity : WokaBaseActivity() {
private lateinit var binding: ActivityKaraokeBinding
// adapters
private lateinit var karaokeAdapter: KaraokeAdapter
private lateinit var continueKaraokeAdapter: ContinueKaraokeAdapter
private lateinit var dialogBinding: DialogModuleShowerBinding
private lateinit var karaokeDialog: Dialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityKaraokeBinding.inflate(layoutInflater)
enableEdgeToEdge()
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.apply {
navigationBarColor = getColor(R.color.color_primary_dark)
}
karaokeAdapter = KaraokeAdapter(this, ::onKaraokeClicked, ::onKaraokeChanged)
continueKaraokeAdapter = ContinueKaraokeAdapter(this, ::onKaraokeClicked, ::onKaraokeChanged)
dialogBinding = DialogModuleShowerBinding.inflate(layoutInflater)
karaokeDialog = Dialog(this)
karaokeDialog.setContentView(dialogBinding.root)
initViews()
initKaraokeDialog()
clickEvents()
setObservers()
KaraokeRepository.loadKaraokeSongs()
}
private fun initViews() {
binding.apply {
adjustTrailerImage()
toolbar.title.text = getString(R.string.karaoke)
rvKaraoke.adapter = karaokeAdapter
(rvKaraoke.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
rvContinueSing.adapter = continueKaraokeAdapter
(rvContinueSing.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
}
private fun initKaraokeDialog() {
try {
val back = ColorDrawable(Color.TRANSPARENT)
val inset = InsetDrawable(back, 50)
karaokeDialog.window!!.setBackgroundDrawable(inset)
} catch (e: Exception) {
// do nothing
}
try {
val layoutParams = karaokeDialog.window!!.attributes
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
karaokeDialog.window!!.setAttributes(layoutParams)
} catch (e: Exception) {
// do nothing
}
dialogBinding.close.setOnClickListener { karaokeDialog.dismiss() }
dialogBinding.watchCard.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_mic, 0, 0, 0)
dialogBinding.watchCard.backgroundTintList = ColorStateList.valueOf(getColor(R.color.game_grad_one))
dialogBinding.watchCard.text = getString(R.string.sing_now)
}
private fun clickEvents() {
binding.apply {
toolbar.backBtn.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
retryBtn.setOnClickListener {
KaraokeRepository.loadKaraokeSongs()
}
}
}
private fun loadTrailerData(karaokeData: KaraokeData) {
binding.apply {
trailerView.show()
adjustTrailerImage()
karaokeData.thumbnail_path?.let {
trailerImage.loadImage(it)
}
karaokeData.content_more_details?.let { moreDetailsList ->
trailerName.text = if (moreDetailsList.isNotEmpty()) {
if (WokaApp.userPrefs?.appLanguage == "hi" && moreDetailsList.size > 1) {
moreDetailsList[1]?.title
} else {
moreDetailsList[0]?.title
}
} else {
karaokeData.title
}
}
trailerBtn.setOnClickListener {
onSingClicked(karaokeData)
}
}
}
private fun onSingClicked(karaokeData: KaraokeData){
karaokeData.video_url?.let {
startActivity(Intent(this@KaraokeActivity, KaraokePlayerActivity::class.java).apply {
putExtra(EXTRA_KARAOKE_DATA, KaraokePlayerData(it, karaokeData.title))
})
}
}
private fun adjustTrailerImage() {
// making space for Trailer image
binding.apply {
topPinnedView.post {
val imgParams = trailerImage.layoutParams as CollapsingToolbarLayout.LayoutParams
imgParams.setMargins(0, 0, 0, topPinnedView.height)
trailerImage.layoutParams = imgParams
}
}
}
private fun showKaraokeDialog(karaokeData: KaraokeData) {
dialogBinding.apply {
karaokeData.content_more_details?.let { moreDetailsList ->
karaokeData.thumbnail_path?.let {
image.loadImage(it)
}
fav.isSelected = karaokeData.mark_as_favourite == true
like.isSelected = karaokeData.is_liked == true
likeCount.text = "${karaokeData.likes_count}"
year.text = try {
karaokeData.release_date?.let {
val cal = Calendar.getInstance()
cal.time =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).parse(
karaokeData.release_date
)!!
"${cal.get(Calendar.YEAR)}"
} ?: throw Exception()
} catch (e: Exception) {
"${karaokeData.release_date}"
}
if (moreDetailsList.isNotEmpty()) {
if (WokaApp.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 = karaokeData.title
description.text = Html.fromHtml(
karaokeData.description?.replace(
"<br>",
" "
), Html.FROM_HTML_MODE_LEGACY
)
}
watchCard.setOnClickListener {
onSingClicked(karaokeData)
}
like.setOnClickListener {
KaraokeRepository.likeUnLikeSong(
"${karaokeData.id}",
!like.isSelected
)
karaokeData.id?.let {
onKaraokeChanged(it, false)
onKaraokeChanged(it, true)
}
like.isSelected = !like.isSelected
likeCount.text = "${karaokeData.likes_count}"
}
fav.setOnClickListener {
KaraokeRepository.updateFavShow(
karaokeData,
!fav.isSelected
)
karaokeData.id?.let {
onKaraokeChanged(it, false)
onKaraokeChanged(it, true)
}
fav.isSelected = !fav.isSelected
}
close.setOnClickListener {
karaokeDialog.dismiss()
}
karaokeDialog.show()
}
}
}
private fun onKaraokeChanged(id: Int, isFromContinue: Boolean) {
if (isFromContinue){
// updating karaoke list
val position = karaokeAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < karaokeAdapter.currentList.size) {
karaokeAdapter.notifyItemChanged(position)
}
}else{
// updating continue karaoke list
val continuePos = continueKaraokeAdapter.currentList.indexOfFirst { it.id == id }
if (continuePos >= 0 && continuePos < continueKaraokeAdapter.currentList.size) {
continueKaraokeAdapter.notifyItemChanged(continuePos)
}
}
}
private fun onKaraokeClicked(karaokeData: KaraokeData) {
loadTrailerData(karaokeData)
showKaraokeDialog(karaokeData)
}
private fun setObservers() {
KaraokeRepository.karaokeLiveData.observe(this) {
when (it) {
is ApiResult.Error -> {
binding.shimmer.hide()
binding.rvKaraoke.hide()
binding.singTxt.hide()
binding.trailerView.hide()
binding.errorView.show()
}
is ApiResult.Loading -> {
binding.shimmer.show()
binding.rvKaraoke.hide()
binding.singTxt.hide()
binding.trailerView.hide()
binding.errorView.hide()
}
is ApiResult.Success -> {
it.data?.karaoke_data?.filterNotNull()?.let { audioBookData ->
if (audioBookData.isNotEmpty()) {
binding.shimmer.hide()
binding.errorView.hide()
KaraokeRepository.loadContinueKaraoke()
loadTrailerData(audioBookData[0])
binding.rvKaraoke.show()
binding.singTxt.show()
karaokeAdapter.submitList(audioBookData)
}
}
}
}
}
KaraokeRepository.continueKaraokeLiveData.observe(this) {
when (it) {
is ApiResult.Error -> {
binding.continueSingTxt.hide()
binding.rvContinueSing.hide()
}
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.filterNotNull()?.let {continueData ->
if (continueData.isNotEmpty()) {
binding.continueSingTxt.show()
binding.rvContinueSing.show()
continueKaraokeAdapter.submitList(continueData)
} else {
binding.continueSingTxt.hide()
binding.rvContinueSing.hide()
}
}
}
}
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData
import com.woka.audiobooks.AudioBookRepository
import com.woka.home.models.Theme
import com.woka.home.mylist.MyListRepository
import com.woka.karaoke.KaraokeRepository
import com.woka.networking.ApiResult
import com.woka.onboard.views.OnboardActivity
import com.woka.userdata.UserRepository
@@ -117,5 +118,6 @@ class UserPreference(val context: Context) {
WebSeriesRepository.clearData()
MyListRepository.clearData()
AudioBookRepository.clearData()
KaraokeRepository.clearData()
}
}

View File

@@ -1,6 +1,7 @@
package com.woka.userdata
import com.woka.audiobooks.models.continuedata.ContinueAudioResponse
import com.woka.karaoke.models.continuesing.ContinueKaraokeResponse
import com.woka.networking.ApiResponse
import com.woka.webseries.models.ContinueEpisodeResponse
import okhttp3.FormBody
@@ -27,4 +28,7 @@ interface UserActionApiService {
@POST("continue_watching")
suspend fun continueAudioBookListing(@Body body: FormBody): Response<ApiResponse<ContinueAudioResponse>>
@POST("continue_watching")
suspend fun continueKaraokeListing(@Body body: FormBody): Response<ApiResponse<ContinueKaraokeResponse>>
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="#CC4A1C"
android:endColor="#CC9B1C"
android:centerColor="#CC9B1C"
android:centerX="0.3"
android:angle="-45"
/>
</shape>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="31" android:viewportWidth="31" android:width="24dp">
<path android:fillColor="#ffffff" android:pathData="M15.5,0C11.731,0 8.68,2.967 8.68,6.646V6.82H13.33C13.671,6.82 13.95,7.099 13.95,7.44C13.95,7.781 13.671,8.06 13.33,8.06H8.68V9.3H13.33C13.671,9.3 13.95,9.579 13.95,9.92C13.95,10.262 13.671,10.54 13.33,10.54H8.68V11.78H13.33C13.671,11.78 13.95,12.059 13.95,12.4C13.95,12.741 13.671,13.02 13.33,13.02H8.68V14.434C8.68,18.113 11.731,21.08 15.5,21.08C19.268,21.08 22.32,18.113 22.32,14.434V13.02H17.67C17.326,13.02 17.05,12.741 17.05,12.4C17.05,12.059 17.326,11.78 17.67,11.78H22.32V10.54H17.67C17.326,10.54 17.05,10.262 17.05,9.92C17.05,9.579 17.326,9.3 17.67,9.3H22.32V8.06H17.67C17.326,8.06 17.05,7.781 17.05,7.44C17.05,7.099 17.326,6.82 17.67,6.82H22.32V6.646C22.32,2.967 19.268,0 15.5,0ZM6.084,10.618C5.829,10.671 5.65,10.901 5.657,11.16V14.26C5.657,19.043 9.106,23.054 13.64,23.928V27.28H17.36V23.928C21.894,23.054 25.342,19.043 25.342,14.26V11.16C25.342,10.86 25.1,10.618 24.8,10.618C24.5,10.618 24.257,10.86 24.257,11.16V14.26C24.257,19.097 20.336,23.017 15.5,23.017C10.663,23.017 6.742,19.097 6.742,14.26V11.16C6.745,11.005 6.679,10.857 6.566,10.753C6.449,10.649 6.297,10.598 6.142,10.618C6.122,10.618 6.103,10.618 6.084,10.618ZM9.61,28.52C8.423,28.52 7.459,29.489 7.459,30.671L7.44,31L23.482,30.961L23.502,30.671C23.502,29.489 22.538,28.52 21.351,28.52H9.61Z"/>
</vector>

View File

@@ -0,0 +1,283 @@
<?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_karaoke"
android:orientation="vertical"
tools:context=".webseries.views.WebSeriesActivity">
<include android:id="@+id/toolbar"
layout="@layout/layout_toolbar"/>
<RelativeLayout
android:id="@+id/shimmer"
android:visibility="gone"
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"
android:layout_gravity="center_horizontal"
/>
<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"
android:background="@color/color_primary_dark"
>
<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">
<com.woka.utils.AdiImageView
android:id="@+id/trailer_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:id="@+id/trailer_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/masila"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_16ssp"
/>
<Button
android:id="@+id/trailer_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sing"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_12ssp"
android:paddingHorizontal="25dp"
android:layout_marginTop="15dp"
android:background="@drawable/play_btn_bg"
/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
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_sing_txt"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sing_again"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="5dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_continue_sing"
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"
android:layout_marginStart="15dp"
/>
<TextView
android:id="@+id/sing_txt"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sing_along_and_dance"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_karaoke"
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"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:background="@color/black"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".karaoke.player.KaraokePlayerActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_player_control_view"
/>
<RelativeLayout
android:id="@+id/error_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/close_btn"
android:layout_width="15dp"
android:layout_height="15dp"
android:contentDescription="@string/image"
android:src="@drawable/ic_close"
android:layout_margin="25dp"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/something_went_wrong"
android:fontFamily="@font/exo_2"
android:textColor="@color/white"
/>
<TextView
android:id="@+id/retry_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/retry_underline"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:padding="5dp"
/>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 0dp dimensions are used to prevent this view from influencing the size of
the parent view if it uses "wrap_content". It is expanded to occupy the
entirety of the parent in code, after the parent's size has been
determined. See: https://github.com/google/ExoPlayer/issues/8726.
-->
<View android:id="@id/exo_controls_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/exo_black_opacity_60"/>
<FrameLayout android:id="@id/exo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:layout_gravity="bottom"
android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr">
<LinearLayout android:id="@id/exo_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingEnd="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingLeft="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingRight="@dimen/exo_styled_bottom_bar_time_padding"
android:layout_gravity="center_vertical|start"
android:layoutDirection="ltr">
<TextView android:id="@id/exo_position"
style="@style/ExoStyledControls.TimeText.Position"/>
<TextView
style="@style/ExoStyledControls.TimeText.Separator"/>
<TextView android:id="@id/exo_duration"
style="@style/ExoStyledControls.TimeText.Duration"/>
</LinearLayout>
<LinearLayout android:id="@id/exo_basic_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layoutDirection="ltr">
<ImageButton android:id="@id/exo_vr"
style="@style/ExoStyledControls.Button.Bottom.VR"/>
<ImageButton android:id="@id/exo_shuffle"
style="@style/ExoStyledControls.Button.Bottom.Shuffle"/>
<ImageButton android:id="@id/exo_repeat_toggle"
style="@style/ExoStyledControls.Button.Bottom.RepeatToggle"/>
<ImageButton android:id="@id/exo_subtitle"
style="@style/ExoStyledControls.Button.Bottom.CC"/>
<ImageButton android:id="@id/exo_overflow_show"
style="@style/ExoStyledControls.Button.Bottom.OverflowShow"/>
</LinearLayout>
<HorizontalScrollView android:id="@id/exo_extra_controls_scroll_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:visibility="invisible">
<LinearLayout android:id="@id/exo_extra_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<ImageButton android:id="@id/exo_overflow_hide"
style="@style/ExoStyledControls.Button.Bottom.OverflowHide"/>
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>
<View android:id="@id/exo_progress_placeholder"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_progress_layout_height"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/>
<LinearLayout android:id="@id/exo_minimal_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/exo_styled_minimal_controls_margin_bottom"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layoutDirection="ltr">
<ImageButton android:id="@id/exo_minimal_fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
</LinearLayout>
<LinearLayout
android:id="@id/exo_center_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding"
android:clipToPadding="false"
android:layoutDirection="ltr">
<include layout="@layout/exo_player_control_rewind_button" />
<ImageButton android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"/>
<include layout="@layout/exo_player_control_ffwd_button" />
</LinearLayout>
<!-- custom vie-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/player_controller_close_btn"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/image"
android:src="@drawable/ic_close"
android:layout_margin="25dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="5dp"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
<TextView
android:id="@+id/player_controller_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="@dimen/_14ssp"
android:textAlignment="center"
android:ems="10"
android:maxLines="1"
android:ellipsize="end"
android:layout_gravity="center_horizontal"
android:layout_margin="15dp"
app:layout_constraintTop_toTopOf="@id/player_controller_close_btn"
app:layout_constraintBottom_toBottomOf="@id/player_controller_close_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/ExoStyledControls.Button.Center.PlayPause"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View File

@@ -198,4 +198,8 @@
<string name="back_warning_text">Clicking \"OK\" will exit you from the game.\nAre you sure you want to proceed?</string>
<string name="cancel_caps">CANCEL</string>
<string name="ok_caps">OK</string>
<string name="sing_along_and_dance">SING ALONG and DANCE</string>
<string name="sing_again">SING AGAIN</string>
<string name="sing">SING</string>
<string name="sing_now">SING NOW</string>
</resources>