|
@@ -0,0 +1,271 @@
|
|
|
+package com.kdanmobile.reader.adpage
|
|
|
+
|
|
|
+import android.util.SparseBooleanArray
|
|
|
+import com.kdanmobile.kmpdfkit.globaldata.Config
|
|
|
+import com.kdanmobile.kmpdfkit.manager.controller.KMPDFDocumentController
|
|
|
+
|
|
|
+/**
|
|
|
+ * 廣告頁面小幫手
|
|
|
+ * 判斷哪些頁面是廣告、是否該顯示廣告
|
|
|
+ *
|
|
|
+ * <名詞解釋>
|
|
|
+ * 廣告預留頁:在原始文件之間插入的空頁面,只有該頁面可以插入廣告
|
|
|
+ *
|
|
|
+ * 預設在原始文件的每頁之間都插入廣告預留頁,因此每隔一頁都可以插入一個廣告
|
|
|
+ */
|
|
|
+class AdPageHelper {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 一些常數定義
|
|
|
+ * 未完全了解的情況下,強烈不建議修改這些數值
|
|
|
+ */
|
|
|
+ companion object {
|
|
|
+ // 原始文件每隔幾頁是「廣告預留頁」
|
|
|
+ const val DEFAULT_AD_INTERVAL = 1
|
|
|
+ // 第一個廣告頁至少是從原始文件第幾頁開始
|
|
|
+ const val DEFAULT_INIT_AD_INDEX = 1
|
|
|
+ // 至少每隔幾頁(包含預留頁)才可以顯示廣告,預設值4即表示每隔至少原始文件2頁才可以顯示廣告
|
|
|
+ const val DEFAULT_PAGE_INTERVAL = 4
|
|
|
+ // 至少每隔幾秒才可以顯示廣告
|
|
|
+ const val DEFAULT_TIME_INTERVAL = 5
|
|
|
+ // 當前頁面改變後,判斷前後第N頁是否該顯示廣告
|
|
|
+ const val DEFAULT_NEXT_INTERVAL = 3
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 頁間廣告顯示策略
|
|
|
+ * 廣告請求成功後,且符合當前策略時才會顯示廣告
|
|
|
+ *
|
|
|
+ * HIDE:不顯示頁間廣告,行為表現與未使用客製化的ReaderView前完全相同
|
|
|
+ * PAGE_INTERVAL:每隔至少幾頁顯示一個廣告
|
|
|
+ * TIME_INTERVAL:每隔至少幾秒顯示一個廣告
|
|
|
+ * PAGE_OR_TIME_INTERVAL:每隔幾頁或每隔幾秒顯示一個廣告
|
|
|
+ * PAGE_AND_TIME_INTERVAL:每隔幾頁且每隔幾秒顯示一個廣告
|
|
|
+ */
|
|
|
+ enum class DisplayStrategy {
|
|
|
+ HIDE,
|
|
|
+ PAGE_INTERVAL,
|
|
|
+ TIME_INTERVAL,
|
|
|
+ PAGE_OR_TIME_INTERVAL,
|
|
|
+ PAGE_AND_TIME_INTERVAL
|
|
|
+ }
|
|
|
+
|
|
|
+ // 廣告顯示策略
|
|
|
+ var displayStrategy = DisplayStrategy.PAGE_INTERVAL
|
|
|
+
|
|
|
+ // 原始文件每隔幾頁是「廣告預留頁」,該空間可以用來塞入廣告
|
|
|
+ var adInterval = DEFAULT_AD_INTERVAL
|
|
|
+ set(value) {
|
|
|
+ if (field != value) {
|
|
|
+ field = if (value <= 0) 1 else value
|
|
|
+ adVisibleSet.clear()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第一個廣告頁至少是從原始文件第幾頁開始
|
|
|
+ var initAdIndex = DEFAULT_INIT_AD_INDEX
|
|
|
+ set(value) {
|
|
|
+ if (field != value) {
|
|
|
+ field = if (value <= 0) 1 else value
|
|
|
+ adVisibleSet.clear()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 至少每隔幾頁(包含預留頁)才可以顯示廣告
|
|
|
+ var pageInterval = DEFAULT_PAGE_INTERVAL
|
|
|
+ set(value) {
|
|
|
+ if (field != value) {
|
|
|
+ field = if (value <= 0) 1 else value
|
|
|
+ adVisibleSet.clear()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 至少每隔幾秒才可以顯示廣告
|
|
|
+ var timeInterval = DEFAULT_TIME_INTERVAL
|
|
|
+
|
|
|
+ // 當前頁面改變後,判斷前後第N頁是否該顯示廣告
|
|
|
+ var nextInterval = DEFAULT_NEXT_INTERVAL
|
|
|
+ set(value) {
|
|
|
+ field = Math.max(DEFAULT_NEXT_INTERVAL, value)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 廣告最後顯示時間,與「TIME_INTERVAL」相關的顯示策略會使用到此變數
|
|
|
+ */
|
|
|
+ private var lastDisplayTime = System.currentTimeMillis()
|
|
|
+
|
|
|
+ private val adVisibleSet = SparseBooleanArray()
|
|
|
+
|
|
|
+ var kmpdfDocumentController: KMPDFDocumentController? = null
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 請求廣告
|
|
|
+ */
|
|
|
+ var requestAd: (position: Int) -> Unit = {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否該顯示廣告(例:已訂閱則不顯示廣告)
|
|
|
+ */
|
|
|
+ var shouldDisplayAd: () -> Boolean = {
|
|
|
+ defaultShouldDisplayAd()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷廣告是否讀取完畢
|
|
|
+ */
|
|
|
+ var isAdLoaded: (position: Int) -> Boolean = {
|
|
|
+ false
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 預設的是否該顯示廣告判斷
|
|
|
+ * 如果顯示策略為HIDE或當前為水平翻頁則不顯示,反之則顯示廣告
|
|
|
+ */
|
|
|
+ fun defaultShouldDisplayAd(): Boolean {
|
|
|
+ if (displayStrategy == DisplayStrategy.HIDE) return false
|
|
|
+ return isVerticalContinuesViewMode()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷第pageIndex頁是否是廣告預留頁,並且該廣告是否可見
|
|
|
+ */
|
|
|
+ fun isAdVisible(pageIndex: Int): Boolean {
|
|
|
+ return isAdPage(pageIndex) && adVisibleSet[pageIndex]
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷第pageIndex頁是否可顯示廣告
|
|
|
+ * 若是滿足以下任意條件則不可顯示廣告:
|
|
|
+ * .該頁小於起始廣告頁
|
|
|
+ * .該頁不是廣告預留頁
|
|
|
+ * .現在不應顯示廣告
|
|
|
+ * .該頁在可視範圍內(與當前頁差距小於nextInterval頁)
|
|
|
+ * .頁碼超過文件範圍
|
|
|
+ */
|
|
|
+ private fun couldAdDisplayAt(pageIndex: Int): Boolean {
|
|
|
+ return when {
|
|
|
+ pageIndex < initAdIndex -> false
|
|
|
+ !isAdPage(pageIndex) -> false
|
|
|
+ !shouldDisplayAd.invoke() -> false
|
|
|
+ Math.abs(pageIndex - getCurrentPage()) < nextInterval -> false
|
|
|
+ pageIndex < 0 || pageIndex >= getPageCount() -> false
|
|
|
+ else -> true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 當前頁面更新時呼叫此方法
|
|
|
+ * 判斷是否哪幾頁應請求/顯示廣告
|
|
|
+ */
|
|
|
+ fun onPageChanged(pageIndex: Int) {
|
|
|
+ // 若不該顯示廣告則停止執行
|
|
|
+ if (!shouldDisplayAd.invoke()) return
|
|
|
+
|
|
|
+ for (dir in -1 .. 1 step 2) {
|
|
|
+ // 判斷pageIndex的前後第nextInterval頁
|
|
|
+ val targetIndex = pageIndex + nextInterval * dir
|
|
|
+ // 是否應顯示廣告
|
|
|
+ val displayAd = when (displayStrategy) {
|
|
|
+ DisplayStrategy.HIDE -> false
|
|
|
+ DisplayStrategy.PAGE_INTERVAL -> isPageStrategyValid(targetIndex)
|
|
|
+ DisplayStrategy.TIME_INTERVAL -> isTimeStrategyValid()
|
|
|
+ DisplayStrategy.PAGE_OR_TIME_INTERVAL -> isPageStrategyValid(targetIndex) || isTimeStrategyValid()
|
|
|
+ DisplayStrategy.PAGE_AND_TIME_INTERVAL -> isPageStrategyValid(targetIndex) && isTimeStrategyValid()
|
|
|
+ }
|
|
|
+ if (displayAd) {
|
|
|
+ // 該頁廣告是否請求成功
|
|
|
+ if (isAdLoaded.invoke(targetIndex)) {
|
|
|
+ // 若可顯示廣告則直接顯示,並重設最後顯示時間
|
|
|
+ if (couldAdDisplayAt(targetIndex)) {
|
|
|
+ adVisibleSet.put(targetIndex, true)
|
|
|
+ lastDisplayTime = System.currentTimeMillis()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ requestAd.invoke(targetIndex)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷是否符合「PAGE_INTERVAL」顯示策略
|
|
|
+ */
|
|
|
+ private fun isPageStrategyValid(pageIndex: Int): Boolean {
|
|
|
+ for (index in 0 until adVisibleSet.size()) {
|
|
|
+ if (Math.abs(pageIndex - adVisibleSet.keyAt(index)) < pageInterval) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷是否符合「TIME_INTERVAL」顯示策略
|
|
|
+ */
|
|
|
+ private fun isTimeStrategyValid(): Boolean {
|
|
|
+ return System.currentTimeMillis() - lastDisplayTime >= timeInterval * 1000L
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判斷文件第pageIndex頁是否是廣告預留頁
|
|
|
+ */
|
|
|
+ fun isAdPage(pageIndex: Int): Boolean {
|
|
|
+ if (!shouldDisplayAd.invoke()) return false
|
|
|
+ if (pageIndex < initAdIndex) return false
|
|
|
+ return (pageIndex - initAdIndex) % (adInterval + 1) == 0
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 頁碼轉換,計算原始文件的第rawPageIndex頁在插入廣告預留頁後是第幾頁
|
|
|
+ */
|
|
|
+ fun convertToPageIndex(rawPageIndex: Int): Int {
|
|
|
+ if (!shouldDisplayAd.invoke()) return rawPageIndex
|
|
|
+ if (rawPageIndex < initAdIndex) return rawPageIndex
|
|
|
+ return rawPageIndex + 1 + (rawPageIndex - initAdIndex) / adInterval
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 頁碼轉換,計算第pageIndex頁刪除廣告預留頁後是原始文件的第幾頁
|
|
|
+ */
|
|
|
+ fun convertToRawPageIndex(pageIndex: Int): Int {
|
|
|
+ if (!shouldDisplayAd.invoke()) return pageIndex
|
|
|
+ if (pageIndex < initAdIndex) return pageIndex
|
|
|
+ return (pageIndex * adInterval + initAdIndex) / (adInterval + 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取得包含廣告預留頁的總頁數
|
|
|
+ */
|
|
|
+ fun getPageCount(isNativeRefresh: Boolean = false): Int {
|
|
|
+ val rawPageCount = getRawPageCount(isNativeRefresh)
|
|
|
+ if (!shouldDisplayAd.invoke()) return rawPageCount
|
|
|
+ val pageCount = convertToPageIndex(rawPageCount)
|
|
|
+ val lastPage = pageCount - 1
|
|
|
+ // 最後一頁不能是廣告預留頁
|
|
|
+ return when (isAdPage(lastPage)) {
|
|
|
+ true -> pageCount - 1
|
|
|
+ false -> pageCount
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取得原始文件的總頁數
|
|
|
+ */
|
|
|
+ fun getRawPageCount(isNativeRefresh: Boolean = false): Int {
|
|
|
+ return kmpdfDocumentController?.getDocumentPageCount(isNativeRefresh) ?: 0
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取得當前顯示頁面的頁碼
|
|
|
+ */
|
|
|
+ fun getCurrentPage(): Int {
|
|
|
+ return kmpdfDocumentController?.currentPageNum ?: 0
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否是垂直閱覽模式
|
|
|
+ */
|
|
|
+ private fun isVerticalContinuesViewMode(): Boolean {
|
|
|
+ return kmpdfDocumentController?.pdfViewMode == Config.PDFViewMode.VERTICAL_SINGLE_PAGE_CONTINUES
|
|
|
+ }
|
|
|
+}
|