KaraokePlayerActivity :

Saving audio file to Music folder.
Handling for api level 29 and above.
Handling permissions for writing_external_storage in api level below 29.

Handling button disabling with recording state change

integrated pagination for karaoke

Started integrating mvvm model for KaraokeActivity
This commit is contained in:
2024-07-15 21:11:42 +05:30
parent a79ba8813e
commit c828a6ae00
10 changed files with 267 additions and 175 deletions

View File

@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name=".WokaApp"

View File

@@ -19,19 +19,11 @@ import okhttp3.FormBody
import kotlin.math.max
object KaraokeRepository {
// api services
private val apiService = RetrofitHelper.getRetrofit().create(KaraokeApiService::class.java)
private val userActionApiService = RetrofitHelper.getRetrofit().create(UserActionApiService::class.java)
// live data
// audio books data (loose caching)
private val _karaokeLiveData = MutableLiveData<ApiResult<KaraokeResponse>>()
val karaokeLiveData: LiveData<ApiResult<KaraokeResponse>>
get() = _karaokeLiveData
private var karaokeData: KaraokeResponse? = null
// continue sing karaoke data
private val _continueKaraokeLiveData = MutableLiveData<ApiResult<ContinueKaraokeResponse>>()
val continueKaraokeLiveData: LiveData<ApiResult<ContinueKaraokeResponse>>
@@ -39,37 +31,15 @@ object KaraokeRepository {
private var continueKaraokeData: ContinueKaraokeResponse? = null
fun loadKaraokeSongs(){
if (karaokeData != null){
_karaokeLiveData.postValue(ApiResult.Success(karaokeData))
return
}
CoroutineScope(Dispatchers.IO).launch{
_karaokeLiveData.postValue(ApiResult.Loading())
val response = handleApiCall {
apiService.karaokeListing(
FormBody.Builder()
.add("api_version", "v2")
.add("start", "0")
.add("limit", "10")
.build()
)
}
when (response){
is ApiResult.Error -> _karaokeLiveData.postValue(ApiResult.Error(response.errorMessage, response.error))
is ApiResult.Loading -> _karaokeLiveData.postValue(ApiResult.Loading())
is ApiResult.Success -> {
response.data?.let {
karaokeData = it
_karaokeLiveData.postValue(ApiResult.Success(it))
}?:{
_karaokeLiveData.postValue(ApiResult.Error())
}
}
}
suspend fun loadKaraokeSongs(pageNo: Int, quantity: Int): ApiResult<KaraokeResponse> {
return handleApiCall {
apiService.karaokeListing(
FormBody.Builder()
.add("api_version", "v2")
.add("start", "$pageNo")
.add("limit", "$quantity")
.build()
)
}
}
@@ -127,26 +97,6 @@ object KaraokeRepository {
}
private fun changeLikeLocally(id: String, isLiked: Boolean){
// changing in karaoke locally
karaokeData?.karaoke_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 Karaoke data update
continueKaraokeData?.result?.let {
@@ -214,15 +164,6 @@ object KaraokeRepository {
}
}
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){
@@ -235,7 +176,6 @@ object KaraokeRepository {
}
fun clearData(){
karaokeData = null
continueKaraokeData = null
}
}

View File

@@ -1,6 +1,6 @@
package com.woka.karaoke.models.listing
data class KaraokeResponse(
val karaoke_data: List<KaraokeData?>?,
val karaoke_data: MutableList<KaraokeData?>?,
val total_records: Int?
)

View File

@@ -1,9 +1,8 @@
package com.woka.karaoke.player
import android.Manifest
import android.R.attr.mimeType
import android.app.AlertDialog
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.media.MediaMetadataRetriever
import android.media.MediaPlayer
@@ -15,7 +14,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.util.Log
import android.view.WindowManager
@@ -23,7 +21,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.OptIn
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@@ -92,6 +89,7 @@ class KaraokePlayerActivity : WokaBaseActivity() {
private var audioPlayer: MediaPlayer? = null
private lateinit var permissionLauncher: ActivityResultLauncher<String>
private lateinit var storagePermissionLauncher: ActivityResultLauncher<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -149,20 +147,6 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
override fun onStart() {
super.onStart()
if (player?.isPlaying == false) {
player?.play()
}
}
override fun onStop() {
super.onStop()
if (player?.isPlaying == true) {
player?.pause()
}
}
override fun onDestroy() {
super.onDestroy()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@@ -178,11 +162,18 @@ class KaraokePlayerActivity : WokaBaseActivity() {
(getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager).unregisterNetworkCallback(
networkCallback
)
File(recordingOutputPath).deleteOnExit()
File(karaokeFinalPath).deleteOnExit()
File(karaokeMusicPath).deleteOnExit()
}
private fun initViews() {
binding.apply {
title.text = karaokePlayerData?.title
binding.audioBtn.isEnabled = false
binding.downloadBtn.isEnabled = false
}
}
@@ -207,26 +198,26 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
audioBtn.setOnClickListener {
playStopPlayingAudio()
if (audioBtn.isEnabled){
playStopPlayingAudio()
}
}
downloadBtn.setOnClickListener {
try {
val cal = Calendar.getInstance()
val fileName = "${karaokePlayerData?.title}_${
SimpleDateFormat(
"dd MMM yyyy hh:mm a",
Locale.getDefault()
).format(cal.time)
}.mp3"
if (audioBtn.isEnabled){
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
saveFileUsingMediaStore()
}else{
storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
saveFileUsingMediaStore(fileName)
} catch (e: FileNotFoundException) {
toast(getString(R.string.no_recorded_audio_found))
} catch (e: Exception) {
Log.d(TAG, "clickEvents: $e")
toast(getString(R.string.something_went_wrong))
} catch (e: FileNotFoundException) {
toast(getString(R.string.no_recorded_audio_found))
} catch (e: Exception) {
Log.d(TAG, "clickEvents: $e")
toast(getString(R.string.something_went_wrong))
}
}
}
}
@@ -242,6 +233,16 @@ class KaraokePlayerActivity : WokaBaseActivity() {
toast(getString(R.string.permission_deniedd))
}
}
storagePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) {
if (it){
saveFileUsingMediaStore()
}else{
toast(getString(R.string.permission_deniedd))
}
}
}
private fun updateRecordingUI() {
@@ -260,15 +261,17 @@ class KaraokePlayerActivity : WokaBaseActivity() {
playerView.useController = false
audioBtn.isEnabled = false
audioBtn.alpha = 0.5f
audioBtn.text = getString(R.string.play)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0)
downloadBtn.isEnabled = false
downloadBtn.alpha = 0.5f
}
RecordingState.NOT_RECORDING -> {
recordBtn.text = getString(R.string.stop_recording)
recordBtn.text = getString(R.string.start_recording)
recordBtn.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_rec_mic,
0,
@@ -278,11 +281,8 @@ class KaraokePlayerActivity : WokaBaseActivity() {
playerView.useController = true
audioBtn.isEnabled = true
audioBtn.text = getString(R.string.play)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_play, 0, 0, 0)
downloadBtn.isEnabled = true
}
RecordingState.PLAYING_AUDIO -> {
@@ -297,7 +297,6 @@ class KaraokePlayerActivity : WokaBaseActivity() {
playerView.useController = true
audioBtn.isEnabled = true
if (audioPlayer?.isPlaying == true) {
audioBtn.text = getString(R.string.pause)
audioBtn.setCompoundDrawablesWithIntrinsicBounds(
@@ -315,8 +314,6 @@ class KaraokePlayerActivity : WokaBaseActivity() {
0
)
}
downloadBtn.isEnabled = true
}
}
}
@@ -397,10 +394,9 @@ class KaraokePlayerActivity : WokaBaseActivity() {
if (player?.isPlaying == true) {
player?.pause()
}
audioPlayer?.start()
start()
recordingState = RecordingState.PLAYING_AUDIO
updateRecordingUI()
setOnCompletionListener {
audioPlayer?.release()
@@ -413,14 +409,14 @@ class KaraokePlayerActivity : WokaBaseActivity() {
toast(getString(R.string.no_recorded_audio_found))
recordingState = RecordingState.NOT_RECORDING
audioPlayer = null
updateRecordingUI()
} catch (e: Exception) {
toast(getString(R.string.something_went_wrong))
recordingState = RecordingState.NOT_RECORDING
audioPlayer = null
updateRecordingUI()
}
}
updateRecordingUI()
}
RecordingState.RECORDING -> {
@@ -562,8 +558,15 @@ class KaraokePlayerActivity : WokaBaseActivity() {
binding.recorderView.show()
binding.mixingProgressView.hide()
binding.mixingProgressBar.progress = 0
binding.mixingProgressPercent.text = null
toast(getString(R.string.audio_is_ready_to_play))
binding.audioBtn.alpha = 1f
binding.audioBtn.isEnabled = true
binding.downloadBtn.alpha = 1f
binding.downloadBtn.isEnabled = true
audioMixer.release()
}
@@ -605,7 +608,16 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
}
private fun saveFileUsingMediaStore(fileName: String) {
private fun saveFileUsingMediaStore() {
val cal = Calendar.getInstance()
val fileName = "${karaokePlayerData?.title}_${
SimpleDateFormat(
"dd MMM yyyy hh:mm a",
Locale.getDefault()
).format(cal.time)
}.mp3"
val resolver = applicationContext.contentResolver
val audioCollection =
@@ -618,8 +630,12 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
val songDetails = ContentValues().apply {
put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
put(MediaStore.Audio.Media.IS_PENDING, 1)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
put(MediaStore.Audio.Media.DATA, File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), fileName).absolutePath)
}else{
put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
resolver.insert(audioCollection, songDetails)?.let {songContentUri ->
@@ -639,14 +655,31 @@ class KaraokePlayerActivity : WokaBaseActivity() {
}
}
}
AlertDialog.Builder(this@KaraokePlayerActivity)
.setMessage(getString(R.string.file_saved_to_your_music_folder))
.setPositiveButton(getString(R.string.ok_caps)) { dialog, which ->
dialog.dismiss()
}
.create()
.show()
}catch (e: Exception){
resolver.delete(songContentUri, null, null)
AlertDialog.Builder(this@KaraokePlayerActivity)
.setMessage(getString(R.string.file_saved_to_your_music_folder))
.setPositiveButton(getString(R.string.ok_caps)) { dialog, which ->
dialog.dismiss()
}
.create()
.show()
}
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)
toast("Done")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
songDetails.clear()
songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0)
resolver.update(songContentUri, songDetails, null, null)
}
}
}

View File

@@ -0,0 +1,35 @@
package com.woka.karaoke.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woka.karaoke.KaraokeRepository
import com.woka.karaoke.models.listing.KaraokeData
import com.woka.networking.ApiResult
import kotlinx.coroutines.launch
class KaraokeViewModel: ViewModel() {
private val _karaokeLiveData = MutableLiveData<ApiResult<List<KaraokeData>>>()
val karaokeLiveData: LiveData<ApiResult<List<KaraokeData>>>
get() = _karaokeLiveData
var nextPageToLoad: Int = 0
private var quantityPerPage: Int = 10
private var lastPage = false
fun loadKaraokeSongs(currentItemCount: Int){
viewModelScope.launch {
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 {
}
}
}
}
}
}

View File

@@ -12,10 +12,12 @@ 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
import com.woka.WokaApp
import com.woka.WokaApp.Companion.userPrefs
import com.woka.databinding.ActivityKaraokeBinding
import com.woka.databinding.DialogModuleShowerBinding
import com.woka.karaoke.KaraokeRepository
@@ -25,9 +27,12 @@ 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.karaoke.viewmodels.KaraokeViewModel
import com.woka.networking.ApiResult
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
@@ -37,6 +42,8 @@ class KaraokeActivity : WokaBaseActivity() {
private lateinit var binding: ActivityKaraokeBinding
private lateinit var viewModel: KaraokeViewModel
// adapters
private lateinit var karaokeAdapter: KaraokeAdapter
private lateinit var continueKaraokeAdapter: ContinueKaraokeAdapter
@@ -59,8 +66,11 @@ class KaraokeActivity : WokaBaseActivity() {
navigationBarColor = getColor(R.color.color_primary_dark)
}
viewModel = ViewModelProvider(this)[KaraokeViewModel::class.java]
karaokeAdapter = KaraokeAdapter(this, ::onKaraokeClicked, ::onKaraokeChanged)
continueKaraokeAdapter = ContinueKaraokeAdapter(this, ::onKaraokeClicked, ::onKaraokeChanged)
continueKaraokeAdapter =
ContinueKaraokeAdapter(this, ::onKaraokeClicked, ::onKaraokeChanged)
dialogBinding = DialogModuleShowerBinding.inflate(layoutInflater)
karaokeDialog = Dialog(this)
@@ -73,8 +83,6 @@ class KaraokeActivity : WokaBaseActivity() {
clickEvents()
setObservers()
KaraokeRepository.loadKaraokeSongs()
}
private fun initViews() {
@@ -112,7 +120,8 @@ class KaraokeActivity : WokaBaseActivity() {
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.backgroundTintList =
ColorStateList.valueOf(getColor(R.color.game_grad_one))
dialogBinding.watchCard.text = getString(R.string.sing_now)
}
@@ -123,7 +132,11 @@ class KaraokeActivity : WokaBaseActivity() {
}
retryBtn.setOnClickListener {
KaraokeRepository.loadKaraokeSongs()
}
loadMoreBtn.setOnClickListener {
}
}
}
@@ -155,7 +168,7 @@ class KaraokeActivity : WokaBaseActivity() {
}
}
private fun onSingClicked(karaokeData: 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))
@@ -274,13 +287,13 @@ class KaraokeActivity : WokaBaseActivity() {
}
private fun onKaraokeChanged(id: Int, isFromContinue: Boolean) {
if (isFromContinue){
if (isFromContinue) {
// updating karaoke list
val position = karaokeAdapter.currentList.indexOfFirst { it.id == id }
if (position >= 0 && position < karaokeAdapter.currentList.size) {
karaokeAdapter.notifyItemChanged(position)
}
}else{
} else {
// updating continue karaoke list
val continuePos = continueKaraokeAdapter.currentList.indexOfFirst { it.id == id }
if (continuePos >= 0 && continuePos < continueKaraokeAdapter.currentList.size) {
@@ -295,44 +308,65 @@ class KaraokeActivity : WokaBaseActivity() {
}
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)
}
}
}
}
}
// 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)
// }
// }
// }
// }
KaraokeRepository.continueKaraokeLiveData.observe(this) {
when (it) {
@@ -343,7 +377,7 @@ class KaraokeActivity : WokaBaseActivity() {
is ApiResult.Loading -> {}
is ApiResult.Success -> {
it.data?.result?.filterNotNull()?.let {continueData ->
it.data?.result?.filterNotNull()?.let { continueData ->
if (continueData.isNotEmpty()) {
binding.continueSingTxt.show()
binding.rvContinueSing.show()

View File

@@ -47,6 +47,11 @@ fun View.hide(){
this.visibility = GONE
}
fun View.setVisibility(show: Boolean){
if (show) show()
else hide()
}
fun Context.changeLocale(language: String){
val locale = Locale(language)
Locale.setDefault(locale)

View File

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

View File

@@ -189,7 +189,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:gravity="center"
>
<Button
@@ -223,15 +223,15 @@
<Button
android:id="@+id/audio_btn"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:layout_height="40dp"
android:text="@string/play"
android:fontFamily="@font/exo_2_bold"
android:textColor="@color/white"
android:textSize="11sp"
android:textAllCaps="false"
android:maxLines="1"
android:ellipsize="end"
android:textAllCaps="false"
android:tooltipText="@string/play"
android:drawableStart="@drawable/ic_play"
@@ -241,11 +241,15 @@
android:paddingVertical="5dp"
android:layout_marginVertical="10dp"
android:layout_marginStart="@dimen/_5sdp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:background="@drawable/round_25"
android:backgroundTint="@android:color/holo_blue_dark"
android:alpha="0.5"
android:enabled="false"
tools:targetApi="o" />
</LinearLayout>
@@ -263,6 +267,9 @@
android:layout_marginTop="10dp"
android:padding="5dp"
android:alpha="0.5"
android:enabled="false"
/>
</LinearLayout>

View File

@@ -218,5 +218,6 @@
<string name="creating_karaoke_music">Creating karaoke music...</string>
<string name="no_recorded_audio_found">No recorded audio found</string>
<string name="download_complete">Download complete</string>
<string name="file_saved_to_your_download_folder">File saved to your download folder.</string>
<string name="file_saved_to_your_music_folder">File saved to your music folder.</string>
<string name="load_more">LOAD MORE</string>
</resources>