浏览代码

Add AdPageHelper

cooperku_kdanmobile 5 年之前
父节点
当前提交
b1c584f202
共有 1 个文件被更改,包括 271 次插入0 次删除
  1. 271 0
      src/main/java/com/kdanmobile/reader/adpage/AdPageHelper.kt

+ 271 - 0
src/main/java/com/kdanmobile/reader/adpage/AdPageHelper.kt

@@ -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
+    }
+}