Explorar el Código

Merge branch 'refactorPageAd' into 124-implementAdBetweenPages

cooperku_kdanmobile hace 5 años
padre
commit
0f9595d770

+ 8 - 10
src/main/java/com/kdanmobile/reader/ReaderActivity.kt

@@ -23,7 +23,7 @@ import com.kdanmobile.kmpdfkit.pdfcommon.PDFInfo
 import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
 import com.kdanmobile.kmpdfkit.pdfcommon.*
 import com.kdanmobile.reader.Utils.applyConstraintSet
-import com.kdanmobile.reader.adpage.AbstractPageAdapter
+import com.kdanmobile.reader.additionalpage.*
 import com.kdanmobile.reader.annotationattribute.AnnotationAttribute
 import com.kdanmobile.reader.annotationattribute.AnnotationColor
 import com.kdanmobile.reader.annotationattribute.InkAttribute
@@ -40,8 +40,6 @@ import com.kdanmobile.reader.screen.view.*
 import com.kdanmobile.reader.screen.view.SearchView
 import com.kdanmobile.reader.setting.ReaderSettingDialogFragment
 import com.kdanmobile.reader.setting.ReaderSettingListener
-import com.kdanmobile.reader.adpage.MyReaderView
-import com.kdanmobile.reader.adpage.AdPageHelper
 import com.kdanmobile.reader.thumb.PdfThumbDialogFragment
 import com.kdanmobile.reader.thumb.PdfThumbFragmentListener
 import com.kdanmobile.reader.utils.AnimationUtil
@@ -122,9 +120,9 @@ abstract class ReaderActivity :
         get() {
             return viewModel.readerModel
         }
-    protected val adPageHelper: AdPageHelper
+    protected val additionalPageManager: AdditionalPageManager
         get() {
-            return viewModel.adPageHelper
+            return viewModel.additionalPageManager
         }
 
     private val viewModel: ReaderViewModel by viewModel {
@@ -461,7 +459,7 @@ abstract class ReaderActivity :
         if (!isOpened) return
         val context = this
         //  採用客製化的KMPDFReaderView以覆寫畫面點擊判斷和當前頁面計算方法
-        val readerView = object : MyReaderView(context, adPageHelper) {
+        val readerView = object : MyReaderView(context, additionalPageManager.pageConverter) {
             @SuppressLint("ClickableViewAccessibility")
             override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
                 if (motionEvent.action == MotionEvent.ACTION_UP) {
@@ -477,7 +475,7 @@ abstract class ReaderActivity :
             override fun onMoveToChild(pageIndex: Int) {
                 viewModel.setPageIndex(pageIndex)
                 //  畫面更新時應通知adPageHelper
-                adPageHelper.onPageChanged(pageIndex)
+                additionalPageManager.onPageChanged(pageIndex)
             }
 
             override fun onScrolling() {
@@ -538,11 +536,11 @@ abstract class ReaderActivity :
         onOpenedFile()
     }
 
-    fun removeAdPages() {
-        if (adPageHelper.displayStrategy == AdPageHelper.DisplayStrategy.HIDE) return
+    fun removeAdditionalPages() {
+        if (additionalPageManager.displayStrategyType == DisplayStrategyType.HIDE) return
         val handler = viewModel.pdfInfoHandler
         val pageIndex = currentPageIndex
-        adPageHelper.displayStrategy = AdPageHelper.DisplayStrategy.HIDE
+        additionalPageManager.displayStrategyType = DisplayStrategyType.HIDE
         viewModel.getReaderView()?.refresh(true)
         handler.goToCurrentPage(pageIndex)
     }

+ 9 - 8
src/main/java/com/kdanmobile/reader/ReaderModel.kt

@@ -11,7 +11,7 @@ import com.kdanmobile.kmpdfkit.pdfcommon.OutlineItem
 import com.kdanmobile.kmpdfkit.pdfcommon.TextChar
 import com.kdanmobile.kmpdfkit.pdfcommon.TextWord
 import com.kdanmobile.reader.screen.handler.*
-import com.kdanmobile.reader.adpage.AdPageHelper
+import com.kdanmobile.reader.additionalpage.AdditionalPageManager
 
 class ReaderModel {
     private var filename: String? = null
@@ -19,7 +19,8 @@ class ReaderModel {
     var password: String = ""
         private set
 
-    var adPageHelper: AdPageHelper? = null
+    var additionalPageManager: AdditionalPageManager? = null
+        private set
 
     var kmpdfFactory: KMPDFFactory? = null
         private set
@@ -46,15 +47,15 @@ class ReaderModel {
         this.isInitialized = true
     }
 
-    fun initPdfAdPageHelper(adPageHelper: AdPageHelper?) {
-        this.adPageHelper = adPageHelper
+    fun initAdditionalPageManager(additionalPageManager: AdditionalPageManager?) {
+        this.additionalPageManager = additionalPageManager
     }
 
     fun initKMPDFDocumentController() {
         kmpdfDocumentController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.DOCUMENT) as KMPDFDocumentController
         kmpdfSignatureController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.SIGNATURE) as KMPDFSignatureController
         kmpdfDocumentController?.also {
-            adPageHelper?.kmpdfDocumentController = it
+            additionalPageManager?.pageConverter?.kmpdfDocumentController = it
         }
     }
 
@@ -68,7 +69,7 @@ class ReaderModel {
         kmpdfDocumentController = null
         kmpdfSignatureController = null
         onPdfChangedListener = null
-        adPageHelper = null
+        additionalPageManager = null
     }
 
     val pdfInfoHandler = object : PdfInfoHandler {
@@ -77,7 +78,7 @@ class ReaderModel {
         }
 
         override fun getPdfPageCount(isNativeRefresh: Boolean): Int {
-            return adPageHelper?.getRawPageCount(isNativeRefresh) ?: 0
+            return additionalPageManager?.pageConverter?.getRawPageCount(isNativeRefresh) ?: 0
         }
 
         override fun getCurrentPage(): Int {
@@ -125,7 +126,7 @@ class ReaderModel {
         }
 
         override fun setSearchResult(page: Int, keyword: String, rectArray: Array<RectF>): Boolean {
-            return kmpdfDocumentController?.setSearchResult(keyword, adPageHelper?.convertToPageIndex(page) ?: 0, rectArray) ?: false
+            return kmpdfDocumentController?.setSearchResult(keyword, additionalPageManager?.pageConverter?.convertToPageIndex(page) ?: 0, rectArray) ?: false
         }
 
         override fun stopSearchKeyWord(): Boolean {

+ 16 - 8
src/main/java/com/kdanmobile/reader/ReaderViewModel.kt

@@ -33,7 +33,8 @@ import com.kdanmobile.reader.screen.data.SignatureAttribute
 import com.kdanmobile.reader.screen.data.StampAttribute
 import com.kdanmobile.reader.screen.data.TextBoxAttribute
 import com.kdanmobile.reader.screen.handler.*
-import com.kdanmobile.reader.adpage.AdPageHelper
+import com.kdanmobile.reader.additionalpage.AdditionalPageManager
+import com.kdanmobile.reader.additionalpage.AdditionalPageManagerImpl
 import java.io.File
 import java.util.*
 import kotlin.collections.ArrayList
@@ -207,9 +208,16 @@ class ReaderViewModel(
 
     private var onClickLinkListener: OnClickLinkListener? = null
 
-    var adPageHelper = AdPageHelper().also {
-        readerModel.initPdfAdPageHelper(it)
+    var additionalPageManager: AdditionalPageManager = AdditionalPageManagerImpl().also {
+        readerModel.initAdditionalPageManager(it)
     }
+        set(value) {
+            field = value
+            readerModel.initAdditionalPageManager(field)
+            kmpdfDocumentController?.also {
+                field.pageConverter.kmpdfDocumentController = it
+            }
+        }
 
     @JvmOverloads
     fun openPdfFile(context: Context, password: String, onRequestPassword: Runnable, type: String? = null): OpenFileResult {
@@ -542,7 +550,7 @@ class ReaderViewModel(
     }
 
     /**
-     * 之前採用以下方式會遮蔽廣告頁拖曳,原因尚不清楚...
+     * 之前採用以下方式會遮蔽額外頁面拖曳,原因尚不清楚...
      * kmpdfFactory?.setAnnotationEditMode(KMPDFAnnotationBean.AnnotationType.NULL)
      * kmpdfFactory?.kmpdfAnnotEditMode?.pdfAnnotEditMode = KMPDFAnnotEditMode.Mode.NULL
      * annotationEitModeLiveData.postValue(AnnotationEitMode.NULL)
@@ -569,7 +577,7 @@ class ReaderViewModel(
 
     fun setPageIndex(pageIndex: Int) {
         //  設定為原始文件頁數
-        mPageIndexLiveData.value = adPageHelper.convertToRawPageIndex(pageIndex)
+        mPageIndexLiveData.value = additionalPageManager.pageConverter.convertToRawPageIndex(pageIndex)
     }
 
     fun addBookmark(title: String) {
@@ -618,15 +626,15 @@ class ReaderViewModel(
     }
 
     /**
-     * 目前在水平閱覽模式時會移除頁間廣告,導致兩者總頁數不一致
+     * 目前在水平閱覽模式時會移除額外頁面,導致兩者總頁數不一致
      * 更動總頁數需要重新設定當前頁面
      */
     private fun updateViewDirection() {
-        val pageCount = adPageHelper.getPageCount(false)
+        val pageCount = additionalPageManager.pageConverter.getPageCount(false)
         val pageIndex = pdfInfoHandler.getCurrentPage()
         kmpdfDocumentController?.pdfViewMode = viewDirection.mode
         kmpdfDocumentController?.refresh(false)
-        if (pageCount != adPageHelper.getPageCount(false)) {
+        if (pageCount != additionalPageManager.pageConverter.getPageCount(false)) {
             pdfInfoHandler.goToCurrentPage(pageIndex)
         }
     }

+ 13 - 14
src/main/java/com/kdanmobile/reader/adpage/AbstractPageAdapter.kt

@@ -1,43 +1,42 @@
-package com.kdanmobile.reader.adpage
+package com.kdanmobile.reader.additionalpage
 
 import android.content.Context
 import android.graphics.Point
 import android.view.View
 import android.view.ViewGroup
-import android.widget.ImageView
 import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
 import com.kdanmobile.kmpdfkit.pdfcommon.FilePicker
 import com.kdanmobile.kmpdfkit.pdfcommon.KMPDFPageAdapter
-import com.kdanmobile.reader.R
 
 /**
  * 客製化KMPDFPageAdapter
  *
- * 負責創建廣告頁面
+ * 負責創建額外頁面
  */
 abstract class AbstractPageAdapter(
         protected val context: Context,
         private val filePickerSupport: FilePicker.FilePickerSupport,
         private val kmpdfFactory: KMPDFFactory,
-        protected val adPageHelper: AdPageHelper
-): KMPDFPageAdapter(context, filePickerSupport, kmpdfFactory) {
+        protected val additionalPageManager: AdditionalPageManager
+) : KMPDFPageAdapter(context, filePickerSupport, kmpdfFactory) {
+    private val pageConverter = additionalPageManager.pageConverter
 
     /**
-     * 設定廣告內容
+     * 設定額外頁面內容
      * 子類別可覆寫此方法以客製化不同視覺設計
      */
     abstract fun setupViewContent(position: Int, pageView: MyPageView)
 
     /**
-     * 總頁數應包含廣告頁
+     * 總頁數應包含額外頁面
      */
     override fun getCount(): Int {
-        return adPageHelper.getPageCount()
+        return pageConverter.getPageCount()
     }
 
     override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
-        //  判斷頁面是否為廣告頁
-        val isAdPage = adPageHelper.isAdPage(position)
+        //  判斷頁面是否為額外頁面
+        val isAdPage = pageConverter.isAdditionalPage(position)
         //  建立或讀取客製化KMPDFPageView
         val pageView = if (convertView == null) {
             MyPageView(context, filePickerSupport, kmpdfFactory, Point(parent?.width ?: 0, parent?.height ?: 0), isAdPage)
@@ -48,14 +47,14 @@ abstract class AbstractPageAdapter(
             true -> {
                 //  設定KMPDFPageView
                 pageView.apply {
-                    adHeight = MyPageView.DEFAULT_AD_HEIGHT
+                    additionalPageHeight = MyPageView.DEFAULT_ADDITIONAL_PAGE_HEIGHT
                     setupViewContent(position, this)
-                    isAdShowing = adPageHelper.isAdVisible(position)
+                    isAdditionalPageShowing = additionalPageManager.isAdditionalPageVisible(position)
                 }
             }
             false -> {
                 //  取得文件原始頁數
-                val page = adPageHelper.convertToRawPageIndex(position)
+                val page = pageConverter.convertToRawPageIndex(position)
                 super.getView(page, pageView, parent)
             }
         }

+ 52 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageConverter.kt

@@ -0,0 +1,52 @@
+package com.kdanmobile.reader.additionalpage
+
+import com.kdanmobile.kmpdfkit.manager.controller.KMPDFDocumentController
+
+/**
+ * 額外頁面頁碼轉換器
+ * 以及判斷哪些頁面是額外頁面
+ *
+ * <名詞解釋>
+ *     額外頁面:在原始文件之間插入的空頁面,之後可在其上插入其他內容(例如廣告)
+ *
+ * 預設在原始文件的每頁之間都插入額外頁面,因此每隔一頁都可以插入一個廣告
+ */
+interface AdditionalPageConverter {
+
+    var kmpdfDocumentController: KMPDFDocumentController?
+
+    /**
+     * 是否啟用額外頁面(例:已訂閱則不顯示)
+     */
+    var isAdditionalPageEnabled: () -> Boolean
+
+    /**
+     * 判斷文件第pageIndex頁是否是額外頁面
+     */
+    fun isAdditionalPage(pageIndex: Int): Boolean
+
+    /**
+     * 頁碼轉換,計算原始文件的第rawPageIndex頁在插入額外頁面後是第幾頁
+     */
+    fun convertToPageIndex(rawPageIndex: Int): Int
+
+    /**
+     * 頁碼轉換,計算第pageIndex頁刪除額外頁面後是原始文件的第幾頁
+     */
+    fun convertToRawPageIndex(pageIndex: Int): Int
+
+    /**
+     * 取得包含額外頁面的總頁數
+     */
+    fun getPageCount(isNativeRefresh: Boolean = false): Int
+
+    /**
+     * 取得原始文件的總頁數
+     */
+    fun getRawPageCount(isNativeRefresh: Boolean = false): Int
+
+    /**
+     * 取得當前顯示頁面的頁碼
+     */
+    fun getCurrentPage(): Int
+}

+ 107 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageConverterImpl.kt

@@ -0,0 +1,107 @@
+package com.kdanmobile.reader.additionalpage
+
+import androidx.annotation.IntRange
+import com.kdanmobile.kmpdfkit.globaldata.Config
+import com.kdanmobile.kmpdfkit.manager.controller.KMPDFDocumentController
+
+/**
+ * 額外頁面頁碼轉換器
+ * 以及判斷哪些頁面是額外頁面
+ *
+ * <名詞解釋>
+ *     額外頁面:在原始文件之間插入的空頁面,之後可在其上插入其他內容(例如廣告)
+ *
+ * 預設在原始文件的每頁之間都插入額外頁面,因此每隔一頁都可以插入一個廣告
+ */
+class AdditionalPageConverterImpl(
+        //  原始文件每隔幾頁是「額外頁面」
+        @IntRange(from = 1) private val additionalPageInterval: Int = DEFAULT_ADDITIONAL_PAGE_INTERVAL,
+        //  第一個額外頁面至少是從原始文件第幾頁開始
+        @IntRange(from = 1) private val firstAdditionalPageIndex: Int = DEFAULT_FIRST_ADDITIONAL_PAGE_INDEX
+) : AdditionalPageConverter {
+
+    companion object {
+        //  原始文件每隔幾頁是「額外頁面」
+        const val DEFAULT_ADDITIONAL_PAGE_INTERVAL = 1
+        //  第一個額外頁面至少是從原始文件第幾頁開始
+        const val DEFAULT_FIRST_ADDITIONAL_PAGE_INDEX = 1
+    }
+
+    override var kmpdfDocumentController: KMPDFDocumentController? = null
+
+    /**
+     * 是否啟用額外頁面(例:已訂閱則不顯示)
+     */
+    override var isAdditionalPageEnabled: () -> Boolean = { true }
+
+    /**
+     * 判斷文件第pageIndex頁是否是額外頁面
+     */
+    override fun isAdditionalPage(pageIndex: Int): Boolean {
+        if (!shouldApplyAdditionalPage()) return false
+        if (pageIndex < firstAdditionalPageIndex) return false
+        return (pageIndex - firstAdditionalPageIndex) % (additionalPageInterval + 1) == 0
+    }
+
+    /**
+     * 頁碼轉換,計算原始文件的第rawPageIndex頁在插入額外頁面後是第幾頁
+     */
+    override fun convertToPageIndex(rawPageIndex: Int): Int {
+        if (!shouldApplyAdditionalPage()) return rawPageIndex
+        if (rawPageIndex < firstAdditionalPageIndex) return rawPageIndex
+        return rawPageIndex + 1 + (rawPageIndex - firstAdditionalPageIndex) / additionalPageInterval
+    }
+
+    /**
+     * 頁碼轉換,計算第pageIndex頁刪除額外頁面後是原始文件的第幾頁
+     */
+    override fun convertToRawPageIndex(pageIndex: Int): Int {
+        if (!shouldApplyAdditionalPage()) return pageIndex
+        if (pageIndex < firstAdditionalPageIndex) return pageIndex
+        return (pageIndex * additionalPageInterval + firstAdditionalPageIndex) / (additionalPageInterval + 1)
+    }
+
+    /**
+     * 取得包含額外頁面的總頁數
+     */
+    override fun getPageCount(isNativeRefresh: Boolean): Int {
+        val rawPageCount = getRawPageCount(isNativeRefresh)
+        if (!shouldApplyAdditionalPage()) return rawPageCount
+        val pageCount = convertToPageIndex(rawPageCount)
+        val lastPage = pageCount - 1
+        //  最後一頁不能是額外頁面
+        return when (isAdditionalPage(lastPage)) {
+            true -> pageCount - 1
+            false -> pageCount
+        }
+    }
+
+    /**
+     * 取得原始文件的總頁數
+     */
+    override fun getRawPageCount(isNativeRefresh: Boolean): Int {
+        return kmpdfDocumentController?.getDocumentPageCount(isNativeRefresh) ?: 0
+    }
+
+    /**
+     * 取得當前顯示頁面的頁碼
+     */
+    override fun getCurrentPage(): Int {
+        return kmpdfDocumentController?.currentPageNum ?: 0
+    }
+
+    /**
+     * 是否套用額外頁面(非垂直閱覽模式則不套用)
+     */
+
+    private fun shouldApplyAdditionalPage(): Boolean {
+        return isAdditionalPageEnabled.invoke() && isVerticalContinuesViewMode()
+    }
+
+    /**
+     * 是否是垂直閱覽模式
+     */
+    private fun isVerticalContinuesViewMode(): Boolean {
+        return kmpdfDocumentController?.pdfViewMode == Config.PDFViewMode.VERTICAL_SINGLE_PAGE_CONTINUES
+    }
+}

+ 23 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageDisplayStrategy.kt

@@ -0,0 +1,23 @@
+package com.kdanmobile.reader.additionalpage
+
+import android.util.SparseBooleanArray
+
+/**
+ * 額外頁面顯示策略
+ * 額外頁面創建成功後,需符合當前顯示策略類型時才會顯示該額外頁面
+ */
+interface AdditionalPageDisplayStrategy {
+
+    //  額外頁面顯示策略類型
+    var displayStrategyType: DisplayStrategyType
+
+    /**
+     * 判斷第pageIndex頁是否能顯示額外頁面
+     */
+    fun canDisplayAdditionalPage(additionalPageVisibleSet: SparseBooleanArray, pageIndex: Int): Boolean
+
+    /**
+     * 更新最後顯示時間
+     */
+    fun updateDisplayTime()
+}

+ 69 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageDisplayStrategyImpl.kt

@@ -0,0 +1,69 @@
+package com.kdanmobile.reader.additionalpage
+
+import android.util.SparseBooleanArray
+import androidx.annotation.IntRange
+import kotlin.math.abs
+
+/**
+ * 額外頁面顯示策略
+ * 額外頁面創建成功後,需符合當前顯示策略類型時才會顯示該額外頁面
+ */
+class AdditionalPageDisplayStrategyImpl(
+        override var displayStrategyType: DisplayStrategyType = DisplayStrategyType.PAGE_INTERVAL,
+        //  至少每隔幾頁(包含額外頁面)才可以顯示廣告
+        @IntRange(from = 4) private val pageInterval: Int = DEFAULT_PAGE_INTERVAL,
+        //  至少每隔幾秒才可以顯示廣告
+        @IntRange(from = 5) private val timeInterval: Int = DEFAULT_TIME_INTERVAL
+) : AdditionalPageDisplayStrategy {
+
+    companion object {
+        //  至少每隔幾頁(包含額外頁面)才可以顯示廣告,預設值4即表示每隔至少原始文件2頁才可以顯示廣告
+        const val DEFAULT_PAGE_INTERVAL = 4
+        //  至少每隔幾秒才可以顯示廣告
+        const val DEFAULT_TIME_INTERVAL = 5
+    }
+
+    /**
+     * 額外頁面最後顯示時間,與「TIME_INTERVAL」相關的顯示策略會使用到此變數
+     */
+    private var lastDisplayTime = System.currentTimeMillis()
+
+    /**
+     * 判斷第pageIndex頁是否能顯示額外頁面
+     */
+    override fun canDisplayAdditionalPage(additionalPageVisibleSet: SparseBooleanArray, pageIndex: Int): Boolean {
+        return when (displayStrategyType) {
+            DisplayStrategyType.HIDE -> false
+            DisplayStrategyType.PAGE_INTERVAL -> isPageStrategyValid(additionalPageVisibleSet, pageIndex)
+            DisplayStrategyType.TIME_INTERVAL -> isTimeStrategyValid()
+            DisplayStrategyType.PAGE_OR_TIME_INTERVAL -> isPageStrategyValid(additionalPageVisibleSet, pageIndex) || isTimeStrategyValid()
+            DisplayStrategyType.PAGE_AND_TIME_INTERVAL -> isPageStrategyValid(additionalPageVisibleSet, pageIndex) && isTimeStrategyValid()
+        }
+    }
+
+    /**
+     * 判斷是否符合「PAGE_INTERVAL」顯示策略
+     */
+    private fun isPageStrategyValid(additionalPageVisibleSet: SparseBooleanArray, pageIndex: Int): Boolean {
+        for (index in 0 until additionalPageVisibleSet.size()) {
+            if (abs(pageIndex - additionalPageVisibleSet.keyAt(index)) < pageInterval) {
+                return false
+            }
+        }
+        return true
+    }
+
+    /**
+     * 判斷是否符合「TIME_INTERVAL」顯示策略
+     */
+    private fun isTimeStrategyValid(): Boolean {
+        return System.currentTimeMillis() - lastDisplayTime >= timeInterval * 1000L
+    }
+
+    /**
+     * 更新最後顯示時間
+     */
+    override fun updateDisplayTime() {
+        lastDisplayTime = System.currentTimeMillis()
+    }
+}

+ 18 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageDisplayStrategyType.kt

@@ -0,0 +1,18 @@
+package com.kdanmobile.reader.additionalpage
+
+/**
+ * 額外頁面顯示策略類型
+ * 額外頁面創建成功後,需符合當前顯示策略類型時才會顯示該額外頁面
+ */
+enum class DisplayStrategyType {
+    //  不顯示額外頁面,行為表現與未使用客製化的ReaderView前完全相同
+    HIDE,
+    //  每隔至少幾頁顯示一個額外頁面
+    PAGE_INTERVAL,
+    //  每隔至少幾秒顯示一個額外頁面
+    TIME_INTERVAL,
+    //  每隔幾頁或每隔幾秒顯示一個額外頁面
+    PAGE_OR_TIME_INTERVAL,
+    //  每隔幾頁且每隔幾秒顯示一個額外頁面
+    PAGE_AND_TIME_INTERVAL
+}

+ 47 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageManager.kt

@@ -0,0 +1,47 @@
+package com.kdanmobile.reader.additionalpage
+
+/**
+ * 額外頁面管理員
+ * 判斷哪些頁面是額外頁面、是否該顯示額外頁面
+ *
+ * <名詞解釋>
+ *     額外頁面:在原始文件之間插入的空頁面,之後可在其上插入其他內容(例如廣告)
+ */
+interface AdditionalPageManager {
+
+    //  額外頁面頁碼轉換器
+    val pageConverter: AdditionalPageConverter
+
+    //  額外頁面顯示策略
+    val displayStrategy: AdditionalPageDisplayStrategy
+
+    //  額外頁面顯示策略類型
+    var displayStrategyType: DisplayStrategyType
+        get() {
+            return displayStrategy.displayStrategyType
+        }
+        set(value) {
+            displayStrategy.displayStrategyType = value
+        }
+
+    /**
+     * 請求額外頁面
+     */
+    var requestAdditionalPage: (position: Int) -> Unit
+
+    /**
+     * 判斷額外頁面是否讀取完畢
+     */
+    var isAdditionalPageLoaded: (position: Int) -> Boolean
+
+    /**
+     * 判斷第pageIndex頁是否是額外頁面,並且該額外頁面是否可見
+     */
+    fun isAdditionalPageVisible(pageIndex: Int): Boolean
+
+    /**
+     * 當前頁面更新時呼叫此方法
+     * 判斷哪幾頁應請求/顯示額外頁面
+     */
+    fun onPageChanged(pageIndex: Int)
+}

+ 92 - 0
src/main/java/com/kdanmobile/reader/additionalpage/AdditionalPageManagerImpl.kt

@@ -0,0 +1,92 @@
+package com.kdanmobile.reader.additionalpage
+
+import android.util.SparseBooleanArray
+import androidx.annotation.IntRange
+import kotlin.math.abs
+
+/**
+ * 額外頁面管理員
+ * 判斷哪些頁面是額外頁面、是否該顯示額外頁面
+ *
+ * <名詞解釋>
+ *     額外頁面:在原始文件之間插入的空頁面,之後可在其上插入其他內容(例如廣告)
+ */
+class AdditionalPageManagerImpl(
+        override val pageConverter: AdditionalPageConverter = AdditionalPageConverterImpl(),
+        override val displayStrategy: AdditionalPageDisplayStrategy = AdditionalPageDisplayStrategyImpl(),
+        @IntRange(from = 3) private val nextInterval: Int = DEFAULT_NEXT_INTERVAL
+) : AdditionalPageManager {
+
+    companion object {
+        //  當前頁面改變後,前後第N頁該顯示額外頁面
+        const val DEFAULT_NEXT_INTERVAL = 3
+    }
+
+    /**
+     * 記錄額外頁面頁碼與其是否可見
+     */
+    private val additionalPageVisibleSet = SparseBooleanArray()
+
+    /**
+     * 請求額外頁面
+     */
+    override var requestAdditionalPage: (position: Int) -> Unit = {}
+
+    /**
+     * 判斷額外頁面是否讀取完畢
+     */
+    override var isAdditionalPageLoaded: (position: Int) -> Boolean = {
+        false
+    }
+
+    /**
+     * 判斷第pageIndex頁是否是額外頁面,並且該額外頁面是否可見
+     */
+    override fun isAdditionalPageVisible(pageIndex: Int): Boolean {
+        return pageConverter.isAdditionalPage(pageIndex) && additionalPageVisibleSet[pageIndex]
+    }
+
+    /**
+     * 判斷第pageIndex頁是否可顯示額外頁面
+     * 若是滿足以下任意條件則不可顯示額外頁面:
+     * .該頁不是額外頁面
+     * .該頁在可視範圍內(與當前頁差距小於nextInterval頁)
+     * .頁碼超過文件範圍
+     */
+    private fun couldAdditionalPageDisplayAt(pageIndex: Int): Boolean {
+        return when {
+            !pageConverter.isAdditionalPage(pageIndex) -> false
+            abs(pageIndex - pageConverter.getCurrentPage()) < nextInterval -> false
+            pageIndex < 0 || pageIndex >= pageConverter.getPageCount() -> false
+            else -> true
+        }
+    }
+
+    /**
+     * 當前頁面更新時呼叫此方法
+     * 判斷哪幾頁應請求/顯示額外頁面
+     */
+    override fun onPageChanged(pageIndex: Int) {
+        //  若不該顯示額外頁面則停止執行
+        if (!pageConverter.isAdditionalPageEnabled.invoke()) return
+
+        for (dir in -1 .. 1 step 2) {
+            //  判斷pageIndex的前後第nextInterval頁
+            val targetIndex = pageIndex + nextInterval * dir
+            //  是否可顯示額外頁面
+            val isValid = displayStrategy.canDisplayAdditionalPage(additionalPageVisibleSet, targetIndex)
+            if (isValid) {
+                //  該額外頁面是否請求成功
+                if (isAdditionalPageLoaded.invoke(targetIndex)) {
+                    //  若可顯示額外頁面則直接顯示,並重設最後顯示時間
+                    if (couldAdditionalPageDisplayAt(targetIndex)) {
+                        additionalPageVisibleSet.put(targetIndex, true)
+                        displayStrategy.updateDisplayTime()
+                    }
+                } else {
+                    requestAdditionalPage.invoke(targetIndex)
+                }
+            }
+        }
+    }
+}

+ 45 - 41
src/main/java/com/kdanmobile/reader/adpage/MyPageView.kt

@@ -1,55 +1,59 @@
-package com.kdanmobile.reader.adpage
+package com.kdanmobile.reader.additionalpage
 
 import android.content.Context
-import android.graphics.*
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.RectF
 import android.view.View
 import android.widget.RelativeLayout
 import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
-import com.kdanmobile.kmpdfkit.pdfcommon.*
+import com.kdanmobile.kmpdfkit.pdfcommon.FilePicker
+import com.kdanmobile.kmpdfkit.pdfcommon.KMPDFPageView
 import com.kdanmobile.reader.utils.DensityUtil
 
 /**
  * 客製化KMPDFPageView
  *
- * 用以處理廣告顯示
+ * 用以處理額外頁面顯示
  */
 class MyPageView(
         context: Context,
         filePickerSupport: FilePicker.FilePickerSupport,
         kmpdfFactory: KMPDFFactory,
         parentSize: Point,
-        //  此頁是否是廣告頁
-        private val isAdPage: Boolean
+        //  此頁是否是額外頁面
+        private val isAdditionalPage: Boolean
 ) : KMPDFPageView(context, filePickerSupport, kmpdfFactory, parentSize) {
 
     companion object {
-        //  預設廣告高度
-        const val DEFAULT_AD_HEIGHT = 450
-        //  預設廣告頁碼(建議為負值,避免錯誤書籤顯示)
-        const val AD_PAGE_NUMBER_ID = -99
+        //  預設額外頁面高度
+        const val DEFAULT_ADDITIONAL_PAGE_HEIGHT = 450
+        //  預設額外頁面頁碼(建議為負值,避免書籤錯誤顯示)
+        const val ADDITIONAL_PAGE_NUMBER_ID = -99
     }
 
-    //  廣告寬度(螢幕寬度)
-    val adWidth = DensityUtil.getScreenWidthPx(context)
-    //  廣告高度(執行期變化)
-    var adHeight = 0
-    //  不可見廣告的大小
-    private val invisibleAdSize = PointF(adWidth.toFloat(), 1f)
-    //  不可見廣告的範圍
-    private val invisibleAdRect = RectF(0f, 0f, invisibleAdSize.x, invisibleAdSize.y)
+    //  額外頁面寬度(螢幕寬度)
+    val additionalPageWidth = DensityUtil.getScreenWidthPx(context)
+    //  額外頁面高度(執行期變化)
+    var additionalPageHeight = 0
+    //  不可見額外頁面的大小
+    private val invisibleAdditionalPageSize = PointF(additionalPageWidth.toFloat(), 1f)
+    //  不可見額外頁面的範圍
+    private val invisibleAdditionalPageRect = RectF(0f, 0f, invisibleAdditionalPageSize.x, invisibleAdditionalPageSize.y)
     //  頁面的縮放值
     private var viewScale = -1f
 
-    //  用來放置廣告的容器
+    //  用來放置額外頁面的容器
     val layout = RelativeLayout(context).also {
         it.visibility = View.GONE
         addView(it)
     }
 
-    //  廣告是否可見
-    var isAdShowing = false
+    //  額外頁面是否可見
+    var isAdditionalPageShowing = false
         set(value) {
-            if (!isAdPage) return
+            if (!isAdditionalPage) return
             if (field != value) {
                 field = value
                 val visibility = when (field) {
@@ -65,12 +69,12 @@ class MyPageView(
     private var initPageSize = false
 
     init {
-        //  如果是廣告
-        if (isAdPage) {
+        //  如果是額外頁面
+        if (isAdditionalPage) {
             //  設定頁碼
-            mPageNumber = AD_PAGE_NUMBER_ID
-            //  廣告大小為不可見
-            super.setPage(page, invisibleAdSize, invisibleAdRect)
+            mPageNumber = ADDITIONAL_PAGE_NUMBER_ID
+            //  頁面大小為不可見
+            super.setPage(page, invisibleAdditionalPageSize, invisibleAdditionalPageRect)
             //  隱藏所有內容
             for (index in 0 until childCount) {
                 getChildAt(index).visibility = View.INVISIBLE
@@ -84,28 +88,28 @@ class MyPageView(
      * 處理長按事件(顯示Context Menu)
      */
     override fun openLongClickBlankContextMenu(view: View?) {
-        //  如果是廣告頁,則遮蔽長按事件
-        if (isAdPage) return
+        //  如果是額外頁面,則遮蔽長按事件
+        if (isAdditionalPage) return
         super.openLongClickBlankContextMenu(view)
     }
 
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
         super.onLayout(changed, left, top, right, bottom)
-        //  如果不是廣告頁,則不執行以下指令
-        if (!isAdPage) return
+        //  如果不是額外頁面,則不執行以下指令
+        if (!isAdditionalPage) return
         //  在某些情況下,頁碼會被重置為0,因此需要重新設定(SDK bug?)
-        mPageNumber = AD_PAGE_NUMBER_ID
+        mPageNumber = ADDITIONAL_PAGE_NUMBER_ID
 
-        //  如果廣告可見
-        if (isAdShowing) {
-            //  廣告置中,大小固定為adWidth x adHeight
-            layout.layout(-x.toInt(), 0, (adWidth - x).toInt(), adHeight)
+        //  如果額外頁面可見
+        if (isAdditionalPageShowing) {
+            //  頁面置中,大小固定為additionalPageWidth x additionalPageHeight
+            layout.layout(-x.toInt(), 0, (additionalPageWidth - x).toInt(), additionalPageHeight)
             //  更新頁面高度
             updateAdHeight()
             //  更新內容大小
             updateAdContentSize()
         } else if (!initPageSize) {
-            super.setPage(page, invisibleAdSize, invisibleAdRect)
+            super.setPage(page, invisibleAdditionalPageSize, invisibleAdditionalPageRect)
             initPageSize = true
         }
     }
@@ -126,11 +130,11 @@ class MyPageView(
         if (scale == viewScale) return
         viewScale = scale
         //  計算頁面大小
-        val modifyPageSize = PointF(adWidth.toFloat(), adHeight / viewScale)
+        val modifyPageSize = PointF(additionalPageWidth.toFloat(), additionalPageHeight / viewScale)
         val cropPageSize = RectF(0f, 0f, modifyPageSize.x, modifyPageSize.y)
         //  設定頁面大小
         setPage(page, modifyPageSize, cropPageSize)
-        //  隱藏廣告以外的所有內容
+        //  隱藏額外頁面以外的所有內容
         for (index in 0 until childCount) {
             getChildAt(index).also {
                 if (it != layout) {
@@ -146,8 +150,8 @@ class MyPageView(
     private fun updateAdContentSize() {
         for (i in 0 until layout.childCount) {
             layout.getChildAt(i).apply {
-                if (left != 0 || top != 0 || right != adWidth || bottom != adHeight) {
-                    layout(0, 0, adWidth, adHeight)
+                if (left != 0 || top != 0 || right != additionalPageWidth || bottom != additionalPageHeight) {
+                    layout(0, 0, additionalPageWidth, additionalPageHeight)
                 }
             }
         }

+ 5 - 5
src/main/java/com/kdanmobile/reader/adpage/MyReaderView.kt

@@ -1,4 +1,4 @@
-package com.kdanmobile.reader.adpage
+package com.kdanmobile.reader.additionalpage
 
 import android.content.Context
 import android.graphics.PointF
@@ -8,11 +8,11 @@ import com.kdanmobile.kmpdfkit.pdfcommon.KMPDFReaderView
 /**
  * 客製化KMPDFReaderView
  *
- * 用以處理廣告點擊事件與修改當前頁面判斷
+ * 用以處理額外頁面點擊事件與修改當前頁面判斷
  */
 open class MyReaderView(
         context: Context,
-        private val adPageHelper: AdPageHelper
+        private val pageConverter: AdditionalPageConverter
 ) : KMPDFReaderView(context) {
 
     /**
@@ -65,11 +65,11 @@ open class MyReaderView(
     }
 
     override fun getDisplayedViewIndex(): Int {
-        return adPageHelper.convertToRawPageIndex(mCurrent)
+        return pageConverter.convertToRawPageIndex(mCurrent)
     }
 
     override fun setDisplayedViewIndex(i: Int, isRecordLastJumpPageNum: Boolean) {
-        super.setDisplayedViewIndex(adPageHelper.convertToPageIndex(i), isRecordLastJumpPageNum)
+        super.setDisplayedViewIndex(pageConverter.convertToPageIndex(i), isRecordLastJumpPageNum)
     }
 
     /**

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

@@ -1,271 +0,0 @@
-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
-    }
-}