Files
EhViewer/app/src/main/java/com/hippo/widget/ContentLayout.kt
T

818 lines
32 KiB
Kotlin

/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.widget
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import com.hippo.easyrecyclerview.HandlerDrawable
import com.hippo.easyrecyclerview.LayoutManagerUtils
import com.hippo.easyrecyclerview.LayoutManagerUtils.OnScrollToPositionListener
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.databinding.WidgetContentLayoutBinding
import com.hippo.util.ExceptionUtils
import com.hippo.util.getParcelableCompat
import com.hippo.view.ViewTransition
import com.hippo.view.ViewTransition.OnShowViewListener
import com.hippo.yorozuya.IntIdGenerator
import com.hippo.yorozuya.LayoutUtils
import com.hippo.yorozuya.collect.IntList
import rikka.core.res.resolveColor
class ContentLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
private lateinit var mContentHelper: ContentHelper<*>
private val mRecyclerViewOriginBottom: Int
private val mFastScrollerOriginBottom: Int
private val binding: WidgetContentLayoutBinding
init {
binding = WidgetContentLayoutBinding.inflate((context as Activity).layoutInflater, this)
clipChildren = false
clipToPadding = false
binding.fastScroller.attachToRecyclerView(binding.recyclerView)
val drawable = HandlerDrawable()
drawable.setColor(context.getTheme().resolveColor(androidx.appcompat.R.attr.colorPrimary))
binding.fastScroller.setHandlerDrawable(drawable)
mRecyclerViewOriginBottom = binding.recyclerView.paddingBottom
mFastScrollerOriginBottom = binding.fastScroller.paddingBottom
}
val recyclerView
get() = binding.recyclerView
val fastScroller
get() = binding.fastScroller
fun setHelper(helper: ContentHelper<*>) {
mContentHelper = helper
helper.init(this)
}
fun hideFastScroll() {
binding.fastScroller.detachedFromRecyclerView()
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, 0)
setFitPaddingBottom(bottom)
}
fun setFitPaddingTop(fitPaddingTop: Int) {
// RefreshLayout
binding.refreshLayout.setProgressViewOffset(
true,
0,
fitPaddingTop + LayoutUtils.dp2pix(context, 32f)
) // TODO
}
private fun setFitPaddingBottom(fitPaddingBottom: Int) {
// RecyclerView
binding.recyclerView.setPadding(
binding.recyclerView.paddingLeft,
binding.recyclerView.paddingTop,
binding.recyclerView.paddingRight,
mRecyclerViewOriginBottom + fitPaddingBottom
)
binding.tip.setPadding(
binding.tip.paddingLeft,
binding.tip.paddingTop,
binding.tip.paddingRight,
fitPaddingBottom
)
binding.progress.setPadding(
binding.progress.paddingLeft,
binding.progress.paddingTop,
binding.progress.paddingRight,
fitPaddingBottom
)
binding.fastScroller.setPadding(
binding.fastScroller.paddingLeft,
binding.fastScroller.paddingTop,
binding.fastScroller.paddingRight,
mFastScrollerOriginBottom + fitPaddingBottom
)
if (fitPaddingBottom > LayoutUtils.dp2pix(context, 16f)) {
binding.bottomProgress.setPadding(0, 0, 0, fitPaddingBottom)
} else {
binding.bottomProgress.setPadding(0, 0, 0, 0)
}
}
override fun onSaveInstanceState(): Parcelable {
return mContentHelper.saveInstanceState(super.onSaveInstanceState())
}
override fun onRestoreInstanceState(state: Parcelable) {
super.onRestoreInstanceState(mContentHelper.restoreInstanceState(state))
}
abstract class ContentHelper<E : Parcelable?> : OnShowViewListener {
/**
* Generate task id
*/
private val mIdGenerator = IntIdGenerator()
private val mOnScrollToPositionListener =
OnScrollToPositionListener { position: Int -> onScrollToPosition(position) }
protected var mPrev: String? = null
protected var mNext: String? = null
private lateinit var mViewTransition: ViewTransition
private lateinit var binding: WidgetContentLayoutBinding
/**
* Store data
*/
private var mData = ArrayList<E>()
/**
* Store the page divider index
*
*
* For example, the data contain page 3, page 4, page 5,
* page 3 size is 7, page 4 size is 8, page 5 size is 9,
* so `mPageDivider` contain 7, 15, 24.
*/
private var mPageDivider = IntList()
/**
* The first page in `mData`
*/
private var mStartPage = 0
/**
* The last page + 1 in `mData`
*/
private var mEndPage = 0
/**
* The available page count.
*/
var pages = 0
private set
private var mNextPage = 0
private var mCurrentTaskId = 0
private var mCurrentTaskType = 0
private var mCurrentTaskPage = 0
private val mOnRefreshListener = OnRefreshListener {
if (mPrev != null || mStartPage > 0) {
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_PRE_PAGE_KEEP_POS
mCurrentTaskPage = mStartPage - 1
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, mPrev, false)
} else {
doRefresh()
}
}
private var mNextPageScrollSize = 0
private var mEmptyString = "No hint"
private val mOnScrollListener: RecyclerView.OnScrollListener =
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (!binding.refreshLayout.isRefreshing && !recyclerView.canScrollVertically(1)) {
if (mNext != null || mEndPage < pages) {
binding.bottomProgress.show()
// Get next page
// Fill pages before NextPage with empty list
while (mNextPage > mEndPage && mEndPage < pages) {
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS
mCurrentTaskPage = mEndPage
onGetPageData(
mCurrentTaskId,
pages,
mNextPage,
null,
null,
emptyList()
)
}
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS
mCurrentTaskPage = mEndPage
getPageData(
mCurrentTaskId,
mCurrentTaskType,
mCurrentTaskPage,
mNext,
true
)
} else if (mStartPage > 0 && mEndPage == pages) {
binding.bottomProgress.show()
// Refresh last page
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_REFRESH_PAGE
mCurrentTaskPage = mEndPage - 1
getPageData(
mCurrentTaskId,
mCurrentTaskType,
mCurrentTaskPage,
null,
true
)
}
}
}
}
private var mSavedDataId = IntIdGenerator.INVALID_ID
fun init(contentLayout: ContentLayout) {
binding = contentLayout.binding
mNextPageScrollSize = LayoutUtils.dp2pix(contentLayout.context, 48f)
val drawable = ContextCompat.getDrawable(context, R.drawable.big_sad_pandroid)!!
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
binding.tip.setCompoundDrawables(null, drawable, null, null)
mViewTransition = ViewTransition(binding.contentView, binding.progress, binding.tip)
mViewTransition.setOnShowViewListener(this)
binding.recyclerView.addOnScrollListener(mOnScrollListener)
binding.refreshLayout.setOnRefreshListener(mOnRefreshListener)
binding.tip.setOnClickListener { refresh() }
}
/**
* Call [.onGetPageData] when get data
*
* @param taskId task id
* @param page the page to get
* @param index the index to get
* @param isNext the index is next or prev
*/
protected abstract fun getPageData(
taskId: Int,
type: Int,
page: Int,
index: String?,
isNext: Boolean
)
protected abstract val context: Context
protected open fun notifyDataSetChanged(callback: () -> Unit) {
callback()
}
protected abstract fun notifyDataSetChanged()
protected abstract fun notifyItemRangeInserted(positionStart: Int, itemCount: Int)
protected open fun onScrollToPosition(position: Int) {}
override fun onShowView(hiddenView: View, shownView: View) {}
val shownViewIndex: Int
get() = mViewTransition.shownViewIndex
fun setRefreshLayoutEnable(enable: Boolean) {
binding.refreshLayout.isEnabled = enable
}
fun setEmptyString(str: String) {
mEmptyString = str
}
val data: List<E>
get() = mData
fun getDataAtEx(location: Int): E? {
return if (location >= 0 && location < mData.size) {
mData[location]
} else {
null
}
}
val firstVisibleItem: E?
get() = getDataAtEx(LayoutManagerUtils.getFirstVisibleItemPosition(binding.recyclerView.layoutManager))
fun size(): Int {
return mData.size
}
fun isCurrentTask(taskId: Int): Boolean {
return mCurrentTaskId == taskId
}
protected abstract fun isDuplicate(d1: E, d2: E): Boolean
private fun removeDuplicateData(data: List<E>, start: Int, end: Int) {
val slicedData = mData.slice(start.coerceAtLeast(0) until end.coerceAtMost(mData.size))
data.dropWhile { d1 ->
slicedData.any { d2 ->
isDuplicate(d1, d2)
}
}
}
protected open fun onAddData(data: List<E>) {}
protected open fun onRemoveData(data: List<E>) {}
protected open fun onClearData() {}
fun onGetPageData(
taskId: Int,
pages: Int,
nextPage: Int,
prev: String?,
next: String?,
data: List<E>
) {
if (mCurrentTaskId == taskId) {
val dataSize: Int
when (mCurrentTaskType) {
TYPE_REFRESH -> {
mStartPage = 0
mEndPage = 1
this.pages = pages
mNextPage = nextPage
mPrev = prev
mNext = next
mPageDivider.clear()
mPageDivider.add(data.size)
if (data.isEmpty()) {
mData.clear()
onClearData()
notifyDataSetChanged()
notifyDataSetChanged {
// Not found
// Ui change, show empty string
showEmptyString()
}
} else {
mData.clear()
onClearData()
mData.addAll(data)
onAddData(data)
notifyDataSetChanged()
notifyDataSetChanged {
// Ui change, show content
showContent()
// RecyclerView scroll
if (binding.recyclerView.isAttachedToWindow) {
scrollToPosition(0)
}
}
}
}
TYPE_PRE_PAGE, TYPE_PRE_PAGE_KEEP_POS -> {
removeDuplicateData(data, 0, CHECK_DUPLICATE_RANGE)
dataSize = data.size
var i = 0
val n = mPageDivider.size
while (i < n) {
mPageDivider[i] = mPageDivider[i] + dataSize
i++
}
mPageDivider.add(0, dataSize)
mStartPage--
this.pages = pages.coerceAtLeast(mEndPage)
mPrev = prev
// assert mStartPage >= 0
if (data.isEmpty()) {
// OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
showEmptyString()
} else {
// Ui change, show content
showContent()
if (mCurrentTaskType == TYPE_PRE_PAGE && binding.recyclerView.isAttachedToWindow) {
// RecyclerView scroll, to top
scrollToPosition(0)
}
}
} else {
mData.addAll(0, data)
onAddData(data)
notifyItemRangeInserted(0, data.size)
notifyDataSetChanged {
// Ui change, show content
showContent()
if (binding.recyclerView.isAttachedToWindow) {
// RecyclerView scroll
if (mCurrentTaskType == TYPE_PRE_PAGE_KEEP_POS) {
binding.recyclerView.stopScroll()
LayoutManagerUtils.scrollToPositionProperly(
binding.recyclerView.layoutManager, context,
dataSize - 1, mOnScrollToPositionListener
)
} else {
scrollToPosition(0)
}
}
}
}
}
TYPE_NEXT_PAGE, TYPE_NEXT_PAGE_KEEP_POS -> {
removeDuplicateData(data, mData.size - CHECK_DUPLICATE_RANGE, mData.size)
dataSize = data.size
val oldDataSize = mData.size
mPageDivider.add(oldDataSize + dataSize)
mEndPage++
mNextPage = nextPage
this.pages = pages.coerceAtLeast(mEndPage)
mNext = next
if (data.isEmpty()) {
// OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
showEmptyString()
} else {
// Ui change, show content
showContent()
if (mCurrentTaskType == TYPE_NEXT_PAGE && binding.recyclerView.isAttachedToWindow) {
// RecyclerView scroll
scrollToPosition(oldDataSize)
}
}
} else {
mData.addAll(data)
onAddData(data)
notifyItemRangeInserted(oldDataSize, dataSize)
notifyDataSetChanged {
// Ui change, show content
showContent()
if (binding.recyclerView.isAttachedToWindow) {
if (mCurrentTaskType == TYPE_NEXT_PAGE_KEEP_POS) {
binding.recyclerView.stopScroll()
binding.recyclerView.smoothScrollBy(0, mNextPageScrollSize)
} else {
scrollToPosition(oldDataSize)
}
}
}
}
}
TYPE_SOMEWHERE -> {
mStartPage = mCurrentTaskPage
mEndPage = mCurrentTaskPage + 1
mNextPage = nextPage
this.pages = pages
mPrev = prev
mNext = next
mPageDivider.clear()
mPageDivider.add(data.size)
if (data.isEmpty()) {
mData.clear()
onClearData()
notifyDataSetChanged()
notifyDataSetChanged {
// Not found
// Ui change, show empty string
showEmptyString()
}
} else {
mData.clear()
onClearData()
mData.addAll(data)
onAddData(data)
notifyDataSetChanged()
notifyDataSetChanged {
// Ui change, show content
showContent()
if (binding.recyclerView.isAttachedToWindow) {
// RecyclerView scroll
scrollToPosition(0)
}
}
}
}
TYPE_REFRESH_PAGE -> {
if (mCurrentTaskPage < mStartPage || mCurrentTaskPage >= mEndPage) {
Log.e(
TAG,
"TYPE_REFRESH_PAGE, but mCurrentTaskPage = " + mCurrentTaskPage +
", mStartPage = " + mStartPage + ", mEndPage = " + mEndPage
)
return
}
if (mCurrentTaskPage == mEndPage - 1) {
mNextPage = nextPage
}
this.pages = pages.coerceAtLeast(mEndPage)
val oldIndexStart =
if (mCurrentTaskPage == mStartPage) 0 else mPageDivider[mCurrentTaskPage - mStartPage - 1]
val oldIndexEnd = mPageDivider[mCurrentTaskPage - mStartPage]
val toRemove = mData.subList(oldIndexStart, oldIndexEnd)
onRemoveData(toRemove)
toRemove.clear()
removeDuplicateData(
data,
oldIndexStart - CHECK_DUPLICATE_RANGE,
oldIndexStart + CHECK_DUPLICATE_RANGE
)
val newIndexEnd = oldIndexStart + data.size
mData.addAll(oldIndexStart, data)
onAddData(data)
notifyDataSetChanged()
notifyDataSetChanged {
var i = mCurrentTaskPage - mStartPage
val n = mPageDivider.size
while (i < n) {
mPageDivider[i] = mPageDivider[i] - oldIndexEnd + newIndexEnd
i++
}
if (mData.isEmpty()) {
// Ui change, show empty string
showEmptyString()
} else {
// Ui change, show content
showContent()
// RecyclerView scroll
if (newIndexEnd > oldIndexEnd && newIndexEnd > 0 && binding.recyclerView.isAttachedToWindow) {
scrollToPosition(newIndexEnd - 1)
}
}
}
}
}
}
}
fun onGetException(taskId: Int, e: Exception?) {
if (mCurrentTaskId == taskId) {
showProgress(PROGRESS_NONE)
val readableError = if (e != null) {
e.printStackTrace()
ExceptionUtils.getReadableString(e)
} else {
context.getString(R.string.error_unknown)
}
if (mViewTransition.shownViewIndex == 0) {
Toast.makeText(context, readableError, Toast.LENGTH_SHORT).show()
} else {
showText(readableError)
}
}
}
private fun scrollToPosition(position: Int) {
binding.recyclerView.stopScroll()
LayoutManagerUtils.scrollToPositionWithOffset(
binding.recyclerView.layoutManager,
position,
0
)
onScrollToPosition(position)
}
private fun showProgress(type: Int) {
when (type) {
PROGRESS_NONE -> {
binding.refreshLayout.isRefreshing = false
binding.bottomProgress.hide()
}
PROGRESS_TOP -> {
binding.refreshLayout.isRefreshing = true
binding.bottomProgress.hide()
}
PROGRESS_BOTTOM -> {
binding.refreshLayout.isRefreshing = false
binding.bottomProgress.show()
}
}
}
private fun showContent() {
showProgress(PROGRESS_NONE)
mViewTransition.showView(0)
}
private val isContentShowing: Boolean
get() = mViewTransition.shownViewIndex == 0
@JvmOverloads
fun showProgressBar(animation: Boolean = true) {
mViewTransition.showView(1, animation)
}
private fun showText(text: CharSequence?) {
binding.tip.text = text
mViewTransition.showView(2)
}
private fun showEmptyString() {
showProgress(PROGRESS_NONE)
showText(mEmptyString)
}
private fun doRefresh() {
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_REFRESH
mCurrentTaskPage = 0
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true)
}
/**
* Like [.refresh], but no animation when show progress bar
*/
fun firstRefresh() {
showProgressBar(false)
doRefresh()
}
/**
* Show progress bar first, than do refresh
*/
fun refresh() {
showProgressBar()
doRefresh()
}
private fun cancelCurrentTask() {
mCurrentTaskId = mIdGenerator.nextId()
showProgress(PROGRESS_NONE)
}
private fun getPageStart(page: Int): Int {
return if (mStartPage == page) {
0
} else {
mPageDivider[page - mStartPage - 1]
}
}
private fun getPageForPosition(position: Int): Int {
if (position < 0) {
return -1
}
val pageDivider = mPageDivider
var i = 0
val n = pageDivider.size
while (i < n) {
if (position < pageDivider[i]) {
return i + mStartPage
}
i++
}
return -1
}
val pageForTop: Int
get() = getPageForPosition(LayoutManagerUtils.getFirstVisibleItemPosition(binding.recyclerView.layoutManager))
val pageForBottom: Int
get() = getPageForPosition(LayoutManagerUtils.getLastVisibleItemPosition(binding.recyclerView.layoutManager))
fun canGoTo(): Boolean {
return isContentShowing
}
/**
* Check range first!
*
* @param page the target page
* @throws IndexOutOfBoundsException if page < 0 or page >= mPages
*/
@Throws(IndexOutOfBoundsException::class)
fun goTo(page: Int) {
if (page < 0 || page >= pages) {
throw IndexOutOfBoundsException("Page count is $pages, page is $page")
} else if (page in mStartPage until mEndPage) {
cancelCurrentTask()
val position = getPageStart(page)
scrollToPosition(position)
} else if (page == mStartPage - 1) {
showProgress(PROGRESS_TOP)
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_PRE_PAGE
mCurrentTaskPage = page
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true)
} else if (page == mEndPage) {
showProgress(PROGRESS_BOTTOM)
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_NEXT_PAGE
mCurrentTaskPage = page
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true)
} else {
showProgress(PROGRESS_TOP)
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_SOMEWHERE
mCurrentTaskPage = page
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true)
}
}
fun goTo(index: String?, isNext: Boolean) {
showProgress(PROGRESS_TOP)
mCurrentTaskId = mIdGenerator.nextId()
mCurrentTaskType = TYPE_SOMEWHERE
mCurrentTaskPage = 0
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, index, isNext)
}
open fun saveInstanceState(superState: Parcelable?): Parcelable {
if (mData.isNotEmpty()) cancelCurrentTask()
val bundle = Bundle()
bundle.putParcelable(KEY_SUPER, superState)
val shownView = mViewTransition.shownViewIndex
bundle.putInt(KEY_SHOWN_VIEW, shownView)
bundle.putString(KEY_TIP, binding.tip.text.toString())
// TODO It's a bad design
val app = EhApplication.application
if (mSavedDataId != IntIdGenerator.INVALID_ID) {
app.removeGlobalStuff(mSavedDataId)
mSavedDataId = IntIdGenerator.INVALID_ID
}
mSavedDataId = app.putGlobalStuff(mData)
bundle.putInt(KEY_DATA, mSavedDataId)
bundle.putInt(KEY_NEXT_ID, mIdGenerator.nextId())
bundle.putParcelable(KEY_PAGE_DIVIDER, mPageDivider)
bundle.putInt(KEY_START_PAGE, mStartPage)
bundle.putInt(KEY_END_PAGE, mEndPage)
bundle.putInt(KEY_PAGES, pages)
bundle.putString(KEY_PREV, mPrev)
bundle.putString(KEY_NEXT, mNext)
return bundle
}
open fun restoreInstanceState(state: Parcelable): Parcelable? {
return if (state is Bundle) {
mViewTransition.showView(state.getInt(KEY_SHOWN_VIEW), false)
binding.tip.text = state.getString(KEY_TIP)
mSavedDataId = state.getInt(KEY_DATA)
var newData: ArrayList<E>? = null
val app = EhApplication.application
if (mSavedDataId != IntIdGenerator.INVALID_ID) {
newData = app.removeGlobalStuff(mSavedDataId) as ArrayList<E>?
mSavedDataId = IntIdGenerator.INVALID_ID
if (newData != null) {
mData = newData
}
}
mIdGenerator.setNextId(state.getInt(KEY_NEXT_ID))
mPageDivider = state.getParcelableCompat(KEY_PAGE_DIVIDER)!!
mStartPage = state.getInt(KEY_START_PAGE)
mEndPage = state.getInt(KEY_END_PAGE)
pages = state.getInt(KEY_PAGES)
mPrev = state.getString(KEY_PREV)
mNext = state.getString(KEY_NEXT)
notifyDataSetChanged()
if (newData == null) {
mPageDivider.clear()
mStartPage = 0
mEndPage = 0
pages = 0
mPrev = null
mNext = null
firstRefresh()
}
state.getParcelableCompat(KEY_SUPER)
} else {
state
}
}
companion object {
const val TYPE_REFRESH = 0
const val TYPE_PRE_PAGE = 1
const val TYPE_PRE_PAGE_KEEP_POS = 2
const val TYPE_NEXT_PAGE = 3
const val TYPE_NEXT_PAGE_KEEP_POS = 4
const val TYPE_SOMEWHERE = 5
const val TYPE_REFRESH_PAGE = 6
private val TAG = ContentHelper::class.java.simpleName
private const val CHECK_DUPLICATE_RANGE = 50
private const val PROGRESS_NONE = 0
private const val PROGRESS_TOP = 1
private const val PROGRESS_BOTTOM = 2
private const val KEY_SUPER = "super"
private const val KEY_SHOWN_VIEW = "shown_view"
private const val KEY_TIP = "tip"
private const val KEY_DATA = "data"
private const val KEY_NEXT_ID = "next_id"
private const val KEY_PAGE_DIVIDER = "page_divider"
private const val KEY_START_PAGE = "start_page"
private const val KEY_END_PAGE = "end_page"
private const val KEY_PAGES = "pages"
private const val KEY_PREV = "prev"
private const val KEY_NEXT = "next"
}
}
}