From 4bb7a10a01430fccb7a31844a720fdf543a089d2 Mon Sep 17 00:00:00 2001 From: AdityaGaikwad Date: Wed, 4 Sep 2024 21:01:27 +0530 Subject: [PATCH] Integrated api for ads data. Cached data globally and inflated ads on Home1Fragment, WebSeriesFragment, KaraokeActivity, AudioBooksActivity and GamesActivity and ShopFragment1 Implemented local database for ads impressions and clicks. Implemented remote sync for ads clicks and impressions. Tested thw working of database and syncing of data. Shared build with client. Solved bug for clearing cart when user logs out. Implemented dialog for session expired. --- app/src/main/AndroidManifest.xml | 4 + app/src/main/java/com/woka/WokaApp.kt | 5 + .../com/woka/advertisements/AdsApiService.kt | 12 ++ .../com/woka/advertisements/AdsRepository.kt | 31 +++ .../com/woka/advertisements/models/AdData.kt | 11 + .../woka/advertisements/models/AdsResponse.kt | 6 + .../com/woka/advertisements/models/ForPage.kt | 11 + .../audiobooks/views/AudioBooksActivity.kt | 53 +++++ .../java/com/woka/database/AppDatabase.kt | 6 +- .../java/com/woka/database/dao/AdClickDao.kt | 43 ++++ .../woka/database/helpers/AdClicksHelper.kt | 37 ++++ .../com/woka/database/helpers/RemoteSync.kt | 66 +++++- .../database/models/adclicks/AdClickEvent.kt | 15 ++ .../woka/database/remote/RemoteApiService.kt | 4 + .../com/woka/home/fragments/Home1Fragment.kt | 45 +++- .../com/woka/home/viewmodels/HomeViewModel.kt | 1 + .../java/com/woka/home/views/HomeActivity.kt | 3 +- .../com/woka/karaoke/views/KaraokeActivity.kt | 54 +++++ .../views/fragments/shop/ShopFragment1.kt | 62 +++++- .../com/woka/userPreference/UserPreference.kt | 9 +- .../main/java/com/woka/utils/AdiImageView.kt | 7 +- .../java/com/woka/utils/DecisionDialog.kt | 4 + .../com/woka/utils/SessionExpiredActivity.kt | 36 ++++ .../views/fragments/WebSeriesFragment.kt | 47 +++++ .../com/woka/wokagames/views/GamesActivity.kt | 53 +++++ .../main/res/layout/activity_audio_books.xml | 25 +++ app/src/main/res/layout/activity_games.xml | 25 +++ app/src/main/res/layout/activity_karaoke.xml | 198 ++++++++++-------- .../res/layout/activity_session_expired.xml | 8 + .../main/res/layout/category_viwe_holder.xml | 7 +- app/src/main/res/layout/fragment_home_1.xml | 8 +- .../main/res/layout/fragment_web_series.xml | 35 +++- .../main/res/layout/layout_adi_imageview.xml | 1 + app/src/main/res/values/strings.xml | 2 + 34 files changed, 790 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/com/woka/advertisements/AdsApiService.kt create mode 100644 app/src/main/java/com/woka/advertisements/AdsRepository.kt create mode 100644 app/src/main/java/com/woka/advertisements/models/AdData.kt create mode 100644 app/src/main/java/com/woka/advertisements/models/AdsResponse.kt create mode 100644 app/src/main/java/com/woka/advertisements/models/ForPage.kt create mode 100644 app/src/main/java/com/woka/database/dao/AdClickDao.kt create mode 100644 app/src/main/java/com/woka/database/helpers/AdClicksHelper.kt create mode 100644 app/src/main/java/com/woka/database/models/adclicks/AdClickEvent.kt create mode 100644 app/src/main/java/com/woka/utils/SessionExpiredActivity.kt create mode 100644 app/src/main/res/layout/activity_session_expired.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e09df64..a34d286 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,10 @@ android:supportsRtl="true" android:theme="@style/Theme.Woka" tools:targetApi="31"> + diff --git a/app/src/main/java/com/woka/WokaApp.kt b/app/src/main/java/com/woka/WokaApp.kt index 94d55ba..59f0658 100644 --- a/app/src/main/java/com/woka/WokaApp.kt +++ b/app/src/main/java/com/woka/WokaApp.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.Application import com.jwplayer.pub.api.license.LicenseUtil import com.onesignal.OneSignal +import com.woka.advertisements.AdsRepository import com.woka.database.AppDatabase import com.woka.streamingurls.StreamingUrlRepository import com.woka.userPreference.UserPreference @@ -30,6 +31,10 @@ class WokaApp: Application() { StreamingUrlRepository.loadLiveStreamingUrls() + CoroutineScope(Dispatchers.IO).launch { + AdsRepository.getADs() + } + initOneSignal() } diff --git a/app/src/main/java/com/woka/advertisements/AdsApiService.kt b/app/src/main/java/com/woka/advertisements/AdsApiService.kt new file mode 100644 index 0000000..051fc4b --- /dev/null +++ b/app/src/main/java/com/woka/advertisements/AdsApiService.kt @@ -0,0 +1,12 @@ +package com.woka.advertisements + +import com.woka.advertisements.models.AdsResponse +import com.woka.networking.ApiResponse +import retrofit2.Response +import retrofit2.http.GET + +interface AdsApiService { + + @GET("get_ad_data") + suspend fun getAdsData() : Response> +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/advertisements/AdsRepository.kt b/app/src/main/java/com/woka/advertisements/AdsRepository.kt new file mode 100644 index 0000000..f049d46 --- /dev/null +++ b/app/src/main/java/com/woka/advertisements/AdsRepository.kt @@ -0,0 +1,31 @@ +package com.woka.advertisements + +import com.woka.advertisements.models.AdData +import com.woka.networking.ApiResult +import com.woka.networking.RetrofitHelper +import com.woka.networking.RetrofitHelper.handleApiCall + +object AdsRepository { + + private val apiService = RetrofitHelper.getRetrofit().create(AdsApiService::class.java) + + @Volatile + private var adsData: List? = null + + suspend fun getADs(): List?{ + if (adsData != null){ + return adsData + } + + when (val response = handleApiCall{ apiService.getAdsData() }){ + is ApiResult.Error -> {} + is ApiResult.Loading -> {} + is ApiResult.Success -> { + adsData = response.data?.result?.filterNotNull() + } + } + + return adsData + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/advertisements/models/AdData.kt b/app/src/main/java/com/woka/advertisements/models/AdData.kt new file mode 100644 index 0000000..b77da63 --- /dev/null +++ b/app/src/main/java/com/woka/advertisements/models/AdData.kt @@ -0,0 +1,11 @@ +package com.woka.advertisements.models + +data class AdData( + val ad_company: String?, + val ad_link: String?, + val banner_image: String?, + val button_image: String?, + val for_page: String?, + val id: Int?, + val title: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/advertisements/models/AdsResponse.kt b/app/src/main/java/com/woka/advertisements/models/AdsResponse.kt new file mode 100644 index 0000000..196b623 --- /dev/null +++ b/app/src/main/java/com/woka/advertisements/models/AdsResponse.kt @@ -0,0 +1,6 @@ +package com.woka.advertisements.models + +data class AdsResponse( + val result: List?, + val total_records: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/advertisements/models/ForPage.kt b/app/src/main/java/com/woka/advertisements/models/ForPage.kt new file mode 100644 index 0000000..76f5b9c --- /dev/null +++ b/app/src/main/java/com/woka/advertisements/models/ForPage.kt @@ -0,0 +1,11 @@ +package com.woka.advertisements.models + +enum class ForPage(val value: String) { + THEME_1("theme-1"), + THEME_2("theme-2"), + WEB_SERIES("web-series"), + KARAOKE("karaoke"), + AUDIO_BOOKS("audio-books"), + GAMES("games"), + SHOP_SUPER_CATEGORY("shop-super-category"); +} \ 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 c685dbe..0c30c62 100644 --- a/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt +++ b/app/src/main/java/com/woka/audiobooks/views/AudioBooksActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.InsetDrawable +import android.net.Uri import android.os.Bundle import android.text.Html import android.view.WindowManager @@ -14,17 +15,21 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope 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.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage 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.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.ActivityAudioBooksBinding @@ -42,6 +47,7 @@ import com.woka.utils.setVisibility import com.woka.utils.shareWokaApp import com.woka.utils.show import com.woka.utils.toast +import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -62,6 +68,8 @@ class AudioBooksActivity : WokaBaseActivity() { private lateinit var playerLauncher: ActivityResultLauncher + private var adLoaded = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -101,6 +109,8 @@ class AudioBooksActivity : WokaBaseActivity() { setObservers() + loadAds() + if (!viewModel.audioBookLiveData.isInitialized) { viewModel.loadAudioSongs() } @@ -182,6 +192,47 @@ class AudioBooksActivity : WokaBaseActivity() { } } + private fun loadAds(){ + lifecycleScope.launch { + AdsRepository.getADs()?.let { ads -> + val theme1ads = ads.filter { it.for_page == ForPage.AUDIO_BOOKS.value} + + if (theme1ads.isNotEmpty()){ + val adDetails = theme1ads.first() + + AdClicksHelper.addImpression(adDetails.id) + + adLoaded = true + + binding.trailerBtn.hide() + binding.trailerName.hide() + + binding.adButtonView.show() + + binding.trailerView.show() + adjustTrailerImage() + + adDetails.banner_image?.let {url -> + binding.trailerImage.loadImage(url) + } + + adDetails.button_image?.let {url -> + binding.adButtonImg.loadImage(url) + } + + adDetails.ad_link?.let {url -> + binding.adBtn.setOnClickListener { + AdClicksHelper.addClick(adDetails.id) + startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(url)) + ) + } + } + } + } + } + } + private fun setObservers() { viewModel.audioBookLiveData.observe(this) { when (it) { @@ -275,6 +326,8 @@ class AudioBooksActivity : WokaBaseActivity() { } private fun loadTrailerData(audioBookData: AudioBookData) { + if (adLoaded) return + binding.apply { trailerView.show() adjustTrailerImage() diff --git a/app/src/main/java/com/woka/database/AppDatabase.kt b/app/src/main/java/com/woka/database/AppDatabase.kt index 1c53ff8..32ffba1 100644 --- a/app/src/main/java/com/woka/database/AppDatabase.kt +++ b/app/src/main/java/com/woka/database/AppDatabase.kt @@ -4,14 +4,16 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import com.woka.database.dao.AdClickDao import com.woka.database.dao.ClicksDao import com.woka.database.dao.VideoViewDao +import com.woka.database.models.adclicks.AdClickEvent import com.woka.database.models.clicks.ClickEvent import com.woka.database.models.videoview.VideoViewEvent private const val DATABASE_NAME = "woka_local_db" -@Database(entities = [ClickEvent::class, VideoViewEvent::class], version = 1) +@Database(entities = [ClickEvent::class, VideoViewEvent::class, AdClickEvent::class], version = 1) abstract class AppDatabase : RoomDatabase(){ companion object{ @@ -35,4 +37,6 @@ abstract class AppDatabase : RoomDatabase(){ abstract fun clicksDao(): ClicksDao abstract fun videoViewDao(): VideoViewDao + + abstract fun adClicksDao(): AdClickDao } \ No newline at end of file diff --git a/app/src/main/java/com/woka/database/dao/AdClickDao.kt b/app/src/main/java/com/woka/database/dao/AdClickDao.kt new file mode 100644 index 0000000..5400585 --- /dev/null +++ b/app/src/main/java/com/woka/database/dao/AdClickDao.kt @@ -0,0 +1,43 @@ +package com.woka.database.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.woka.database.models.adclicks.AdClickEvent +import com.woka.database.models.clicks.ClickEvent + +@Dao +interface AdClickDao { + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(vararg adClickEvent: AdClickEvent) + + @Query("UPDATE ad_click_events " + + "SET no_of_click = no_of_click + :clicksCount, " + + "no_of_impression = no_of_impression + :impressionCount " + + "WHERE ad_id = :adId") + suspend fun updateAndGetRowCount(adId: Int, clicksCount: Int, impressionCount: Int): Int + + @Transaction + suspend fun upsertClickEvent(adId: Int, clicksCount: Int, impressionCount: Int){ + val count = updateAndGetRowCount(adId, clicksCount, impressionCount) + if (count == 0){ + insert( + AdClickEvent( + adId, + clicksCount, + impressionCount + ) + ) + } + } + + @Query("select * from ad_click_events limit :batchSize") + suspend fun getAdClickEventBatch(batchSize: Int): List + + @Delete + suspend fun deleteAdClickEvents(clickEvent: List): Int + +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/database/helpers/AdClicksHelper.kt b/app/src/main/java/com/woka/database/helpers/AdClicksHelper.kt new file mode 100644 index 0000000..4b33b4a --- /dev/null +++ b/app/src/main/java/com/woka/database/helpers/AdClicksHelper.kt @@ -0,0 +1,37 @@ +package com.woka.database.helpers + +import com.woka.WokaApp.Companion.appDatabase +import com.woka.database.dao.AdClickDao +import com.woka.database.dao.ClicksDao +import com.woka.database.models.ContentType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object AdClicksHelper { + private val adClicksDao: AdClickDao? = appDatabase?.adClicksDao() + + fun addImpression(adId: Int?, impressionCount: Int = 1){ + CoroutineScope(Dispatchers.IO).launch { + if (adId != null){ + adClicksDao?.upsertClickEvent( + adId, + 0, + impressionCount + ) + } + } + } + + fun addClick(adId: Int?, clickCount: Int = 1){ + CoroutineScope(Dispatchers.IO).launch { + if (adId != null){ + adClicksDao?.upsertClickEvent( + adId, + clickCount, + 0 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/database/helpers/RemoteSync.kt b/app/src/main/java/com/woka/database/helpers/RemoteSync.kt index 3c79852..3623042 100644 --- a/app/src/main/java/com/woka/database/helpers/RemoteSync.kt +++ b/app/src/main/java/com/woka/database/helpers/RemoteSync.kt @@ -12,13 +12,16 @@ private const val SYNC_BATCH_COUNT = 15 object RemoteSync { - private const val TAG = "aditya_RemoteSync" + private const val CLICKS_TAG = "aditya_RemoteSync_clicks" + private const val AD_CLICKS_TAG = "aditya_RemoteSync_ad_clicks" private val clicksDao = appDatabase?.clicksDao() + private val adClickDao = appDatabase?.adClicksDao() + private val apiService = RetrofitHelper.getRetrofit().create(RemoteApiService::class.java) suspend fun syncClickEvents(){ - Log.d(TAG, "syncClickEvents: CLICK EVENTS SYNC CALLED") + Log.d(CLICKS_TAG, "syncClickEvents: CLICK EVENTS SYNC CALLED") var roundCount = 0 @@ -26,36 +29,77 @@ object RemoteSync { val clicksBatch = clicksDao?.getClickEventBatch(SYNC_BATCH_COUNT) if (clicksDao == null || clicksBatch.isNullOrEmpty()){ - Log.d(TAG, "syncClickEvents: RECEIVED BATCH IS EMPTY") + Log.d(CLICKS_TAG, "syncClickEvents: RECEIVED BATCH IS EMPTY") break } - Log.d(TAG, "syncClickEvents: BATCH COUNT FROM DATABASE ${clicksBatch.size}") + Log.d(CLICKS_TAG, "syncClickEvents: BATCH COUNT FROM DATABASE ${clicksBatch.size}") when (val response = handleApiCall{ apiService.sendClickEvents(clicksBatch.map { RemoteClickEvent(it) })}){ is ApiResult.Error -> { - Log.e(TAG, "syncClickEvents: BATCH SYNC FAILED due to ${response.errorMessage}", response.error) + Log.e(CLICKS_TAG, "syncClickEvents: BATCH SYNC FAILED due to ${response.errorMessage}", response.error) break } is ApiResult.Loading -> {} is ApiResult.Success -> { - Log.d(TAG, "syncClickEvents: BATCH SYNC IS SUCCESSFUL") + Log.d(CLICKS_TAG, "syncClickEvents: BATCH SYNC IS SUCCESSFUL") - Log.d(TAG, "syncClickEvents: DELETING SYNCED DATA FROM LOCAL DATABASE") + Log.d(CLICKS_TAG, "syncClickEvents: DELETING SYNCED DATA FROM LOCAL DATABASE") val deleteCount = clicksDao.deleteClickEvents(clicksBatch) - Log.d(TAG, "syncClickEvents: $deleteCount ENTRIES ARE DELETED FROM LOCAL DATABASE") + Log.d(CLICKS_TAG, "syncClickEvents: $deleteCount ENTRIES ARE DELETED FROM LOCAL DATABASE") if (deleteCount <= 0){ - Log.e(TAG, "syncClickEvents: NO ENTRIES WERE DELETED FROM LOCAL DATABASE") + Log.e(CLICKS_TAG, "syncClickEvents: NO ENTRIES WERE DELETED FROM LOCAL DATABASE") } roundCount++ } } - Log.d(TAG, "syncClickEvents: \n") + Log.d(CLICKS_TAG, "syncClickEvents: \n") } - Log.d(TAG, "syncClickEvents: NUMBER OF ROUND : $roundCount") + Log.d(CLICKS_TAG, "syncClickEvents: NUMBER OF ROUND : $roundCount") + } + + suspend fun syncAdClickEvents() { + Log.d(AD_CLICKS_TAG, "syncClickEvents: AD CLICK EVENTS SYNC CALLED") + + var roundCount = 0 + + while (true){ + val clicksBatch = adClickDao?.getAdClickEventBatch(SYNC_BATCH_COUNT) + + if (adClickDao == null || clicksBatch.isNullOrEmpty()){ + Log.d(AD_CLICKS_TAG, "syncClickEvents: RECEIVED BATCH IS EMPTY") + break + } + + Log.d(AD_CLICKS_TAG, "syncClickEvents: BATCH COUNT FROM DATABASE ${clicksBatch.size}") + when (val response = handleApiCall{ apiService.updateAdsCount(clicksBatch)}){ + is ApiResult.Error -> { + Log.e(AD_CLICKS_TAG, "syncClickEvents: BATCH SYNC FAILED due to ${response.errorMessage}", response.error) + break + } + is ApiResult.Loading -> {} + is ApiResult.Success -> { + Log.d(AD_CLICKS_TAG, "syncClickEvents: BATCH SYNC IS SUCCESSFUL") + + Log.d(AD_CLICKS_TAG, "syncClickEvents: DELETING SYNCED DATA FROM LOCAL DATABASE") + val deleteCount = adClickDao.deleteAdClickEvents(clicksBatch) + Log.d(AD_CLICKS_TAG, "syncClickEvents: $deleteCount ENTRIES ARE DELETED FROM LOCAL DATABASE") + + if (deleteCount <= 0){ + Log.e(AD_CLICKS_TAG, "syncClickEvents: NO ENTRIES WERE DELETED FROM LOCAL DATABASE") + } + + roundCount++ + } + } + + Log.d(AD_CLICKS_TAG, "syncClickEvents: \n") + } + + Log.d(AD_CLICKS_TAG, "syncClickEvents: NUMBER OF ROUND : $roundCount") } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/database/models/adclicks/AdClickEvent.kt b/app/src/main/java/com/woka/database/models/adclicks/AdClickEvent.kt new file mode 100644 index 0000000..ac192c2 --- /dev/null +++ b/app/src/main/java/com/woka/database/models/adclicks/AdClickEvent.kt @@ -0,0 +1,15 @@ +package com.woka.database.models.adclicks + +import androidx.room.Entity +import androidx.room.PrimaryKey + + +private const val DB_TABLE_NAME = "ad_click_events" + +@Entity(tableName = DB_TABLE_NAME) +data class AdClickEvent( + @PrimaryKey + val ad_id: Int, + val no_of_click: Int?, + val no_of_impression: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/woka/database/remote/RemoteApiService.kt b/app/src/main/java/com/woka/database/remote/RemoteApiService.kt index 9ba0a71..0f8a7ae 100644 --- a/app/src/main/java/com/woka/database/remote/RemoteApiService.kt +++ b/app/src/main/java/com/woka/database/remote/RemoteApiService.kt @@ -1,5 +1,6 @@ package com.woka.database.remote +import com.woka.database.models.adclicks.AdClickEvent import com.woka.database.remote.models.RemoteClickEvent import com.woka.networking.ApiResponse import retrofit2.Response @@ -10,4 +11,7 @@ interface RemoteApiService { @POST("v2/user_clicks") suspend fun sendClickEvents(@Body clickEventList: List): Response> + + @POST("update_ad_count") + suspend fun updateAdsCount(@Body adClickEvents: List): Response> } \ No newline at end of file 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 c741ed9..f69d330 100644 --- a/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt +++ b/app/src/main/java/com/woka/home/fragments/Home1Fragment.kt @@ -17,10 +17,14 @@ import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.woka.R import com.woka.WokaApp.Companion.userPrefs +import com.woka.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage import com.woka.audiobooks.views.AudioBooksActivity +import com.woka.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.FragmentHome1Binding @@ -40,9 +44,9 @@ import com.woka.utils.changeLocale import com.woka.utils.hide import com.woka.utils.scaleAnimate import com.woka.utils.show -import com.woka.utils.toast import com.woka.webseries.views.WebSeriesActivity import com.woka.wokagames.views.GamesActivity +import kotlinx.coroutines.launch class Home1Fragment : Fragment() { @@ -62,6 +66,7 @@ class Home1Fragment : Fragment() { private lateinit var progressView: ProgressView private val clickHelper = ClicksHelper + private val adClickHelper = AdClicksHelper override fun onAttach(context: Context) { super.onAttach(context) @@ -88,6 +93,8 @@ class Home1Fragment : Fragment() { clickEvents() + loadAds() + return binding.root } @@ -219,12 +226,6 @@ class Home1Fragment : Fragment() { liveTv.performClick() } - adView.setOnClickListener { - startActivity( - Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com/")) - ) - } - more.setOnClickListener { activity?.let { startActivity( @@ -522,6 +523,36 @@ class Home1Fragment : Fragment() { } } + private fun loadAds(){ + lifecycleScope.launch { + AdsRepository.getADs()?.let {ads -> + val homeAds = ads.filter { it.for_page == ForPage.THEME_1.value } + + if (homeAds.isNotEmpty()){ + val adDetails = homeAds.first() + + adClickHelper.addImpression(adDetails.id) + + adDetails.banner_image?.let {url -> + binding.imgAd.loadImage(url) + binding.adView.show() + } + + adDetails.ad_link?.let {url -> + binding.adView.setOnClickListener { + + adClickHelper.addClick(adDetails.id) + + startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(url)) + ) + } + } + } + } + } + } + companion object { fun getInstance() = Home1Fragment() } diff --git a/app/src/main/java/com/woka/home/viewmodels/HomeViewModel.kt b/app/src/main/java/com/woka/home/viewmodels/HomeViewModel.kt index 14ca837..58b19ec 100644 --- a/app/src/main/java/com/woka/home/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/woka/home/viewmodels/HomeViewModel.kt @@ -110,6 +110,7 @@ class HomeViewModel : ViewModel() { viewModelScope.launch { _logoutLiveData.postValue(ApiResult.Loading()) RemoteSync.syncClickEvents() + RemoteSync.syncAdClickEvents() _logoutLiveData.postValue(UserRepository.logout()) } } 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 0574878..3a1ad44 100644 --- a/app/src/main/java/com/woka/home/views/HomeActivity.kt +++ b/app/src/main/java/com/woka/home/views/HomeActivity.kt @@ -15,6 +15,7 @@ import android.net.Network import android.net.NetworkRequest import android.os.Build import android.os.Bundle +import android.util.Log import android.view.HapticFeedbackConstants import android.view.View.GONE import android.view.View.VISIBLE @@ -165,7 +166,7 @@ class HomeActivity : WokaBaseActivity(), decisionDialog.setPositiveButton(getString(R.string.yes)){ lifecycleScope.launch { RemoteSync.syncClickEvents() - + RemoteSync.syncAdClickEvents() @Suppress("DEPRECATION") super.onBackPressed() } 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 cc6d41a..5b44ddc 100644 --- a/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt +++ b/app/src/main/java/com/woka/karaoke/views/KaraokeActivity.kt @@ -6,6 +6,7 @@ import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.InsetDrawable +import android.net.Uri import android.os.Bundle import android.text.Html import android.view.WindowManager @@ -15,10 +16,15 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.SimpleItemAnimator +import com.bumptech.glide.Glide import com.google.android.material.appbar.CollapsingToolbarLayout import com.woka.R import com.woka.WokaApp.Companion.userPrefs +import com.woka.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage +import com.woka.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.ActivityKaraokeBinding @@ -41,6 +47,7 @@ import com.woka.utils.setVisibility import com.woka.utils.shareWokaApp import com.woka.utils.show import com.woka.utils.toast +import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -62,6 +69,8 @@ class KaraokeActivity : WokaBaseActivity() { private lateinit var playerLauncher: ActivityResultLauncher + private var adLoaded = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityKaraokeBinding.inflate(layoutInflater) @@ -103,6 +112,8 @@ class KaraokeActivity : WokaBaseActivity() { setObservers() + loadAds() + if (!viewModel.karaokeLiveData.isInitialized) { viewModel.loadKaraokeSongs() } @@ -192,6 +203,8 @@ class KaraokeActivity : WokaBaseActivity() { } private fun loadTrailerData(karaokeData: KaraokeData) { + if (adLoaded) return + binding.apply { trailerView.show() adjustTrailerImage() @@ -542,4 +555,45 @@ class KaraokeActivity : WokaBaseActivity() { } } } + + private fun loadAds(){ + lifecycleScope.launch { + AdsRepository.getADs()?.let { ads -> + val theme1ads = ads.filter { it.for_page == ForPage.KARAOKE.value} + + if (theme1ads.isNotEmpty()){ + val adDetails = theme1ads.first() + + AdClicksHelper.addImpression(adDetails.id) + + adLoaded = true + + binding.trailerBtn.hide() + binding.trailerName.hide() + + binding.adButtonView.show() + + binding.trailerView.show() + adjustTrailerImage() + + adDetails.banner_image?.let {url -> + binding.trailerImage.loadImage(url) + } + + adDetails.button_image?.let {url -> + binding.adButtonImg.loadImage(url) + } + + adDetails.ad_link?.let {url -> + binding.adBtn.setOnClickListener { + AdClicksHelper.addClick(adDetails.id) + startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(url)) + ) + } + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment1.kt b/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment1.kt index 52ad2e2..0d08a9a 100644 --- a/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment1.kt +++ b/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment1.kt @@ -1,13 +1,19 @@ package com.woka.shop.views.fragments.shop +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.woka.R +import com.woka.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage +import com.woka.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.FragmentShop1Binding @@ -17,6 +23,7 @@ import com.woka.shop.models.BaseCategory import com.woka.shop.viewmodels.ShopViewModel import com.woka.utils.hide import com.woka.utils.show +import kotlinx.coroutines.launch class ShopFragment1 : Fragment() { @@ -24,6 +31,9 @@ class ShopFragment1 : Fragment() { private lateinit var viewModel: ShopViewModel private lateinit var adapter: CategoryAdapter + private var adId: Int? = null + private var adLink: String? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -67,9 +77,25 @@ class ShopFragment1 : Fragment() { ClicksHelper.upsertClickEvent(ContentType.OTHERS) } - adapter.onCategoryClickListener = {id, _ -> - findNavController().navigate(ShopFragment1Directions.actionShopFragment1ToShopFragment2(id)) - ClicksHelper.upsertClickEvent(ContentType.SHOP_PRODUCT, id) + adapter.onCategoryClickListener = { id, _ -> + if (id == -1){ + // ad clicked + AdClicksHelper.addClick(adId) + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(adLink) + ) + ) + }else{ + // normal click + findNavController().navigate( + ShopFragment1Directions.actionShopFragment1ToShopFragment2( + id + ) + ) + ClicksHelper.upsertClickEvent(ContentType.SHOP_PRODUCT, id) + } } } } @@ -92,16 +118,19 @@ class ShopFragment1 : Fragment() { is ApiResult.Success -> { it.data?.let { categoryList -> - adapter.submitList(categoryList.map { category -> + val baseCategoryList = categoryList.map { category -> BaseCategory( category.id, category.super_category_thumbnail, category.super_category_name ) - } - ) { + }.toMutableList() + + adapter.submitList(baseCategoryList) { rvSuperCategory.show() + loadAds(baseCategoryList) + shimmer.hide() errorView.hide() } @@ -112,8 +141,23 @@ class ShopFragment1 : Fragment() { } } - companion object { - @JvmStatic - fun newInstance() = ShopFragment1() + private fun loadAds(categoryList: MutableList) { + lifecycleScope.launch { + AdsRepository.getADs()?.let { ads -> + val superShopAds = ads.filter { it.for_page == ForPage.SHOP_SUPER_CATEGORY.value } + + if (superShopAds.isNotEmpty()) { + val adDetails = superShopAds.first() + + AdClicksHelper.addImpression(adDetails.id) + adId = adDetails.id + + categoryList.add(0, BaseCategory(-1, adDetails.banner_image, adDetails.title)) + adLink = adDetails.ad_link + + adapter.notifyItemInserted(0) + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/userPreference/UserPreference.kt b/app/src/main/java/com/woka/userPreference/UserPreference.kt index bc441af..7752024 100644 --- a/app/src/main/java/com/woka/userPreference/UserPreference.kt +++ b/app/src/main/java/com/woka/userPreference/UserPreference.kt @@ -8,13 +8,17 @@ import android.content.SharedPreferences import android.provider.Settings.Secure import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.woka.R import com.woka.home.models.Theme import com.woka.home.mylist.MyListRepository import com.woka.networking.ApiResult import com.woka.onboard.views.OnboardActivity +import com.woka.shop.ShopRepository import com.woka.userdata.UserRepository import com.woka.userdata.userDataModels.UserData import com.woka.userdata.userDataModels.UserDataResponse +import com.woka.utils.DecisionDialog +import com.woka.utils.SessionExpiredActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -108,8 +112,8 @@ class UserPreference(val context: Context) { _userLiveData.postValue(null) if (openLoginScreen){ - context.startActivity(Intent(context, OnboardActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + context.startActivity(Intent(context, SessionExpiredActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) } @@ -118,5 +122,6 @@ class UserPreference(val context: Context) { private fun clearData(){ MyListRepository.clearData() + ShopRepository.clearCart() } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/utils/AdiImageView.kt b/app/src/main/java/com/woka/utils/AdiImageView.kt index 50afa15..40cfad6 100644 --- a/app/src/main/java/com/woka/utils/AdiImageView.kt +++ b/app/src/main/java/com/woka/utils/AdiImageView.kt @@ -29,9 +29,6 @@ class AdiImageView: FrameLayout { private var progressView: ProgressBar? = null private var cardView: CardView? = null - // variables - var onLoadSuccessListener: (() -> Unit)? = null - constructor(context: Context?) : super(context!!) constructor(context: Context?, attrs: AttributeSet?) : super( context!!, attrs @@ -69,7 +66,7 @@ class AdiImageView: FrameLayout { } - fun loadImage(url: String?) { + fun loadImage(url: String?, successListener: (() -> Unit)? = null) { imageView?.let { progressView?.show() @@ -96,7 +93,7 @@ class AdiImageView: FrameLayout { isFirstResource: Boolean ): Boolean { progressView?.hide() - onLoadSuccessListener?.invoke() + successListener?.invoke() return false } }) diff --git a/app/src/main/java/com/woka/utils/DecisionDialog.kt b/app/src/main/java/com/woka/utils/DecisionDialog.kt index a73d3cc..f2cd238 100644 --- a/app/src/main/java/com/woka/utils/DecisionDialog.kt +++ b/app/src/main/java/com/woka/utils/DecisionDialog.kt @@ -83,6 +83,10 @@ class DecisionDialog(context: Context) { } } + fun setCancelable(cancelable: Boolean){ + dialog.setCancelable(cancelable) + } + fun show(titleTxt: String, messageTxt: String): DecisionDialog{ binding.apply { title.text = titleTxt diff --git a/app/src/main/java/com/woka/utils/SessionExpiredActivity.kt b/app/src/main/java/com/woka/utils/SessionExpiredActivity.kt new file mode 100644 index 0000000..0e6c2f2 --- /dev/null +++ b/app/src/main/java/com/woka/utils/SessionExpiredActivity.kt @@ -0,0 +1,36 @@ +package com.woka.utils + +import android.content.Intent +import android.os.Bundle +import androidx.activity.addCallback +import androidx.activity.enableEdgeToEdge +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.woka.R +import com.woka.onboard.views.OnboardActivity + +class SessionExpiredActivity : WokaBaseActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContentView(R.layout.activity_session_expired) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + val decisionDialog = DecisionDialog(this) + decisionDialog.setCancelable(false) + + decisionDialog.setPositiveButton(getString(R.string.login)){ + startActivity(Intent(this, OnboardActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + }) + } + + decisionDialog.show(getString(R.string.session_expired), getString(R.string.please_login_again)) + + onBackPressedDispatcher.addCallback{} + } +} \ 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 index 167f615..ecdc20a 100644 --- a/app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt +++ b/app/src/main/java/com/woka/webseries/views/fragments/WebSeriesFragment.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.InsetDrawable +import android.net.Uri import android.os.Bundle import android.text.Html import android.view.LayoutInflater @@ -17,12 +18,17 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.SimpleItemAnimator +import com.bumptech.glide.Glide 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.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage +import com.woka.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.DialogContinueEpisodeBinding @@ -45,6 +51,7 @@ import com.woka.webseries.adapters.WebSeriesShowAdapter import com.woka.webseries.models.ContinueEpisodeData import com.woka.webseries.models.ShowData import com.woka.webseries.viewmodel.WebSeriesViewModel +import kotlinx.coroutines.launch class WebSeriesFragment : Fragment() { @@ -103,6 +110,8 @@ class WebSeriesFragment : Fragment() { setObservers() + loadAds() + if (!viewModel.showCategoryLiveData.isInitialized) { viewModel.loadCategories() } @@ -379,6 +388,44 @@ class WebSeriesFragment : Fragment() { } } + private fun loadAds(){ + lifecycleScope.launch { + AdsRepository.getADs()?.let { ads -> + val theme1ads = ads.filter { it.for_page == ForPage.WEB_SERIES.value} + + if (theme1ads.isNotEmpty()){ + val adDetails = theme1ads.first() + + AdClicksHelper.addImpression(adDetails.id) + + binding.trailerBtn.hide() + binding.trailerTxt.hide() + + binding.adButtonView.show() + + adDetails.banner_image?.let {url -> + Glide.with(binding.masilaImage) + .load(url) + .into(binding.masilaImage) + } + + adDetails.button_image?.let {url -> + binding.adButtonImg.loadImage(url) + } + + adDetails.ad_link?.let {url -> + binding.adBtn.setOnClickListener { + AdClicksHelper.addClick(adDetails.id) + startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(url)) + ) + } + } + } + } + } + } + private fun onShowClicked(showData: ShowData, categoryId: String) { findNavController().navigate( WebSeriesFragmentDirections.actionWebSeriesFragment2ToWebShowFragment( 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 ee32a4c..a3a3460 100644 --- a/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt +++ b/app/src/main/java/com/woka/wokagames/views/GamesActivity.kt @@ -6,6 +6,7 @@ import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.InsetDrawable +import android.net.Uri import android.os.Bundle import android.text.Html import android.view.WindowManager @@ -13,10 +14,14 @@ import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.SimpleItemAnimator import com.google.android.material.appbar.CollapsingToolbarLayout import com.woka.R import com.woka.WokaApp.Companion.userPrefs +import com.woka.advertisements.AdsRepository +import com.woka.advertisements.models.ForPage +import com.woka.database.helpers.AdClicksHelper import com.woka.database.helpers.ClicksHelper import com.woka.database.models.ContentType import com.woka.databinding.ActivityGamesBinding @@ -38,6 +43,7 @@ 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 kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -55,6 +61,8 @@ class GamesActivity : WokaBaseActivity() { private lateinit var noSignInDialog: NoSignInDialog + private var adLoaded = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityGamesBinding.inflate(layoutInflater) @@ -90,6 +98,8 @@ class GamesActivity : WokaBaseActivity() { setObservers() + loadAds() + if (!viewModel.gamesLiveData.isInitialized){ viewModel.loadGames() } @@ -170,6 +180,8 @@ class GamesActivity : WokaBaseActivity() { } private fun loadTrailerData(gameData: GameData) { + if (adLoaded) return + binding.apply { trailerView.show() adjustTrailerImage() @@ -432,4 +444,45 @@ class GamesActivity : WokaBaseActivity() { } } } + + private fun loadAds(){ + lifecycleScope.launch { + AdsRepository.getADs()?.let { ads -> + val theme1ads = ads.filter { it.for_page == ForPage.GAMES.value} + + if (theme1ads.isNotEmpty()){ + val adDetails = theme1ads.first() + + AdClicksHelper.addImpression(adDetails.id) + + adLoaded = true + + binding.trailerBtn.hide() + binding.trailerName.hide() + + binding.adButtonView.show() + + binding.trailerView.show() + adjustTrailerImage() + + adDetails.banner_image?.let {url -> + binding.trailerImage.loadImage(url) + } + + adDetails.button_image?.let {url -> + binding.adButtonImg.loadImage(url) + } + + adDetails.ad_link?.let {url -> + binding.adBtn.setOnClickListener { + AdClicksHelper.addClick(adDetails.id) + startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(url)) + ) + } + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_audio_books.xml b/app/src/main/res/layout/activity_audio_books.xml index 9d9e10c..c931869 100644 --- a/app/src/main/res/layout/activity_audio_books.xml +++ b/app/src/main/res/layout/activity_audio_books.xml @@ -202,6 +202,31 @@ /> + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_games.xml b/app/src/main/res/layout/activity_games.xml index efc81b1..f44914f 100644 --- a/app/src/main/res/layout/activity_games.xml +++ b/app/src/main/res/layout/activity_games.xml @@ -202,6 +202,31 @@ /> + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_karaoke.xml b/app/src/main/res/layout/activity_karaoke.xml index adfeb30..15c3769 100644 --- a/app/src/main/res/layout/activity_karaoke.xml +++ b/app/src/main/res/layout/activity_karaoke.xml @@ -9,72 +9,66 @@ android:orientation="vertical" tools:context=".webseries.views.WebSeriesActivity"> - + + android:layout_height="match_parent" + android:background="@color/color_primary_dark" + android:visibility="gone"> + app:shimmer_highlight_alpha="0.5"> + android:layout_height="wrap_content"> + android:orientation="vertical" + android:scrollbars="none"> + android:background="@color/white_50" /> + android:background="@color/white_50" /> + android:background="@color/white_50" /> + android:background="@color/white_50" /> + android:layout_marginVertical="15dp" + android:background="@color/white_50" /> @@ -86,20 +80,19 @@ + android:orientation="vertical" + android:visibility="gone"> @@ -127,15 +120,13 @@ + android:background="@color/color_primary_dark"> + android:visibility="gone"> + android:contentDescription="@string/image" + android:scaleType="fitXY" + android:src="@drawable/img_masila_full" + app:layout_collapseMode="parallax" /> + app:layout_collapseMode="pin"> + android:orientation="vertical" + android:padding="15dp"> @@ -190,18 +179,43 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/sing" + android:layout_marginTop="15dp" + android:background="@drawable/play_btn_bg" android:fontFamily="@font/exo_2_bold" + android:paddingHorizontal="25dp" + + android:text="@string/sing" android:textColor="@color/white" + android:textSize="@dimen/_12ssp" - android:paddingHorizontal="25dp" - android:layout_marginTop="15dp" - - android:background="@drawable/play_btn_bg" - /> + + + + + + + + + + @@ -223,91 +237,89 @@ + android:orientation="horizontal" + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/continue_show_view_holder" /> + tools:listitem="@layout/show_view_holder" />