1.添加admob广告

2.添加广告打点
This commit is contained in:
ocean 2025-12-02 11:49:50 +08:00
parent e714a1ef63
commit 905c97e415
29 changed files with 923 additions and 83 deletions

6
.idea/studiobot.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="StudioBotProjectSettings">
<option name="shareContext" value="OptedIn" />
</component>
</project>

View File

@ -26,6 +26,7 @@
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<application
android:name=".PRApp"
@ -40,9 +41,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PDFReaderPro">
<!--正式ID ca-app-pub-5717753826607607~1850604454 -->
<!--google测试ID ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
android:value="ca-app-pub-5717753826607607~1850604454" />
<meta-data
android:name="android.max_aspect"
android:value="2.4" />

View File

@ -46,10 +46,11 @@ class PRApp : Application() {
UpLoadManager.init(context = this, tag = "PRApp_upload_task") { _, _ -> }
// 广告初始化
MobileAds.initialize(this)
initMobileAds()
}
private fun initMobileAds(){
val testDeviceIds = listOf("TEST_DEVICE_ID")
private fun initMobileAds() {
val testDeviceIds = listOf("9A96E667D69B45F744FD7D724DF8B093")
val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build()
MobileAds.setRequestConfiguration(configuration)
}

View File

@ -19,48 +19,55 @@ class AdInstLoad(
}
private fun loadAd() {
//多处调用load也不会重复、不影响缓存广告、展示安全
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 开始加载")
// 是否存在缓存广告、展示安全
val cachedAd = InstAdCacheManager.instance.getAdCache(placement)
if (cachedAd != null) {
Log.d("ocean","广告存在缓存,跳过加载,返回成功")
Log.d(AdsInsUtil.Placement.TAG,"广告存在缓存,跳过加载,返回成功")
//缓存广告有效,跳过加载,返回成功
adLoadListener?.loaded(cachedAd)
return
}
// 如果正在加载(其他地方已 load 加入等待队列
if (InstAdCacheManager.instance.isLoading(placement)) {
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 正在加载中,加入等待队列")
InstAdCacheManager.instance.addPendingListener(placement, adLoadListener)
return
}
// 本次为真正开始加载 标记 loading并加入当前 listener
InstAdCacheManager.instance.markLoading(placement, adLoadListener)
val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run {
val errorMsg = "No AdUnitId for $placement"
Log.d("ocean","没找到对应的广告ID->$placement")
adLoadListener?.loadFailed(errorMsg)
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.LOAD_FAIL,
null,
errorMsg
)
Log.d(AdsInsUtil.Placement.TAG,"没找到对应的广告ID->$placement")
InstAdCacheManager.instance.notifyFail(placement, errorMsg)
return
}
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ)
Log.d(AdsInsUtil.Placement.TAG,"adUnitId->$adUnitId")
InterstitialAd.load(
activity,
adUnitId,
AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载成功")
InstAdCacheManager.instance.setAdCache(placement, ad)
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED)
adLoadListener?.loaded(ad)
InstAdCacheManager.instance.notifySuccess(placement, ad)
}
override fun onAdFailedToLoad(adError: LoadAdError) {
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载失败")
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.LOAD_FAIL,
adError.code,
adError.message
)
adLoadListener?.loadFailed(adError.toString())
InstAdCacheManager.instance.notifyFail(placement, adError.toString())
}
},
)

View File

@ -1,38 +1,52 @@
package com.all.pdfreader.pdf.reader.ad
import android.app.Activity
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil.AdPlacement
import android.util.Log
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.FullScreenContentCallback
class AdInstShower(
private val activity: Activity,
private val placement: AdPlacement,
private val placement: AdsInsUtil.AdPlacement,
private val showListener: ShowListener?
) {
companion object {
private const val SHOW_INTERVAL = 60_000L // 60秒间隔
private var lastShowTimestamp = 0L
}
init {
showAd()
}
private fun showAd() {
// ======= 新增:全局展示间隔限制 启动页不处理 =======
if(placement != AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME){
val now = System.currentTimeMillis()
if (now - lastShowTimestamp < SHOW_INTERVAL) {
val errorMsg = "广告展示过于频繁, 请等待 ${(SHOW_INTERVAL - (now - lastShowTimestamp)) / 1000}s间隔限制60s"
showListener?.onAdShowFailed(errorMsg)
Log.d(AdsInsUtil.Placement.TAG, "showAd->$errorMsg")
return
}
}
val interstitialAd = InstAdCacheManager.instance.getAdCache(placement)
?: run {
val errorMsg = "InterstitialAd cache is null for place = $placement"
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.SHOW_FAIL,
null,
errorMsg
)
val errorMsg = "插页式广告缓存为空 = $placement"
showListener?.onAdShowFailed(errorMsg)
Log.d(AdsInsUtil.Placement.TAG, "showAd->$errorMsg")
return
}
interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
// ======= 新增:展示成功 记录时间 =======
lastShowTimestamp = System.currentTimeMillis()
Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示成功")
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.SHOW_SUC)
showListener?.onAdShown()
}
@ -52,6 +66,7 @@ class AdInstShower(
)
InstAdCacheManager.instance.remove(placement)
showListener?.onAdShowFailed(adError.toString())
Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示失败->${adError}")
}
override fun onAdClicked() {

View File

@ -5,16 +5,17 @@ import android.app.Activity
object AdsInsUtil {
/** 广告位定义(可扩展) */
enum class AdPlacement(val tag: String) {
SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME), INT_AND_TOPDF(Placement.INT_AND_TOPDF), INT_AND_PDFTOHOME(
Placement.INT_AND_PDFTOHOME
),
INT_AND_MERGE(Placement.INT_AND_MERGE), INT_AND_SPLIT(Placement.INT_AND_SPLIT), NATIVE_AND_EXIT(
Placement.NATIVE_AND_EXIT
),
SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME),
INT_AND_TOPDF(Placement.INT_AND_TOPDF),
INT_AND_PDFTOHOME(Placement.INT_AND_PDFTOHOME),
INT_AND_MERGE(Placement.INT_AND_MERGE),
INT_AND_SPLIT(Placement.INT_AND_SPLIT),
NATIVE_AND_EXIT(Placement.NATIVE_AND_EXIT),
BAN_AND_HOMEPAGE(Placement.BAN_AND_HOMEPAGE)
}
object Placement {
const val TAG = "ocean-ad"
/**
* 启动页插页
*/
@ -53,7 +54,7 @@ object AdsInsUtil {
// 广告位对应的广告ID
val adUnitIdMap: Map<AdPlacement, String> = mapOf(
AdPlacement.SPL_AND_INTO_HOME to "ca-app-pub-5717753826607607/5211991318",
AdPlacement.SPL_AND_INTO_HOME to "ca-app-pub-5717753826607607/9606833210",
AdPlacement.INT_AND_TOPDF to "ca-app-pub-5717753826607607/5308904672",
AdPlacement.INT_AND_PDFTOHOME to "ca-app-pub-5717753826607607/7085128570",
AdPlacement.INT_AND_MERGE to "ca-app-pub-5717753826607607/8928693282",
@ -74,9 +75,33 @@ object AdsInsUtil {
return AdInstLoad(act, adPlacement, loadListener)
}
fun showAd(
act: Activity, adPlacement: AdPlacement
): AdInstShower {
return AdInstShower(act, adPlacement, null)
}
fun showAd(
act: Activity, adPlacement: AdPlacement, listener: ShowListener?
): AdInstShower {
return AdInstShower(act, adPlacement, listener)
}
fun showFinishAd(act: Activity, adPlacement: AdPlacement, onFinish: () -> Unit) {
showAd(
act = act,
adPlacement = adPlacement,
listener = object : ShowListener {
override fun onAdShown() {}
override fun onAdClicked() {}
override fun onAdShowFailed(string: String) {
onFinish()
}
override fun onAdClosed() {
onFinish()
}
}
)
}
}

View File

@ -0,0 +1,132 @@
package com.all.pdfreader.pdf.reader.ad
import android.app.Activity
import android.util.DisplayMetrics
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.LoadAdError
object BannerManager {
private data class BannerInfo(val adView: AdView, val container: FrameLayout, var isLoaded: Boolean = false)
private val bannerMap = mutableMapOf<String, BannerInfo>()
//因为有多个activity来展示这个banner所以我们使用act的名称+广告位做为key
private fun key(placement: AdsInsUtil.AdPlacement, activity: Activity): String {
return "${placement.name}_${activity::class.java.simpleName}"
}
fun loadBanner(
activity: Activity,
container: FrameLayout,
placement: AdsInsUtil.AdPlacement,
onLoaded: (() -> Unit)? = null
) {
val currentKey = key(placement, activity)//得到当前的key
val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run {
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner 未找到广告ID: $placement")
container.visibility = View.GONE
return
}
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 开始加载")
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ)
container.post {
container.visibility = View.GONE
val adSize = getAdSize(activity, container)
// 销毁旧 AdView
bannerMap[currentKey]?.adView?.destroy()
val adView = AdView(activity).apply {
this.adUnitId = adUnitId
setAdSize(adSize)
adListener = object : AdListener() {
override fun onAdLoaded() {
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 加载成功")
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED)
val info = bannerMap[currentKey]
info?.isLoaded = true
// 广告加载成功后显示容器
info?.container?.visibility = View.VISIBLE
info?.container?.alpha = 0f
info?.container?.animate()?.alpha(1f)?.setDuration(300)?.start()
onLoaded?.invoke()
}
override fun onAdFailedToLoad(error: LoadAdError) {
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 加载失败: ${error.message}")
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.LOAD_FAIL,
error.code,
error.message
)
val info = bannerMap[currentKey]
info?.isLoaded = false
info?.container?.visibility = View.GONE
}
}
}
bannerMap[currentKey] = BannerInfo(adView, container)
container.removeAllViews()
container.addView(adView)
adView.loadAd(AdRequest.Builder().build())
}
}
fun onResume(activity: Activity, placement: AdsInsUtil.AdPlacement) {
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 恢复广告")
val info = bannerMap[key(placement, activity)] ?: return
info.adView.resume()
if (info.isLoaded) {
info.container.visibility = View.VISIBLE
info.container.alpha = 0f
info.container.animate().alpha(1f).setDuration(300).start()
} else {
info.container.visibility = View.GONE
}
}
fun onPause(activity: Activity, placement: AdsInsUtil.AdPlacement) {
val info = bannerMap[key(placement, activity)] ?: return
info.container.visibility = View.GONE//暂停后就隐藏,让回调来显示
info.adView.pause()
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 暂停广告")
}
fun onDestroy(activity: Activity, placement: AdsInsUtil.AdPlacement) {
val key = key(placement, activity)
bannerMap[key]?.adView?.destroy()
bannerMap.remove(key)
Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 销毁广告")
}
private fun getAdSize(activity: Activity, container: FrameLayout): AdSize {
val display = activity.windowManager.defaultDisplay
val outMetrics = DisplayMetrics()
display.getMetrics(outMetrics)
var adWidthPixels = container.width.toFloat()
if (adWidthPixels == 0f) adWidthPixels = outMetrics.widthPixels.toFloat()
val density = outMetrics.density
val adWidth = (adWidthPixels / density).toInt()
return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(activity, adWidth)
}
}

View File

@ -3,14 +3,56 @@ package com.all.pdfreader.pdf.reader.ad
import com.google.android.gms.ads.interstitial.InterstitialAd
class InstAdCacheManager {
private val mAdCacheDict: MutableMap<AdsInsUtil.AdPlacement, CachedAd> = mutableMapOf()
// 是否正在加载
private val loadingSet = mutableSetOf<AdsInsUtil.AdPlacement>()
// 等待回调的 listener多个 load 时会加入这里)
private val pendingListeners =
mutableMapOf<AdsInsUtil.AdPlacement, MutableList<LoadListener?>>()
companion object {
val instance: InstAdCacheManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
InstAdCacheManager()
}
}
/** ============= 加载状态控制 ============= */
fun isLoading(place: AdsInsUtil.AdPlacement) = loadingSet.contains(place)
fun markLoading(place: AdsInsUtil.AdPlacement, listener: LoadListener?) {
loadingSet.add(place)
addPendingListener(place, listener)
}
fun addPendingListener(place: AdsInsUtil.AdPlacement, listener: LoadListener?) {
if (listener == null) return
val list = pendingListeners[place] ?: mutableListOf()
if (!list.contains(listener)) {
list.add(listener)
}
pendingListeners[place] = list
}
fun notifySuccess(place: AdsInsUtil.AdPlacement, ad: InterstitialAd) {
pendingListeners[place]?.forEach { it?.loaded(ad) }
pendingListeners.remove(place)
loadingSet.remove(place)
}
fun notifyFail(place: AdsInsUtil.AdPlacement, msg: String) {
pendingListeners[place]?.forEach { it?.loadFailed(msg) }
pendingListeners.remove(place)
loadingSet.remove(place)
}
/** ============= 广告缓存逻辑 ============= */
fun setAdCache(place: AdsInsUtil.AdPlacement, adCache: InterstitialAd) {
mAdCacheDict[place] = CachedAd(adCache)
}
@ -19,7 +61,7 @@ class InstAdCacheManager {
val cached = mAdCacheDict[place]
return if (cached != null && cached.isValid()) cached.ad
else {
mAdCacheDict.remove(place) // 过期广告清理
mAdCacheDict.remove(place) // 清理过期
null
}
}
@ -28,12 +70,14 @@ class InstAdCacheManager {
mAdCacheDict.remove(place)
}
/**
* 包装广告 + 时间
*/
data class CachedAd(
val ad: InterstitialAd,
val loadedAt: Long = System.currentTimeMillis()
) {
// 广告有效期 1 小时
// 广告有效期 1 小时
fun isValid(): Boolean = System.currentTimeMillis() - loadedAt < 3600_000
}
}

View File

@ -0,0 +1,74 @@
package com.all.pdfreader.pdf.reader.ad
import android.app.Activity
import android.content.Context
import android.util.Log
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.NativeAd
object NativeAdCache {
private var cachedAd: NativeAd? = null
fun load(
context: Context,
placement: AdsInsUtil.AdPlacement,
onAdLoaded: (() -> Unit)? = null,
onAdFailedToLoad: (() -> Unit)? = null
) {
if (cachedAd != null) {
onAdLoaded?.invoke()
return
}
val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run {
val errorMsg = "No AdUnitId for $placement"
Log.d(AdsInsUtil.Placement.TAG, "没找到对应的广告ID->$placement")
onAdFailedToLoad?.invoke()
return
}
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ)
val adLoader = AdLoader.Builder(context, adUnitId)
.forNativeAd { ad ->
if (context is Activity) {
if (context.isFinishing || context.isDestroyed) {
ad.destroy()
return@forNativeAd
}
}
cachedAd?.destroy()
cachedAd = ad
onAdLoaded?.invoke()
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) {
cachedAd = null
onAdFailedToLoad?.invoke()
Log.d(AdsInsUtil.Placement.TAG, "$placement 广告加载失败 ${error.message}")
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.LOAD_FAIL,
error.code,
error.message
)
}
override fun onAdLoaded() {
Log.d(AdsInsUtil.Placement.TAG, "$placement 广告加载成功")
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED)
}
})
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
fun getAd(): NativeAd? = cachedAd
fun clear() {
cachedAd?.destroy()
cachedAd = null
}
}

View File

@ -24,33 +24,27 @@ abstract class BaseActivity : AppCompatActivity() {
protected val appStore by lazy { AppStore(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("ocean", "🚀 ${javaClass.simpleName} onCreate")
setupBackPressedCallback()//初始化back事件
}
override fun onStart() {
super.onStart()
Log.d("ocean", "🔄 ${javaClass.simpleName} onStart")
}
override fun onResume() {
super.onResume()
Log.d("ocean", "📱 ${javaClass.simpleName} onResume")
}
override fun onPause() {
super.onPause()
Log.d("ocean", "⏸️ ${javaClass.simpleName} onPause")
}
override fun onStop() {
super.onStop()
Log.d("ocean", "🛑 ${javaClass.simpleName} onStop")
}
override fun onDestroy() {
super.onDestroy()
Log.d("ocean", "💀 ${javaClass.simpleName} onDestroy")
}
protected fun logDebug(message: String) {

View File

@ -3,6 +3,8 @@ package com.all.pdfreader.pdf.reader.ui.act
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
@ -12,10 +14,14 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.ad.BannerManager
import com.all.pdfreader.pdf.reader.ad.NativeAdCache
import com.all.pdfreader.pdf.reader.databinding.ActivityMainBinding
import com.all.pdfreader.pdf.reader.model.FileActionEvent
import com.all.pdfreader.pdf.reader.model.FragmentType
import com.all.pdfreader.pdf.reader.model.PdfPickerSource
import com.all.pdfreader.pdf.reader.ui.dialog.ExitDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.ProgressDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.PromptDialogFragment
@ -33,6 +39,16 @@ import com.all.pdfreader.pdf.reader.util.PdfScanner
import com.all.pdfreader.pdf.reader.util.StoragePermissionHelper
import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel
import com.all.pdfreader.pdf.reader.viewmodel.observeEvent
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.NativeAdOptions
import com.google.android.gms.ads.nativead.NativeAdView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
@ -60,6 +76,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
loadAd()
AnalyticsUtils.logEvent(AnalyticsUtils.Event.HOME_SHOW)
setupDoubleBackExit()
initObserve()
@ -305,9 +322,9 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
}
binding.switchScreenOn.setOnCheckedChangeListener(object : OnCheckedChangeListener {
override fun onCheckedChanged(view: CustomSwitchButton?, isChecked: Boolean) {
if(isChecked){
if (isChecked) {
AnalyticsUtils.logEvent(AnalyticsUtils.Event.KEEP_SCREEN_OPEN)
}else{
} else {
AnalyticsUtils.logEvent(AnalyticsUtils.Event.KEEP_SCREEN_CLOSE)
}
view?.setChecked(isChecked)
@ -414,25 +431,6 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
supportFragmentManager.putFragment(outState, fragmentTag, activeFragment)
}
override fun onResume() {
super.onResume()
logDebug("main onResume")
if (StoragePermissionHelper.hasBasicStoragePermission(this)) {
// 有授权才初始化文件变化监听器
PRApp.getInstance().startFileChangeObserving()
scanningStrategy()
binding.pnLayout.visibility = View.GONE
} else {
binding.pnLayout.visibility = View.VISIBLE
val dialog = PermissionDialogFragment()
//如果之前展示过授权对话框,则不再展示
if (!appStore.isShowPermissionsDialogPrompt) {
dialog.show(supportFragmentManager, TAG)
}
}
updateSelectedNav(activeFragment)
}
private fun scanningStrategy() {
if (StoragePermissionHelper.hasBasicStoragePermission(this)) {
lifecycleScope.launch {
@ -563,22 +561,54 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
}
}
private var lastBackPressedTime: Long = 0
private val EXIT_INTERVAL = 2000L // 2 秒内双击退出
private fun setupDoubleBackExit() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressedTime < EXIT_INTERVAL) {
// 双击退出
ExitDialogFragment(onExitClick = {
isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
} else {
lastBackPressedTime = currentTime
showToast(getString(R.string.press_again_to_exit))
}
}).show(supportFragmentManager, TAG)
}
})
}
private fun loadAd() {
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_TOPDF)
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME)
NativeAdCache.load(this, AdsInsUtil.AdPlacement.NATIVE_AND_EXIT)
BannerManager.loadBanner(this, binding.bannerAdFl, AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
}
override fun onResume() {
super.onResume()
BannerManager.onResume(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
logDebug("main onResume")
if (StoragePermissionHelper.hasBasicStoragePermission(this)) {
// 有授权才初始化文件变化监听器
PRApp.getInstance().startFileChangeObserving()
scanningStrategy()
binding.pnLayout.visibility = View.GONE
} else {
binding.pnLayout.visibility = View.VISIBLE
val dialog = PermissionDialogFragment()
//如果之前展示过授权对话框,则不再展示
if (!appStore.isShowPermissionsDialogPrompt) {
dialog.show(supportFragmentManager, TAG)
}
}
updateSelectedNav(activeFragment)
}
override fun onPause() {
BannerManager.onPause(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
super.onPause()
}
override fun onDestroy() {
BannerManager.onDestroy(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
super.onDestroy()
}
}

View File

@ -11,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.databinding.ActivityPdfMergeBinding
import com.all.pdfreader.pdf.reader.model.PdfPickerSource
import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity
@ -41,6 +42,7 @@ class MergePdfActivity : BaseActivity() {
binding = ActivityPdfMergeBinding.inflate(layoutInflater)
setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.MERGE_SHOW)
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_MERGE)
setupImmersionBar {
statusBarView(binding.view)
statusBarDarkFont(true)

View File

@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.databinding.ActivityPdfSplitResultBinding
import com.all.pdfreader.pdf.reader.model.PdfPageItem
import com.all.pdfreader.pdf.reader.model.PdfPickerSource
@ -160,7 +161,11 @@ class PdfResultActivity : BaseActivity() {
}
if (source == PdfPickerSource.SPLIT) {
val splitPassword = intent.getStringExtra(EXTRA_SPLIT_PASSWORD) ?: ""
runOnUiThread {
withContext(Dispatchers.Main) {
AdsInsUtil.showFinishAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_SPLIT){
AdsInsUtil.loadAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_SPLIT)
}
binding.progressBar.isIndeterminate = false
binding.progressBar.progress = 0
binding.progressBar.max = 100
@ -210,7 +215,10 @@ class PdfResultActivity : BaseActivity() {
}
}
} else if (source == PdfPickerSource.MERGE) {
runOnUiThread {
withContext(Dispatchers.Main) {
AdsInsUtil.showFinishAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_MERGE){
AdsInsUtil.loadAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_MERGE)
}
binding.progressBar.isIndeterminate = false
binding.progressBar.progress = 0
binding.progressBar.max = 100

View File

@ -11,13 +11,18 @@ import android.view.inputmethod.EditorInfo
import androidx.activity.OnBackPressedCallback
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.ad.BannerManager
import com.all.pdfreader.pdf.reader.ad.ShowListener
import com.all.pdfreader.pdf.reader.databinding.ActivityPdfViewBinding
import com.all.pdfreader.pdf.reader.model.FileActionEvent
import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity
import com.all.pdfreader.pdf.reader.ui.dialog.BookmarksDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.ListMoreDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.PdfPasswordProtectionDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.ViewModelDialogFragment
import com.all.pdfreader.pdf.reader.ui.view.CustomScrollHandle
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils
@ -27,6 +32,7 @@ import com.all.pdfreader.pdf.reader.util.AppUtils.showKeyboard
import com.all.pdfreader.pdf.reader.util.FileUtils
import com.all.pdfreader.pdf.reader.util.PDFHighlighter
import com.all.pdfreader.pdf.reader.util.PDFSearchManager
import com.all.pdfreader.pdf.reader.util.StoragePermissionHelper
import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel
import com.all.pdfreader.pdf.reader.viewmodel.observeEvent
import com.github.barteksc.pdfviewer.listener.OnErrorListener
@ -48,6 +54,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
override val TAG: String = "PdfViewActivity"
override val rootBottomView: View?
get() = binding.rootBottomLayout
companion object {
const val FRAG_TAG = "PdfViewActivity"
private const val EXTRA_PDF_FILE_PATH = "extra_pdf_file_path"
@ -77,6 +84,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
binding = ActivityPdfViewBinding.inflate(layoutInflater)
setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.PDF_SHOW)
BannerManager.loadBanner(this, binding.bannerAdFl, AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
setupImmersionBar {
statusBarView(binding.view)
statusBarDarkFont(true)
@ -94,6 +102,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
//加载书签数据
viewModel.getBookmarks(filePath)
setupOnClick()
AdsInsUtil.showFinishAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_TOPDF
) {
AdsInsUtil.loadAd(this@PdfViewActivity, AdsInsUtil.AdPlacement.INT_AND_TOPDF)
}
}
private fun initObserve() {
@ -451,6 +466,15 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
setSelection(0)
}
} else {
AdsInsUtil.showFinishAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME
) {
AdsInsUtil.loadAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME
)
}
isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
}
@ -558,10 +582,20 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
}
override fun onDestroy() {
BannerManager.onDestroy(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
super.onDestroy()
if (::searchManager.isInitialized) {
searchManager.closeCachedDocument()
}
}
override fun onResume() {
super.onResume()
BannerManager.onResume( this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
}
override fun onPause() {
BannerManager.onPause(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
super.onPause()
}
}

View File

@ -5,11 +5,16 @@ import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.core.view.doOnPreDraw
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.ad.LoadListener
import com.all.pdfreader.pdf.reader.ad.ShowListener
import com.all.pdfreader.pdf.reader.databinding.ActivitySplashBinding
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.gyf.immersionbar.BarHide
import com.gyf.immersionbar.ImmersionBar
@ -21,7 +26,7 @@ class SplashActivity : BaseActivity() {
private lateinit var binding: ActivitySplashBinding
companion object {
private const val SPLASH_DELAY = 3000L // 启动页显示时长
private const val AD_TIMEOUT = 15000L // 广告加载超时时间 15 秒
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -35,15 +40,62 @@ class SplashActivity : BaseActivity() {
// 设置启动页布局
setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.SPLASH_VISIBLE)
// 延迟跳转到权限检查
Handler(Looper.getMainLooper()).postDelayed({
navigateToNext()
}, SPLASH_DELAY)
loadSplashAd()
}
private val adHandler = Handler(Looper.getMainLooper())
private val adTimeoutRunnable = Runnable {
navigateToNext() // 超时直接跳转
}
private fun loadSplashAd() {
adHandler.postDelayed(adTimeoutRunnable, AD_TIMEOUT)
AdsInsUtil.loadAd(
act = this,
adPlacement = AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME,
loadListener = object : LoadListener {
override fun loaded(ad: InterstitialAd) {
logDebug("loaded 启动页广告加载成功")
adHandler.removeCallbacks(adTimeoutRunnable)
showSplashAd()
}
override fun loadFailed(string: String) {
logDebug("loadFailed->$string")
adHandler.removeCallbacks(adTimeoutRunnable)
navigateToNext() // 加载失败直接跳转
}
})
}
private fun showSplashAd() {
AdsInsUtil.showAd(
act = this,
adPlacement = AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME,
listener = object : ShowListener {
override fun onAdShown() {
// 可以记录日志
}
override fun onAdShowFailed(string: String) {
navigateToNext() // 展示失败也直接跳转
}
override fun onAdClosed() {
navigateToNext() // 广告关闭后跳转
}
})
}
private var hasNavigated = false
//保证只跳转一次
private fun navigateToNext() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
if (hasNavigated) return
hasNavigated = true
startActivity(Intent(this, MainActivity::class.java))
finish()
}
@ -51,4 +103,9 @@ class SplashActivity : BaseActivity() {
override fun onInterceptBackPressed() {
}
override fun onDestroy() {
super.onDestroy()
adHandler.removeCallbacks(adTimeoutRunnable)
}
}

View File

@ -9,6 +9,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.databinding.ActivityPdfSplitBinding
import com.all.pdfreader.pdf.reader.model.PdfPageItem
import com.all.pdfreader.pdf.reader.model.PdfPickerSource
@ -66,6 +67,7 @@ class SplitPdfActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivityPdfSplitBinding.inflate(layoutInflater)
setContentView(binding.root)
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_SPLIT)
setupImmersionBar {
statusBarView(binding.view)
statusBarDarkFont(true)

View File

@ -0,0 +1,171 @@
package com.all.pdfreader.pdf.reader.ui.dialog
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.all.pdfreader.pdf.reader.R
import com.all.pdfreader.pdf.reader.ad.AdsInsUtil
import com.all.pdfreader.pdf.reader.ad.NativeAdCache
import com.all.pdfreader.pdf.reader.databinding.AdAdmobNativeExitLayoutBinding
import com.all.pdfreader.pdf.reader.databinding.DialogExitBinding
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class ExitDialogFragment(
private val onExitClick: () -> Unit,
) : BottomSheetDialogFragment() {
private lateinit var binding: DialogExitBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = DialogExitBinding.inflate(layoutInflater)
return binding.root
}
override fun onStart() {
super.onStart()
dialog?.window?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
?.setBackgroundResource(R.drawable.dr_rc_top_12_bg_white)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupOnClick()
val cached = NativeAdCache.getAd()
if (cached != null) {
Log.d(AdsInsUtil.Placement.TAG, "使用缓存广告")
showNativeAd(cached)
} else {
Log.d(AdsInsUtil.Placement.TAG, "无缓存,开始加载")
loadNativeAd()
}
}
private fun setupOnClick() {
binding.exitBtn.setOnClickListener {
onExitClick()
dismiss()
}
}
private fun showNativeAd(nativeAd: NativeAd) {
if (!isAdded || activity == null) {
nativeAd.destroy()
return
}
if (requireActivity().isFinishing || requireActivity().isDestroyed) {
nativeAd.destroy()
return
}
// Inflate 必须在主线程
val adBinding = AdAdmobNativeExitLayoutBinding.inflate(layoutInflater)
populateNativeAdView(nativeAd, adBinding)
binding.adFl.removeAllViews()
binding.adFl.addView(adBinding.root)
}
private fun loadNativeAd() {
NativeAdCache.load(requireActivity(), AdsInsUtil.AdPlacement.NATIVE_AND_EXIT, onAdLoaded = {
if (!isAdded) return@load
val ad = NativeAdCache.getAd() ?: return@load
showNativeAd(ad)
})
}
private fun populateNativeAdView(
nativeAd: NativeAd,
unifiedAdBinding: AdAdmobNativeExitLayoutBinding
) {
val nativeAdView = unifiedAdBinding.root
// Set the media view.
nativeAdView.mediaView = unifiedAdBinding.adMedia
// Set other ad assets.
nativeAdView.headlineView = unifiedAdBinding.adHeadline
nativeAdView.bodyView = unifiedAdBinding.adBody
nativeAdView.callToActionView = unifiedAdBinding.adCallToAction
nativeAdView.iconView = unifiedAdBinding.adAppIcon
// nativeAdView.priceView = unifiedAdBinding.adPrice
nativeAdView.starRatingView = unifiedAdBinding.adStars
// nativeAdView.storeView = unifiedAdBinding.adStore
nativeAdView.advertiserView = unifiedAdBinding.adAdvertiser
// The headline and media content are guaranteed to be in every UnifiedNativeAd.
unifiedAdBinding.adHeadline.text = nativeAd.headline
nativeAd.mediaContent?.let { unifiedAdBinding.adMedia.setMediaContent(it) }
// These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to
// check before trying to display them.
if (nativeAd.body == null) {
unifiedAdBinding.adBody.visibility = View.INVISIBLE
} else {
unifiedAdBinding.adBody.visibility = View.VISIBLE
unifiedAdBinding.adBody.text = nativeAd.body
}
if (nativeAd.callToAction == null) {
unifiedAdBinding.adCallToAction.visibility = View.INVISIBLE
} else {
unifiedAdBinding.adCallToAction.visibility = View.VISIBLE
unifiedAdBinding.adCallToAction.text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
unifiedAdBinding.adAppIcon.visibility = View.GONE
} else {
unifiedAdBinding.adAppIcon.setImageDrawable(nativeAd.icon?.drawable)
unifiedAdBinding.adAppIcon.visibility = View.VISIBLE
}
// if (nativeAd.price == null) {
// unifiedAdBinding.adPrice.visibility = View.INVISIBLE
// } else {
// unifiedAdBinding.adPrice.visibility = View.VISIBLE
// unifiedAdBinding.adPrice.text = nativeAd.price
// }
//
// if (nativeAd.store == null) {
// unifiedAdBinding.adStore.visibility = View.INVISIBLE
// } else {
// unifiedAdBinding.adStore.visibility = View.VISIBLE
// unifiedAdBinding.adStore.text = nativeAd.store
// }
if (nativeAd.starRating == null) {
unifiedAdBinding.adStars.visibility = View.INVISIBLE
} else {
unifiedAdBinding.adStars.rating = nativeAd.starRating!!.toFloat()
unifiedAdBinding.adStars.visibility = View.VISIBLE
}
if (nativeAd.advertiser == null) {
unifiedAdBinding.adAdvertiser.visibility = View.INVISIBLE
} else {
unifiedAdBinding.adAdvertiser.text = nativeAd.advertiser
unifiedAdBinding.adAdvertiser.visibility = View.VISIBLE
}
// This method tells the Google Mobile Ads SDK that you have finished populating your
// native ad view with this native ad.
nativeAdView.setNativeAd(nativeAd)
}
override fun onDestroyView() {
super.onDestroyView()
// 销毁广告,避免内存泄漏
NativeAdCache.clear()
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
<solid android:color="@color/bg_color" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@color/gnt_white" />
<stroke android:width="2dp" android:color="@color/gnt_outline"/>
</shape>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffffff"/>
<stroke android:width="2dp"
android:color="@color/gnt_ad_green"/>
<padding android:left="1dp"
android:top="1dp"
android:right="1dp"
android:bottom="1dp" />
<corners android:bottomRightRadius="5dp"
android:bottomLeftRadius="5dp"
android:topLeftRadius="5dp"
android:topRightRadius="5dp" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/gnt_white" />
<corners
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
<stroke
android:width="2dp"
android:color="@color/gnt_outline" />
</shape>

View File

@ -8,6 +8,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -455,6 +456,11 @@
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/bannerAdFl"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/rootBottomLayout"
android:layout_width="match_parent"

View File

@ -110,8 +110,8 @@
android:id="@+id/selectNextBtn"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:layout_marginEnd="16dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center">
<ImageView
@ -332,7 +332,13 @@
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/bannerAdFl"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/rootBottomLayout"
android:layout_width="match_parent"

View File

@ -0,0 +1,113 @@
<com.google.android.gms.ads.nativead.NativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minHeight="50dp"
android:orientation="vertical">
<TextView style="@style/AdAttribution" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="3dp"
android:paddingRight="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#0000FF"
android:textSize="16sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="bottom"
android:textSize="14sp"
android:textStyle="bold" />
<RatingBar
android:id="@+id/ad_stars"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.5" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:textSize="12sp" />
<com.google.android.gms.ads.nativead.MediaView
android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="175dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_color"
android:gravity="center"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:orientation="vertical">
<FrameLayout
android:id="@+id/adFl"
android:layout_marginTop="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white" />
<TextView
android:id="@+id/exitBtn"
style="@style/TextViewFont_PopRegular"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginBottom="12dp"
android:gravity="center"
android:text="@string/tap_again_to_exit"
android:textColor="@color/black"
android:textSize="14sp" />
</LinearLayout>

View File

@ -39,4 +39,14 @@
<color name="ok_btn_bg_lv2_color">#FD4E1D</color>
<color name="no_click_btn_bg_lv1_color">#7FE43521</color>
<color name="no_click_btn_bg_lv2_color">#80FD4E1D</color>
<color name="gnt_test_background_color">#00F4F4F4</color>
<color name="gnt_test_background_color_2">#00A3A3A3</color>
<color name="gnt_black">#000000</color>
<color name="gnt_white">#FFFFFF</color>
<color name="gnt_red">#FF0000</color>
<color name="gnt_green">#00FF00</color>
<color name="gnt_blue">#4285f4</color>
<color name="gnt_ad_green">#3A6728</color>
<color name="gnt_gray">#808080</color>
<color name="gnt_outline">#E0E0E0</color>
</resources>

View File

@ -1,4 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dialog_margin">16dp</dimen>
<item name="gnt_medium_template_bottom_weight" format="float" type="dimen">0.4</item>
<item name="gnt_text_row_weight" format="float" type="dimen">0.25</item>
<item name="gnt_media_view_weight" format="float" type="dimen">1.0</item>
<item name="gnt_medium_template_top_weight" format="float" type="dimen">0.2</item>
<dimen name="gnt_default_margin">10dp</dimen>
<dimen name="gnt_small_margin">5dp</dimen>
<dimen name="gnt_text_size_small">12sp</dimen>
<dimen name="gnt_text_size_large">15sp</dimen>
<dimen name="gnt_ad_indicator_width">25dp</dimen>
<dimen name="gnt_ad_indicator_height">20dp</dimen>
<dimen name="gnt_ad_indicator_bar_height">30dp</dimen>
<dimen name="gnt_ad_indicator_top_margin">0dp</dimen>
<dimen name="gnt_ad_indicator_bottom_margin">10dp</dimen>
<dimen name="gnt_ad_indicator_text_size">10sp</dimen>
<dimen name="gnt_small_cta_button_height">30dp</dimen>
<dimen name="gnt_medium_cta_button_height">50dp</dimen>
<dimen name="gnt_no_margin">0dp</dimen>
<dimen name="gnt_no_size">0dp</dimen>
</resources>

View File

@ -195,4 +195,6 @@
<string name="number_image_converted">%1$d imagesF converted</string>
<string name="file_not_pdf_or_corrupted">File not in PDF format or corrupted</string>
<string name="processing_failed">Processing failed</string>
<string name="tap_again_to_exit">Tap again to exit</string>
<string name="ad_attribution" translatable="false">Ad</string>
</resources>

View File

@ -45,4 +45,16 @@
<item name="android:includeFontPadding">false</item>
<item name="android:fontFamily">@font/poppins_semibold</item>
</style>
<style name="AdAttribution">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">left</item>
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">12sp</item>
<item name="android:text">@string/ad_attribution</item>
<item name="android:background">#FFCC66</item>
<item name="android:width">15dp</item>
<item name="android:height">15dp</item>
</style>
</resources>