Просмотр исходного кода

[新增] 增加阅读界面的手柄支持(#616) @Z2549

Kudomaga 8 месяцев назад
Родитель
Сommit
49e0f2abca
1 измененных файлов с 1991 добавлено и 1966 удалено
  1. 1991 1966
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

+ 1991 - 1966
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

@@ -1,1966 +1,1991 @@
-package io.legado.app.ui.book.read
-
-//import io.legado.app.lib.theme.accentColor
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.content.res.Configuration
-import android.graphics.Color
-import android.os.Bundle
-import android.view.Gravity
-import android.view.HapticFeedbackConstants
-import android.view.InputDevice
-import android.view.KeyEvent
-import android.view.Menu
-import android.view.MenuItem
-import android.view.MotionEvent
-import android.view.View
-import androidx.activity.addCallback
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.widget.PopupMenu
-import androidx.core.view.get
-import androidx.core.view.size
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.transition.platform.MaterialContainerTransform
-import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
-import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
-import com.script.rhino.runScriptWithContext
-import io.legado.app.BuildConfig
-import io.legado.app.R
-import io.legado.app.constant.AppLog
-import io.legado.app.constant.BookType
-import io.legado.app.constant.EventBus
-import io.legado.app.constant.PreferKey
-import io.legado.app.constant.Status
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.Book
-import io.legado.app.data.entities.BookChapter
-import io.legado.app.data.entities.BookProgress
-import io.legado.app.data.entities.BookSource
-import io.legado.app.exception.NoStackTraceException
-import io.legado.app.help.AppWebDav
-import io.legado.app.help.IntentData
-import io.legado.app.help.TTS
-import io.legado.app.help.book.BookHelp
-import io.legado.app.help.book.ContentProcessor
-import io.legado.app.help.book.isAudio
-import io.legado.app.help.book.isEpub
-import io.legado.app.help.book.isLocal
-import io.legado.app.help.book.isLocalTxt
-import io.legado.app.help.book.isMobi
-import io.legado.app.help.book.removeType
-import io.legado.app.help.book.update
-import io.legado.app.help.config.AppConfig
-import io.legado.app.help.config.ReadBookConfig
-import io.legado.app.help.config.ReadTipConfig
-import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.help.source.getSourceType
-import io.legado.app.help.storage.Backup
-import io.legado.app.lib.dialogs.SelectItem
-import io.legado.app.lib.dialogs.alert
-import io.legado.app.lib.dialogs.selector
-import io.legado.app.model.ReadAloud
-import io.legado.app.model.ReadBook
-import io.legado.app.model.analyzeRule.AnalyzeRule
-import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter
-import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
-import io.legado.app.model.analyzeRule.AnalyzeUrl.Companion.paramPattern
-import io.legado.app.model.localBook.EpubFile
-import io.legado.app.model.localBook.MobiFile
-import io.legado.app.receiver.NetworkChangedListener
-import io.legado.app.receiver.TimeBatteryReceiver
-import io.legado.app.service.BaseReadAloudService
-import io.legado.app.ui.about.AppLogDialog
-import io.legado.app.ui.book.bookmark.BookmarkDialog
-import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
-import io.legado.app.ui.book.changesource.ChangeChapterSourceDialog
-import io.legado.app.ui.book.info.BookInfoActivity
-import io.legado.app.ui.book.read.config.AutoReadDialog
-import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
-import io.legado.app.ui.book.read.config.FontConfigDialog
-import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.S_COLOR
-import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.TEXT_ACCENT_COLOR
-import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.TEXT_COLOR
-import io.legado.app.ui.book.read.config.FontSelectDialog
-import io.legado.app.ui.book.read.config.ReadAloudDialog
-import io.legado.app.ui.book.read.config.ReadStyleDialog
-import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.A_COLOR
-import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.B_COLOR
-import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_COLOR
-import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_COLOR
-import io.legado.app.ui.book.read.config.ToolButtonConfigDialog
-import io.legado.app.ui.book.read.config.UnderlineConfigDialog.Companion.U_COLOR
-import io.legado.app.ui.book.read.page.ContentTextView
-import io.legado.app.ui.book.read.page.ReadView
-import io.legado.app.ui.book.read.page.entities.PageDirection
-import io.legado.app.ui.book.read.page.entities.TextPage
-import io.legado.app.ui.book.read.page.provider.ChapterProvider
-import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
-import io.legado.app.ui.book.searchContent.SearchContentActivity
-import io.legado.app.ui.book.searchContent.SearchResult
-import io.legado.app.ui.book.source.edit.BookSourceEditActivity
-import io.legado.app.ui.book.toc.TocActivityResult
-import io.legado.app.ui.book.toc.rule.TxtTocRuleDialog
-import io.legado.app.ui.browser.WebViewActivity
-import io.legado.app.ui.dict.DictDialog
-import io.legado.app.ui.login.SourceLoginActivity
-import io.legado.app.ui.login.SourceLoginJsExtensions
-import io.legado.app.ui.replace.ReplaceEditRoute
-import io.legado.app.ui.replace.ReplaceRuleActivity
-import io.legado.app.ui.widget.PopupAction
-import io.legado.app.ui.widget.dialog.PhotoDialog
-import io.legado.app.utils.Debounce
-import io.legado.app.utils.GSON
-import io.legado.app.utils.LogUtils
-import io.legado.app.utils.NetworkUtils
-import io.legado.app.utils.StartActivityContract
-import io.legado.app.utils.applyOpenTint
-import io.legado.app.utils.buildMainHandler
-import io.legado.app.utils.dismissDialogFragment
-import io.legado.app.utils.fromJsonObject
-import io.legado.app.utils.getPrefBoolean
-import io.legado.app.utils.getPrefString
-import io.legado.app.utils.hexString
-import io.legado.app.utils.iconItemOnLongClick
-import io.legado.app.utils.invisible
-import io.legado.app.utils.isAbsUrl
-import io.legado.app.utils.isTrue
-import io.legado.app.utils.navigationBarGravity
-import io.legado.app.utils.observeEvent
-import io.legado.app.utils.observeEventSticky
-import io.legado.app.utils.postEvent
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.showHelp
-import io.legado.app.utils.startActivity
-import io.legado.app.utils.startActivityForBook
-import io.legado.app.utils.sysScreenOffTime
-import io.legado.app.utils.themeColor
-import io.legado.app.utils.throttle
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.Dispatchers.Main
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * 阅读界面
- */
-class ReadBookActivity : BaseReadBookActivity(),
-    View.OnTouchListener,
-    ReadView.CallBack,
-    TextActionMenu.CallBack,
-    ContentTextView.CallBack,
-    PopupMenu.OnMenuItemClickListener,
-    ReadMenu.CallBack,
-    SearchMenu.CallBack,
-    ReadAloudDialog.CallBack,
-    ChangeBookSourceDialog.CallBack,
-    ChangeChapterSourceDialog.CallBack,
-    ReadBook.CallBack,
-    AutoReadDialog.CallBack,
-    ToolButtonConfigDialog.CallBack,
-    TxtTocRuleDialog.CallBack,
-    ColorPickerDialogListener,
-    FontConfigDialog.CallBack,
-    FontSelectDialog.CallBack,
-    LayoutProgressListener {
-
-    private val tocActivity =
-        registerForActivityResult(TocActivityResult()) {
-            it?.let {
-                viewModel.openChapter(it.first, it.second)
-            }
-        }
-    private val sourceEditActivity =
-        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
-            if (it.resultCode == RESULT_OK) {
-                viewModel.upBookSource {
-                    upMenuView()
-                }
-            }
-        }
-    private val replaceActivity =
-        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-            if (it.resultCode == RESULT_OK) {
-                viewModel.replaceRuleChanged()
-            }
-        }
-
-    private val searchContentActivity =
-        registerForActivityResult(StartActivityContract(SearchContentActivity::class.java)) {
-            val data = it.data ?: return@registerForActivityResult
-            val key = data.getLongExtra("key", System.currentTimeMillis())
-            val index = data.getIntExtra("index", 0)
-            val searchResult = IntentData.get<SearchResult>("searchResult$key")
-            val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
-            if (searchResult != null && searchResultList != null) {
-                viewModel.searchContentQuery = searchResult.query
-                binding.searchMenu.upSearchResultList(searchResultList)
-                isShowingSearchResult = true
-                viewModel.searchResultIndex = index
-                binding.searchMenu.updateSearchResultIndex(index)
-                binding.searchMenu.selectedSearchResult?.let { currentResult ->
-                    ReadBook.saveCurrentBookProgress() //退出全文搜索恢复此时进度
-                    skipToSearch(currentResult)
-                    showActionMenu()
-                }
-            }
-        }
-    private val bookInfoActivity =
-        registerForActivityResult(StartActivityContract(BookInfoActivity::class.java)) {
-            if (it.resultCode == RESULT_OK) {
-                setResult(RESULT_DELETED)
-                finish()
-            } else {
-                ReadBook.loadOrUpContent()
-            }
-        }
-    private var menu: Menu? = null
-    private var backupJob: Job? = null
-    private var tts: TTS? = null
-    val textActionMenu: TextActionMenu by lazy {
-        TextActionMenu(this, this)
-    }
-    private val popupAction: PopupAction by lazy {
-        PopupAction(this)
-    }
-
-    // 当前使用的字体路径(给 FontDialog 用)
-    override val curFontPath: String
-        get() = ReadBookConfig.textFont
-
-    // 当选择了字体时被调用
-    override fun selectFont(path: String) {
-        // path 为空表示恢复系统默认字体
-        ReadBookConfig.textFont = path
-        // 通知阅读界面刷新字体
-        postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5, 2))
-    }
-
-    override val isInitFinish: Boolean get() = viewModel.isInitFinish
-    override val isScroll: Boolean get() = binding.readView.isScroll
-    private val isAutoPage get() = binding.readView.isAutoPage
-    override var isShowingSearchResult = false
-    override var isSelectingSearchResult = false
-        set(value) {
-            field = value && isShowingSearchResult
-        }
-    private val timeBatteryReceiver = TimeBatteryReceiver()
-    private var screenTimeOut: Long = 0
-    private var loadStates: Boolean = false
-    override val pageFactory get() = binding.readView.pageFactory
-    override val pageDelegate get() = binding.readView.pageDelegate
-    override val headerHeight: Int get() = binding.readView.curPage.headerHeight
-    override val imgBgPaddingStart: Int get() = binding.readView.curPage.imgBgPaddingStart
-    private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
-    private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
-    private var bookChanged = false
-    private var pageChanged = false
-    private val handler by lazy { buildMainHandler() }
-    private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
-    private val executor = ReadBook.executor
-    private val upSeekBarThrottle = throttle(200) {
-        runOnUiThread {
-            upSeekBarProgress()
-            binding.readMenu.upSeekBar()
-        }
-    }
-
-    //恢复跳转前进度对话框的交互结果
-    private var confirmRestoreProcess: Boolean? = null
-    private val networkChangedListener by lazy {
-        NetworkChangedListener(this)
-    }
-    private var justInitData: Boolean = false
-    private var syncDialog: AlertDialog? = null
-
-    private val delayMillis: Long = 500
-    private var hasLoadedBook = false
-
-    private fun ensureLoadBook() {
-        if (!hasLoadedBook) {
-            hasLoadedBook = true
-            loadBook()
-        }
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    override fun onCreate(savedInstanceState: Bundle?) {
-        setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
-        setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
-        val transform = MaterialContainerTransform().apply {
-            addTarget(binding.root)
-            scrimColor = Color.TRANSPARENT
-            duration = delayMillis
-        }
-        window.sharedElementEnterTransition = transform
-        window.sharedElementReturnTransition = transform
-
-        super.onCreate(savedInstanceState)
-
-        binding.rootView.transitionName = intent.getStringExtra("transitionName")
-
-        upScreenTimeOut()
-        ReadBook.register(this)
-        binding.readMenu.colorSurfaceContainer =
-            themeColor(com.google.android.material.R.attr.colorSurfaceContainer)
-        binding.readMenu.colorSecondary =
-            themeColor(androidx.appcompat.R.attr.colorPrimary)
-        binding.readMenu.colorSecondaryContainer =
-            themeColor(com.google.android.material.R.attr.colorSecondaryContainer)
-        binding.cursorLeft.setOnTouchListener(this)
-        binding.cursorRight.setOnTouchListener(this)
-
-        onBackPressedDispatcher.addCallback(this) {
-            if (isShowingSearchResult) {
-                exitSearchMenu()
-                restoreLastBookProcess()
-                return@addCallback
-            }
-            //拦截返回供恢复阅读进度
-            if (ReadBook.lastBookProgress != null && confirmRestoreProcess != false) {
-                restoreLastBookProcess()
-                return@addCallback
-            }
-            if (BaseReadAloudService.isPlay()) {
-                ReadAloud.pause(this@ReadBookActivity)
-                toastOnUi(R.string.read_aloud_pause)
-                return@addCallback
-            }
-            if (isAutoPage) {
-                autoPageStop()
-                return@addCallback
-            }
-            if (getPrefBoolean("disableReturnKey") && !menuLayoutIsVisible) {
-                return@addCallback
-            }
-            if (savedInstanceState != null || !ReadBook.inBookshelf) {
-                ReadBook.commitReadSession()
-                finish()
-            } else {
-                ReadBook.commitReadSession()
-                supportFinishAfterTransition()
-            }
-            //TODO: 有关测量相关问题
-            //孩子们,我的水平不够,只能这样修复测量问题了
-        }
-    }
-
-    override fun onPostCreate(savedInstanceState: Bundle?) {
-        super.onPostCreate(savedInstanceState)
-        if (!AppConfig.sharedElementEnterTransitionEnable || !AppConfig.delayBookLoadEnable) {
-            ensureLoadBook()
-        }
-        //虽然很扯但有用
-        binding.rootView.postDelayed({
-            ensureLoadBook()
-        }, delayMillis)
-    }
-
-    override fun onNewIntent(intent: Intent) {
-        super.onNewIntent(intent)
-        viewModel.initData(intent)
-    }
-
-    override fun onWindowFocusChanged(hasFocus: Boolean) {
-        super.onWindowFocusChanged(hasFocus)
-        upSystemUiVisibility()
-        if (hasFocus) {
-            binding.readMenu.upBrightnessState()
-        } else if (!menuLayoutIsVisible) {
-            ReadBook.cancelPreDownloadTask()
-        }
-    }
-
-    override fun onConfigurationChanged(newConfig: Configuration) {
-        super.onConfigurationChanged(newConfig)
-        //upSystemUiVisibility()
-        binding.readView.upStyle()
-        recreate()
-    }
-
-
-    override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) {
-        if (!isTopResumedActivity) {
-            ReadBook.cancelPreDownloadTask()
-        }
-    }
-
-    @SuppressLint("UnspecifiedRegisterReceiverFlag")
-    override fun onResume() {
-        super.onResume()
-        ReadBook.readStartTime = System.currentTimeMillis()
-        ReadBook.initReadTime()
-        ReadBook.startAutoSaveSession()
-        if (bookChanged) {
-            bookChanged = false
-            ReadBook.callBack = this
-            viewModel.initData(intent)
-            justInitData = true
-        } else {
-            //web端阅读时,app处于阅读界面,本地记录会覆盖web保存的进度,在此处恢复
-            ReadBook.webBookProgress?.let {
-                ReadBook.setProgress(it)
-                ReadBook.webBookProgress = null
-            }
-        }
-        upSystemUiVisibility()
-        registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)
-        binding.readView.upTime()
-        screenOffTimerStart()
-        // 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作)
-        networkChangedListener.register()
-        networkChangedListener.onNetworkChanged = {
-            // 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑)
-            if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
-                ReadBook.syncProgress({ progress -> sureNewProgress(progress) })
-            }
-        }
-    }
-
-    override fun onPause() {
-        super.onPause()
-        autoPageStop()
-        backupJob?.cancel()
-        ReadBook.saveRead()
-        ReadBook.stopAutoSaveSession()
-        ReadBook.commitReadSession()
-        ReadBook.cancelPreDownloadTask()
-        unregisterReceiver(timeBatteryReceiver)
-        upSystemUiVisibility()
-        if (!BuildConfig.DEBUG) {
-            if (AppConfig.syncBookProgressPlus) {
-                ReadBook.syncProgress()
-            } else {
-                ReadBook.uploadProgress()
-            }
-            Backup.autoBack(this)
-        }
-        justInitData = false
-        networkChangedListener.unRegister()
-    }
-
-    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
-        menuInflater.inflate(R.menu.book_read, menu)
-        menu.iconItemOnLongClick(R.id.menu_change_source) {
-            PopupMenu(this, it).apply {
-                inflate(R.menu.book_read_change_source)
-                this.menu.applyOpenTint(this@ReadBookActivity)
-                setOnMenuItemClickListener(this@ReadBookActivity)
-            }.show()
-        }
-        menu.iconItemOnLongClick(R.id.menu_refresh) {
-            PopupMenu(this, it).apply {
-                inflate(R.menu.book_read_refresh)
-                this.menu.applyOpenTint(this@ReadBookActivity)
-                setOnMenuItemClickListener(this@ReadBookActivity)
-            }.show()
-        }
-        binding.readMenu.refreshMenuColorFilter()
-        return super.onCompatCreateOptionsMenu(menu)
-    }
-
-    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
-        this.menu = menu
-        upMenu()
-        return super.onPrepareOptionsMenu(menu)
-    }
-
-    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
-        menu.findItem(R.id.menu_same_title_removed)?.isChecked =
-            ReadBook.curTextChapter?.sameTitleRemoved == true
-        return super.onMenuOpened(featureId, menu)
-    }
-
-    private fun loadBook() {
-        lifecycleScope.launch(Dispatchers.Default) {
-            viewModel.initReadBookConfig(intent)
-            viewModel.initData(intent)
-            withContext(Main) {
-                justInitData = true
-            }
-        }
-    }
-
-    /**
-     * 更新菜单
-     */
-    private fun upMenu() {
-        val menu = menu ?: return
-        val book = ReadBook.book ?: return
-        val onLine = !book.isLocal
-        for (i in 0 until menu.size) {
-            val item = menu[i]
-            when (item.groupId) {
-                R.id.menu_group_on_line -> item.isVisible = onLine
-                R.id.menu_group_local -> item.isVisible = !onLine
-                R.id.menu_group_text -> item.isVisible = book.isLocalTxt
-                R.id.menu_group_epub -> item.isVisible = book.isEpub
-                else -> when (item.itemId) {
-                    R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
-                    R.id.menu_re_segment -> item.isChecked = book.getReSegment()
-//                    R.id.menu_enable_review -> {
-//                        item.isVisible = BuildConfig.DEBUG
-//                        item.isChecked = AppConfig.enableReview
-//                    }
-
-                    R.id.menu_reverse_content -> item.isVisible = onLine
-                    R.id.menu_del_ruby_tag -> item.isChecked = book.getDelTag(Book.rubyTag)
-                    R.id.menu_del_h_tag -> item.isChecked = book.getDelTag(Book.hTag)
-                }
-            }
-        }
-        lifecycleScope.launch {
-            menu.findItem(R.id.menu_get_progress)?.isVisible = withContext(IO) {
-                AppWebDav.isOk
-            }
-            menu.findItem(R.id.menu_cover_progress)?.isVisible = withContext(IO) {
-                AppWebDav.isOk
-            }
-        }
-    }
-
-    private fun defaultChangeSource() {
-        if (AppConfig.defaultSourceChangeAll)
-        {
-            binding.readMenu.runMenuOut()
-            ReadBook.book?.let {
-                showDialogFragment(ChangeBookSourceDialog(it.name, it.author))
-            }
-        } else {
-            lifecycleScope.launch {
-                val book = ReadBook.book ?: return@launch
-                val chapter =
-                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-                        ?: return@launch
-                binding.readMenu.runMenuOut()
-                showDialogFragment(
-                    ChangeChapterSourceDialog(book.name, book.author, chapter.index, chapter.title)
-                )
-            }
-        }
-    }
-
-    /**
-     * 菜单
-     */
-    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_change_source -> { defaultChangeSource() }
-
-            R.id.menu_book_change_source -> {
-                binding.readMenu.runMenuOut()
-                ReadBook.book?.let {
-                    showDialogFragment(ChangeBookSourceDialog(it.name, it.author))
-                }
-            }
-
-            R.id.menu_chapter_change_source -> lifecycleScope.launch {
-                val book = ReadBook.book ?: return@launch
-                val chapter =
-                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-                        ?: return@launch
-                binding.readMenu.runMenuOut()
-                showDialogFragment(
-                    ChangeChapterSourceDialog(book.name, book.author, chapter.index, chapter.title)
-                )
-            }
-
-            R.id.menu_refresh,
-            R.id.menu_refresh_dur -> {
-                if (ReadBook.bookSource == null) {
-                    upContent()
-                } else {
-                    ReadBook.book?.let {
-                        ReadBook.curTextChapter = null
-                        binding.readView.upContent()
-                        viewModel.refreshContentDur(it)
-                    }
-                }
-            }
-
-            R.id.menu_refresh_after -> {
-                if (ReadBook.bookSource == null) {
-                    upContent()
-                } else {
-                    ReadBook.book?.let {
-                        ReadBook.clearTextChapter()
-                        binding.readView.upContent()
-                        viewModel.refreshContentAfter(it)
-                    }
-                }
-            }
-
-            R.id.menu_refresh_all -> {
-                if (ReadBook.bookSource == null) {
-                    upContent()
-                } else {
-                    ReadBook.book?.let {
-                        refreshContentAll(it)
-                    }
-                }
-            }
-
-            R.id.menu_setting_replace -> openReplaceRule()
-            R.id.menu_download -> showDownloadDialog()
-            R.id.menu_add_bookmark -> addBookmark()
-            R.id.menu_simulated_reading -> showSimulatedReading()
-            R.id.menu_edit_content -> {
-                binding.readMenu.runMenuOut()
-                showDialogFragment(ContentEditDialog())
-            }
-            R.id.menu_update_toc -> ReadBook.book?.let {
-                if (it.isEpub) {
-                    BookHelp.clearCache(it)
-                    EpubFile.clear()
-                }
-                if (it.isMobi) {
-                    MobiFile.clear()
-                }
-                loadChapterList(it)
-            }
-            R.id.menu_enable_replace -> {
-                changeReplaceRuleState()
-            }
-            R.id.menu_re_segment -> ReadBook.book?.let {
-                it.setReSegment(!it.getReSegment())
-                item.isChecked = it.getReSegment()
-                ReadBook.loadContent(false)
-            }
-
-            R.id.menu_tool_button -> {
-                binding.readMenu.runMenuOut()
-                showDialogFragment(ToolButtonConfigDialog())
-            }
-
-//            R.id.menu_enable_review -> {
-//                AppConfig.enableReview = !AppConfig.enableReview
-//                item.isChecked = AppConfig.enableReview
-//                ReadBook.loadContent(false)
-//            }
-
-            R.id.menu_del_ruby_tag -> ReadBook.book?.let {
-                item.isChecked = !item.isChecked
-                if (item.isChecked) {
-                    it.addDelTag(Book.rubyTag)
-                } else {
-                    it.removeDelTag(Book.rubyTag)
-                }
-                refreshContentAll(it)
-            }
-
-            R.id.menu_del_h_tag -> ReadBook.book?.let {
-                item.isChecked = !item.isChecked
-                if (item.isChecked) {
-                    it.addDelTag(Book.hTag)
-                } else {
-                    it.removeDelTag(Book.hTag)
-                }
-                refreshContentAll(it)
-            }
-
-            R.id.menu_page_anim -> showPageAnimConfig {
-                binding.readView.upPageAnim()
-                ReadBook.loadContent(false)
-            }
-
-            R.id.menu_log -> showDialogFragment<AppLogDialog>()
-            R.id.menu_toc_regex -> showDialogFragment(
-                TxtTocRuleDialog(ReadBook.book?.tocUrl)
-            )
-
-            R.id.menu_reverse_content -> ReadBook.book?.let {
-                viewModel.reverseContent(it)
-            }
-
-            R.id.menu_set_charset -> showCharsetConfig()
-            R.id.menu_image_style -> {
-                val imgStyles =
-                    arrayListOf(
-                        Book.imgStyleDefault, Book.imgStyleFull, Book.imgStyleText,
-                        Book.imgStyleSingle
-                    )
-                selector(
-                    R.string.image_style,
-                    imgStyles
-                ) { _, index ->
-                    val imageStyle = imgStyles[index]
-                    ReadBook.book?.setImageStyle(imageStyle)
-                    if (imageStyle == Book.imgStyleSingle) {
-                        ReadBook.book?.setPageAnim(0)  // 切换图片样式single后,自动切换为覆盖
-                        binding.readView.upPageAnim()
-                    }
-                    ReadBook.loadContent(false)
-                }
-            }
-
-            R.id.menu_get_progress -> ReadBook.book?.let {
-                viewModel.syncBookProgress(it) { progress ->
-                    sureSyncProgress(progress)
-                }
-            }
-
-            R.id.menu_cover_progress -> ReadBook.book?.let {
-                ReadBook.uploadProgress(true) { toastOnUi(R.string.upload_book_success) }
-            }
-
-            R.id.menu_same_title_removed -> {
-                ReadBook.book?.let {
-                    val contentProcessor = ContentProcessor.get(it)
-                    val textChapter = ReadBook.curTextChapter
-                    if (textChapter != null
-                        && !textChapter.sameTitleRemoved
-                        && !contentProcessor.removeSameTitleCache.contains(
-                            textChapter.chapter.getFileName("nr")
-                        )
-                    ) {
-                        toastOnUi("未找到可移除的重复标题")
-                    }
-                }
-                viewModel.reverseRemoveSameTitle()
-            }
-
-            R.id.menu_effective_replaces -> showDialogFragment<EffectiveReplacesDialog>()
-
-            R.id.menu_help -> showHelp()
-        }
-        return super.onCompatOptionsItemSelected(item)
-    }
-
-    private fun refreshContentAll(book: Book) {
-        ReadBook.clearTextChapter()
-        binding.readView.upContent()
-        viewModel.refreshContentAll(book)
-    }
-
-    override fun onMenuItemClick(item: MenuItem): Boolean {
-        return onCompatOptionsItemSelected(item)
-    }
-
-    /**
-     * 按键拦截,显示菜单
-     */
-    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
-        val keyCode = event.keyCode
-        val action = event.action
-        val isDown = action == 0
-
-        if (keyCode == KeyEvent.KEYCODE_MENU) {
-            if (isDown && !binding.readMenu.canShowMenu) {
-                binding.readMenu.runMenuIn()
-                return true
-            }
-            if (!isDown && !binding.readMenu.canShowMenu) {
-                binding.readMenu.canShowMenu = true
-                return true
-            }
-        }
-        return super.dispatchKeyEvent(event)
-    }
-
-    /**
-     * 鼠标滚轮事件
-     */
-    override fun onGenericMotionEvent(event: MotionEvent): Boolean {
-        if (0 != (event.source and InputDevice.SOURCE_CLASS_POINTER)) {
-            if (event.action == MotionEvent.ACTION_SCROLL) {
-                val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
-                LogUtils.d("onGenericMotionEvent", "axisValue = $axisValue")
-                // 获得垂直坐标上的滚动方向
-                if (axisValue < 0.0f) { // 滚轮向下滚
-                    mouseWheelPage(PageDirection.NEXT)
-                } else { // 滚轮向上滚
-                    mouseWheelPage(PageDirection.PREV)
-                }
-                return true
-            }
-        }
-        return super.onGenericMotionEvent(event)
-    }
-
-    /**
-     * 按键事件
-     */
-    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
-        if (menuLayoutIsVisible) {
-            return super.onKeyDown(keyCode, event)
-        }
-        val longPress = event.repeatCount > 0
-        when {
-            isPrevKey(keyCode) -> {
-                handleKeyPage(PageDirection.PREV, longPress)
-                return true
-            }
-
-            isNextKey(keyCode) -> {
-                handleKeyPage(PageDirection.NEXT, longPress)
-                return true
-            }
-        }
-        when (keyCode) {
-            KeyEvent.KEYCODE_VOLUME_UP -> if (volumeKeyPage(PageDirection.PREV, longPress)) {
-                return true
-            }
-
-            KeyEvent.KEYCODE_VOLUME_DOWN -> if (volumeKeyPage(PageDirection.NEXT, longPress)) {
-                return true
-            }
-
-            KeyEvent.KEYCODE_PAGE_UP -> {
-                handleKeyPage(PageDirection.PREV, longPress)
-                return true
-            }
-
-            KeyEvent.KEYCODE_PAGE_DOWN -> {
-                handleKeyPage(PageDirection.NEXT, longPress)
-                return true
-            }
-
-            KeyEvent.KEYCODE_SPACE -> {
-                handleKeyPage(PageDirection.NEXT, longPress)
-                return true
-            }
-        }
-
-        return super.onKeyDown(keyCode, event)
-    }
-
-    /**
-     * 松开按键事件
-     */
-    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
-        when (keyCode) {
-            KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
-                if (volumeKeyPage(PageDirection.NONE, false)) {
-                    return true
-                }
-            }
-
-        }
-        return super.onKeyUp(keyCode, event)
-    }
-
-    /**
-     * view触摸,文字选择
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    override fun onTouch(v: View, event: MotionEvent): Boolean = binding.run {
-        if (!binding.readView.isTextSelected) {
-            return false
-        }
-        when (event.action) {
-            MotionEvent.ACTION_DOWN -> textActionMenu.dismiss()
-            MotionEvent.ACTION_MOVE -> {
-                when (v.id) {
-                    R.id.cursor_left -> if (!readView.curPage.getReverseStartCursor()) {
-                        readView.curPage.selectStartMove(
-                            event.rawX + cursorLeft.width,
-                            event.rawY - cursorLeft.height
-                        )
-                    } else {
-                        readView.curPage.selectEndMove(
-                            event.rawX - cursorRight.width,
-                            event.rawY - cursorRight.height
-                        )
-                    }
-
-                    R.id.cursor_right -> if (readView.curPage.getReverseEndCursor()) {
-                        readView.curPage.selectStartMove(
-                            event.rawX + cursorLeft.width,
-                            event.rawY - cursorLeft.height
-                        )
-                    } else {
-                        readView.curPage.selectEndMove(
-                            event.rawX - cursorRight.width,
-                            event.rawY - cursorRight.height
-                        )
-                    }
-                }
-            }
-
-            MotionEvent.ACTION_UP -> {
-                readView.curPage.resetReverseCursor()
-                showTextActionMenu()
-            }
-        }
-        return true
-    }
-
-    /**
-     * 更新文字选择开始位置
-     */
-    override fun upSelectedStart(x: Float, y: Float, top: Float): Unit = binding.run {
-        cursorLeft.x = x - cursorLeft.width
-        cursorLeft.y = y
-        cursorLeft.visible(true)
-        textMenuPosition.x = x
-        textMenuPosition.y = top
-
-        root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
-    }
-
-    /**
-     * 更新文字选择结束位置
-     */
-    override fun upSelectedEnd(x: Float, y: Float): Unit = binding.run {
-        cursorRight.x = x
-        cursorRight.y = y
-        cursorRight.visible(true)
-
-        root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
-    }
-
-    /**
-     * 取消文字选择
-     */
-    override fun onCancelSelect() = binding.run {
-        cursorLeft.invisible()
-        cursorRight.invisible()
-        textActionMenu.dismiss()
-    }
-
-    override fun onLongScreenshotTouchEvent(event: MotionEvent): Boolean {
-        return binding.readView.onTouchEvent(event)
-    }
-
-    /**
-     * 显示文本操作菜单
-     */
-    override fun showTextActionMenu() {
-        val navigationBarHeight =
-            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)
-                binding.navigationBar.height else 0
-        textActionMenu.show(
-            binding.textMenuPosition,
-            binding.root.height + navigationBarHeight,
-            binding.textMenuPosition.x.toInt(),
-            binding.textMenuPosition.y.toInt(),
-            binding.cursorLeft.y.toInt() + binding.cursorLeft.height,
-            binding.cursorRight.x.toInt(),
-            binding.cursorRight.y.toInt() + binding.cursorRight.height
-        )
-    }
-
-    /**
-     * 当前选择的文本
-     */
-    override val selectedText: String get() = binding.readView.getSelectText()
-
-    /**
-     * 文本选择菜单操作
-     */
-    override fun onMenuItemSelected(itemId: Int): Boolean {
-        when (itemId) {
-            R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) {
-                1 -> lifecycleScope.launch {
-                    binding.readView.aloudStartSelect()
-                }
-
-                else -> speak(binding.readView.getSelectText())
-            }
-
-            R.id.menu_bookmark -> binding.readView.curPage.let {
-                val bookmark = it.createBookmark()
-                if (bookmark == null) {
-                    toastOnUi(R.string.create_bookmark_error)
-                } else {
-                    showDialogFragment(BookmarkDialog(bookmark))
-                }
-                return true
-            }
-
-            R.id.menu_edit -> {
-                val startPos = ReadBook.durChapterPos
-                showDialogFragment<ContentEditDialog> {
-                    putInt("start_position", startPos)
-                    putString("selected_text", binding.readView.getSelectText())
-                }
-                return true
-            }
-
-            R.id.menu_replace -> {
-                val scopes = arrayListOf<String>()
-                ReadBook.book?.name?.let { scopes.add(it) }
-                ReadBook.bookSource?.bookSourceUrl?.let { scopes.add(it) }
-
-                val text = selectedText.lineSequence().map { it.trim() }.joinToString("\n")
-
-                val editRoute = ReplaceEditRoute(
-                    id = -1,
-                    pattern = text,
-                    scope = scopes.joinToString(";"),
-                    isScopeTitle = false,
-                    isScopeContent = true
-                )
-
-                replaceActivity.launch(
-                    ReplaceRuleActivity.startIntent(
-                        context = this,
-                        editRoute = editRoute
-                    )
-                )
-                return true
-            }
-
-            R.id.menu_search_content -> {
-                viewModel.searchContentQuery = selectedText
-                openSearchActivity(selectedText)
-                return true
-            }
-
-            R.id.menu_dict -> {
-                showDialogFragment(DictDialog(selectedText))
-                return true
-            }
-        }
-        return false
-    }
-
-    private fun onEditTextAction(selectedText: String, startPos: Int) {
-        val bundle = Bundle().apply {
-            putString("selected_text", selectedText)
-            putInt("start_position", startPos)
-        }
-
-        val dialog = ContentEditDialog().apply {
-            arguments = bundle
-        }
-
-        dialog.show(supportFragmentManager, "ContentEditDialog")
-    }
-
-    /**
-     * 文本选择菜单操作完成
-     */
-    override fun onMenuActionFinally() = binding.run {
-        textActionMenu.dismiss()
-        readView.cancelSelect()
-    }
-
-    private fun speak(text: String) {
-        if (tts == null) {
-            tts = TTS()
-        }
-        tts?.speak(text)
-    }
-
-    /**
-     * 鼠标滚轮翻页
-     */
-    private fun mouseWheelPage(direction: PageDirection) {
-        if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) {
-            return
-        }
-        keyPageDebounce(direction, mouseWheel = true, longPress = false)
-    }
-
-    /**
-     * 音量键翻页
-     */
-    private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {
-        if (!AppConfig.volumeKeyPage) {
-            return false
-        }
-        if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
-            return false
-        }
-        handleKeyPage(direction, longPress)
-        return true
-    }
-
-    private fun handleKeyPage(direction: PageDirection, longPress: Boolean) {
-        if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
-            keyPage(direction)
-        } else {
-            keyPageDebounce(direction, longPress = longPress)
-        }
-    }
-
-    private fun keyPageDebounce(
-        direction: PageDirection,
-        mouseWheel: Boolean = false,
-        longPress: Boolean
-    ) {
-        if (longPress) {
-            return
-        }
-        nextPageDebounce.apply {
-            wait = if (mouseWheel) 200L else 600L
-            leading = !mouseWheel
-            trailing = mouseWheel
-        }
-        prevPageDebounce.apply {
-            wait = if (mouseWheel) 200L else 600L
-            leading = !mouseWheel
-            trailing = mouseWheel
-        }
-        when (direction) {
-            PageDirection.NEXT -> nextPageDebounce.invoke()
-            PageDirection.PREV -> prevPageDebounce.invoke()
-            else -> {}
-        }
-    }
-
-    private fun keyPage(direction: PageDirection) {
-        binding.readView.cancelSelect()
-        binding.readView.pageDelegate?.isCancel = false
-        binding.readView.pageDelegate?.keyTurnPage(direction)
-    }
-
-    override fun upMenuView() {
-        handler.post {
-            upMenu()
-            binding.readMenu.upBookView()
-        }
-    }
-
-    override fun loadChapterList(book: Book) {
-        ReadBook.upMsg(getString(R.string.toc_updateing))
-        viewModel.loadChapterList(book)
-    }
-
-    /**
-     * 内容加载完成
-     */
-    override fun contentLoadFinish() {
-        if (intent.getBooleanExtra("readAloud", false)) {
-            intent.removeExtra("readAloud")
-            ReadBook.readAloud()
-        }
-        loadStates = true
-    }
-
-    /**
-     * 更新内容
-     */
-    override fun upContent(
-        relativePosition: Int,
-        resetPageOffset: Boolean,
-        success: (() -> Unit)?
-    ) {
-        lifecycleScope.launch {
-            binding.readView.upContent(relativePosition, resetPageOffset)
-            if (relativePosition == 0) {
-                upSeekBarProgress()
-            }
-            loadStates = false
-            success?.invoke()
-        }
-    }
-
-    override suspend fun upContentAwait(
-        relativePosition: Int,
-        resetPageOffset: Boolean,
-        success: (() -> Unit)?
-    ) = withContext(Main.immediate) {
-        binding.readView.upContent(relativePosition, resetPageOffset)
-        if (relativePosition == 0) {
-            upSeekBarProgress()
-        }
-        loadStates = false
-    }
-
-    override fun upPageAnim(upRecorder: Boolean) {
-        lifecycleScope.launch {
-            binding.readView.upPageAnim(upRecorder)
-        }
-    }
-
-    override fun notifyBookChanged() {
-        bookChanged = true
-        if (!ReadBook.inBookshelf) {
-            viewModel.removeFromBookshelf { super.finish() }
-        }
-    }
-
-    override fun cancelSelect() {
-        runOnUiThread {
-            binding.readView.cancelSelect()
-        }
-    }
-
-    /**
-     * 页面改变
-     */
-    override fun pageChanged() {
-        pageChanged = true
-        binding.readView.onPageChange()
-        handler.post {
-            upSeekBarProgress()
-        }
-        executor.execute {
-            startBackupJob()
-        }
-    }
-
-    /**
-     * 更新进度条位置
-     */
-    private fun upSeekBarProgress() {
-        val progress = when (AppConfig.progressBarBehavior) {
-            "page" -> ReadBook.durPageIndex
-            else /* chapter */ -> ReadBook.durChapterIndex
-        }
-        if (progress >= 0) {
-            binding.readMenu.setSeekPage(progress)
-        }
-    }
-
-    /**
-     * 显示菜单
-     */
-    override fun showMenuBar() {
-        binding.readMenu.runMenuIn()
-    }
-
-    override val oldBook: Book?
-        get() = ReadBook.book
-
-    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
-        if (!book.isAudio) {
-            viewModel.changeTo(book, toc)
-        } else {
-            ReadAloud.stop(this)
-            lifecycleScope.launch {
-                withContext(IO) {
-                    ReadBook.book?.migrateTo(book, toc)
-                    book.removeType(BookType.updateError)
-                    ReadBook.book?.delete()
-                    appDb.bookDao.insert(book)
-                }
-                startActivityForBook(book)
-                finish()
-            }
-        }
-    }
-
-    override fun replaceContent(content: String) {
-        ReadBook.book?.let {
-            viewModel.saveContent(it, content)
-        }
-    }
-
-    override fun showActionMenu() {
-        when {
-            BaseReadAloudService.isRun -> showReadAloudDialog()
-            isAutoPage -> showDialogFragment<AutoReadDialog>()
-            isShowingSearchResult -> binding.searchMenu.runMenuIn()
-            else -> binding.readMenu.runMenuIn()
-        }
-    }
-
-    /**
-     * 显示朗读菜单
-     */
-    override fun showReadAloudDialog() {
-        showDialogFragment<ReadAloudDialog>()
-    }
-
-    /**
-     * 自动翻页
-     */
-    override fun autoPage() {
-        ReadAloud.stop(this)
-        if (isAutoPage) {
-            autoPageStop()
-        } else {
-            binding.readView.autoPager.start()
-            binding.readMenu.setAutoPage(true)
-            screenTimeOut = -1L
-            screenOffTimerStart()
-        }
-    }
-
-    override fun autoPageStop() {
-        if (isAutoPage) {
-            binding.readView.autoPager.stop()
-            binding.readMenu.setAutoPage(false)
-            dismissDialogFragment<AutoReadDialog>()
-            upScreenTimeOut()
-        }
-    }
-
-    override fun openSourceEditActivity() {
-        ReadBook.bookSource?.let {
-            sourceEditActivity.launch {
-                putExtra("sourceUrl", it.bookSourceUrl)
-            }
-        }
-    }
-
-    override fun openBookInfoActivity() {
-        ReadBook.book?.let {
-            bookInfoActivity.launch {
-                putExtra("name", it.name)
-                putExtra("author", it.author)
-                putExtra("bookUrl", it.bookUrl)
-            }
-        }
-    }
-
-    /**
-     * 替换
-     */
-    override fun openReplaceRule() {
-        replaceActivity.launch(Intent(this, ReplaceRuleActivity::class.java))
-    }
-
-    /**
-     * 打开目录
-     */
-    override fun openChapterList() {
-        ReadBook.book?.let {
-            tocActivity.launch(it.bookUrl)
-        }
-    }
-
-    /**
-     * 打开搜索界面
-     */
-    override fun openSearchActivity(searchWord: String?) {
-        val book = ReadBook.book ?: return
-        searchContentActivity.launch {
-            putExtra("bookUrl", book.bookUrl)
-            putExtra("searchWord", searchWord)
-            putExtra("searchResultIndex", viewModel.searchResultIndex)
-            viewModel.searchResultList?.first()?.let {
-                if (it.query == viewModel.searchContentQuery) {
-                    IntentData.put("searchResultList", viewModel.searchResultList)
-                }
-            }
-        }
-    }
-
-    /**
-     * 禁用书源
-     */
-    override fun disableSource() {
-        viewModel.disableSource()
-    }
-
-    /**
-     * 显示阅读样式配置
-     */
-//    override fun showReadStyle() {
-//        showDialogFragment<ReadStyleDialog>()
-//    }
-//
-//    /**
-//     * 显示更多设置
-//     */
-//    override fun showMoreSetting() {
-//        showDialogFragment<MoreConfigDialog>()
-//    }
-//
-    override fun showSearchSetting() {
-        showDialogFragment<ReadStyleDialog>()
-    }
-
-    override fun showReadStyle() {
-        showDialogFragment<ReadStyleDialog>()
-    }
-
-    /**
-     * 更新状态栏,导航栏
-     */
-    override fun upSystemUiVisibility() {
-        upSystemUiVisibility(isInMultiWindow, !menuLayoutIsVisible)
-        upNavigationBarColor()
-    }
-
-    // 退出全文搜索
-    override fun exitSearchMenu() {
-        if (isShowingSearchResult) {
-            isShowingSearchResult = false
-            binding.searchMenu.invalidate()
-            binding.searchMenu.invisible()
-            ReadBook.clearSearchResult()
-            binding.readView.cancelSelect(true)
-        }
-    }
-
-    /* 恢复到 全文搜索/进度条跳转前的位置 */
-    private fun restoreLastBookProcess() {
-        if (confirmRestoreProcess == true) {
-            ReadBook.restoreLastBookProgress()
-        } else if (confirmRestoreProcess == null) {
-            alert(R.string.draw) {
-                setMessage(R.string.restore_last_book_process)
-                yesButton {
-                    confirmRestoreProcess = true
-                    ReadBook.restoreLastBookProgress() //恢复启动全文搜索前的进度
-                }
-                noButton {
-                    ReadBook.lastBookProgress = null
-                    confirmRestoreProcess = false
-                }
-                onCancelled {
-                    ReadBook.lastBookProgress = null
-                    confirmRestoreProcess = false
-                }
-            }
-        }
-    }
-
-    override fun showLogin() {
-        ReadBook.bookSource?.let {
-            startActivity<SourceLoginActivity> {
-                putExtra("bookType", BookType.text)
-            }
-        }
-    }
-
-    override fun payAction() {
-        val book = ReadBook.book ?: return
-        if (book.isLocal) return
-        val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-        if (chapter == null) {
-            toastOnUi("no chapter")
-            return
-        }
-        alert(R.string.chapter_pay) {
-            setMessage(chapter.title)
-            yesButton {
-                Coroutine.async(lifecycleScope) {
-                    val source =
-                        ReadBook.bookSource ?: throw NoStackTraceException("no book source")
-                    val payAction = source.getContentRule().payAction
-                    if (payAction.isNullOrBlank()) {
-                        throw NoStackTraceException("no pay action")
-                    }
-                    val analyzeRule = AnalyzeRule(book, source)
-                    analyzeRule.setCoroutineContext(coroutineContext)
-                    analyzeRule.setBaseUrl(chapter.url)
-                    analyzeRule.setChapter(chapter)
-                    analyzeRule.evalJS(payAction).toString()
-                }.onSuccess(IO) {
-                    if (it.isAbsUrl()) {
-                        startActivity<WebViewActivity> {
-                            val bookSource = ReadBook.bookSource
-                            putExtra("title", getString(R.string.chapter_pay))
-                            putExtra("url", it)
-                            putExtra("sourceOrigin", bookSource?.bookSourceUrl)
-                            putExtra("sourceName", bookSource?.bookSourceName)
-                            putExtra("sourceType", bookSource?.getSourceType())
-                        }
-                    } else if (it.isTrue()) {
-                        //购买成功后刷新目录
-                        ReadBook.book?.let {
-                            ReadBook.curTextChapter = null
-                            BookHelp.delContent(book, chapter)
-                            loadChapterList(book)
-                        }
-                    }
-                }.onError {
-                    AppLog.put("执行购买操作出错\n${it.localizedMessage}", it, true)
-                }
-            }
-            noButton()
-        }
-    }
-
-    /**
-     * 朗读按钮
-     */
-    override fun onClickReadAloud() {
-        autoPageStop()
-        when {
-            !BaseReadAloudService.isRun -> {
-                ReadAloud.upReadAloudClass()
-                val scrollPageAnim = ReadBook.pageAnim() == 3
-                if (scrollPageAnim) {
-                    val pos = binding.readView.getReadAloudPos()
-                    if (pos != null) {
-                        val (index, line) = pos
-                        if (ReadBook.durChapterIndex != index) {
-                            ReadBook.openChapter(index, line.chapterPosition, false) {
-                                ReadBook.readAloud(startPos = line.pagePosition)
-                            }
-                        } else {
-                            ReadBook.durChapterPos = line.chapterPosition
-                            ReadBook.readAloud(startPos = line.pagePosition)
-                        }
-                    } else {
-                        ReadBook.readAloud()
-                    }
-                } else {
-                    ReadBook.readAloud()
-                }
-            }
-
-            BaseReadAloudService.pause -> {
-                val scrollPageAnim = ReadBook.pageAnim() == 3
-                if (scrollPageAnim && pageChanged) {
-                    pageChanged = false
-                    val pos = binding.readView.getReadAloudPos()
-                    if (pos != null) {
-                        val (index, line) = pos
-                        if (ReadBook.durChapterIndex != index) {
-                            ReadBook.openChapter(index, line.chapterPosition, false) {
-                                ReadBook.readAloud(startPos = line.pagePosition)
-                            }
-                        } else {
-                            ReadBook.durChapterPos = line.chapterPosition
-                            ReadBook.readAloud(startPos = line.pagePosition)
-                        }
-                    } else {
-                        ReadBook.readAloud()
-                    }
-                } else {
-                    ReadAloud.resume(this)
-                }
-            }
-
-            else -> ReadAloud.pause(this)
-        }
-    }
-
-    override fun showHelp() {
-        showHelp("readMenuHelp")
-    }
-
-    /**
-     * 点击图片
-     */
-    override fun oldClickImg(src: String): Boolean {
-        val urlMatcher = paramPattern.matcher(src)
-        if (urlMatcher.find()) {
-            val urlOptionStr = src.substring(urlMatcher.end())
-            val urlOptionMap = GSON.fromJsonObject<Map<String, String>>(urlOptionStr).getOrNull()
-            val click = urlOptionMap?.get("click")
-            if (click != null) {
-                Coroutine.async(lifecycleScope, IO) {
-                    val source = ReadBook.bookSource ?: return@async
-                    val java = SourceLoginJsExtensions(this@ReadBookActivity, source, BookType.text)
-                    val book = ReadBook.book ?: return@async
-                    val chapter =
-                        appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-                            ?: throw Exception("no find chapter")
-                    runScriptWithContext {
-                        source.evalJS(click) {
-                            put("java", java)
-                            put("book", book)
-                            put("chapter", chapter)
-                            put("result", src)
-                        }
-                    }
-                }.onError {
-                    AppLog.put("执行图片链接click键值出错\n${it.localizedMessage}", it, true)
-                }
-                return true
-            }
-            val jsStr = urlOptionMap?.get("js") ?: return false
-            Coroutine.async(lifecycleScope, IO) {
-                val source = ReadBook.bookSource ?: return@async
-                val book = ReadBook.book ?: return@async
-                val chapter =
-                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-                        ?: throw Exception("no find chapter")
-                val urlNoOption = src.take(urlMatcher.start())
-                AnalyzeRule(book, source).apply {
-                    setCoroutineContext(coroutineContext)
-                    setBaseUrl(chapter.url)
-                    setChapter(chapter)
-                    evalJS(jsStr, urlNoOption)
-                }
-            }.onError {
-                AppLog.put("执行图片链接js键值出错\n${it.localizedMessage}", it, true)
-            }
-            return true
-        }
-        return false
-    }
-
-    override fun clickImg(click: String, src: String) {
-        Coroutine.async(lifecycleScope, IO) {
-            val source = ReadBook.bookSource ?: return@async
-            val java = SourceLoginJsExtensions(this@ReadBookActivity, source, BookType.text)
-            val book = ReadBook.book ?: return@async
-            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
-                ?: throw Exception("no find chapter")
-            runScriptWithContext {
-                source.evalJS(click) {
-                    put("java", java)
-                    put("book", book)
-                    put("chapter", chapter)
-                    put("result", src)
-                }
-            }
-        }.onError {
-            AppLog.put("执行图片链接click键值出错\n${it.localizedMessage}", it, true)
-        }
-    }
-
-    /**
-     * 长按图片
-     */
-    @SuppressLint("RtlHardcoded")
-    override fun onImageLongPress(x: Float, y: Float, src: String) {
-        binding.root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
-        popupAction.setItems(
-            listOf(
-                SelectItem(getString(R.string.show), "show"),
-                SelectItem(getString(R.string.refresh), "refresh"),
-                SelectItem("保存到相册", "save"),
-                SelectItem(getString(R.string.menu), "menu"),
-            )
-        )
-        popupAction.onActionClick = {
-            when (it) {
-                "show" -> showDialogFragment(PhotoDialog(src))
-                "refresh" -> viewModel.refreshImage(src)
-                "save" -> {
-                    viewModel.saveImage(src)
-                }
-                "menu" -> showActionMenu()
-            }
-            popupAction.dismiss()
-        }
-        val navigationBarHeight =
-            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)
-                binding.navigationBar.height else 0
-        popupAction.showAtLocation(
-            binding.readView, Gravity.BOTTOM or Gravity.LEFT, x.toInt(),
-            binding.root.height + navigationBarHeight - y.toInt()
-        )
-    }
-
-    /**
-     * colorSelectDialog
-     */
-    override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run {
-        when (dialogId) {
-            S_COLOR -> {
-                setCurShadColor(color)
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
-            }
-
-            TEXT_COLOR -> {
-                setCurTextColor(color)
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
-                if (AppConfig.readBarStyleFollowPage) {
-                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
-                }
-            }
-
-            TEXT_ACCENT_COLOR -> {
-                setCurTextAccentColor(color)
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
-                if (AppConfig.readBarStyleFollowPage) {
-                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
-                }
-            }
-
-            BG_COLOR -> {
-                setCurBg(0, "#${color.hexString}")
-                postEvent(EventBus.UP_CONFIG, arrayListOf(1))
-                if (AppConfig.readBarStyleFollowPage) {
-                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
-                }
-            }
-
-            TIP_COLOR -> {
-                ReadTipConfig.tipColor = color
-                postEvent(EventBus.TIP_COLOR, "")
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
-            }
-
-            TIP_DIVIDER_COLOR -> {
-                ReadTipConfig.tipDividerColor = color
-                postEvent(EventBus.TIP_COLOR, "")
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
-            }
-
-            B_COLOR -> {
-                setMenuCurBg(color)
-                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
-            }
-
-            A_COLOR -> {
-                setMenuCurAc(color)
-                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
-            }
-
-            U_COLOR -> {
-                setUnderlineColor(color)
-                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
-                postEvent(EventBus.UP_CONFIG, arrayListOf(6, 9, 11))
-            }
-        }
-    }
-
-    /**
-     * colorSelectDialog
-     */
-    override fun onDialogDismissed(dialogId: Int) = Unit
-
-    override fun onTocRegexDialogResult(tocRegex: String) {
-        ReadBook.book?.let {
-            it.tocUrl = tocRegex
-            loadChapterList(it)
-        }
-    }
-
-    private fun sureSyncProgress(progress: BookProgress) {
-        alert(R.string.get_book_progress) {
-            setMessage(R.string.current_progress_exceeds_cloud)
-            okButton {
-                ReadBook.setProgress(progress)
-            }
-            noButton()
-        }
-    }
-
-    /* 进度条跳转到指定章节 */
-    override fun skipToChapter(index: Int) {
-        ReadBook.saveCurrentBookProgress() //退出章节跳转恢复此时进度
-        viewModel.openChapter(index)
-    }
-
-    /* 全文搜索跳转 */
-    override fun navigateToSearch(searchResult: SearchResult, index: Int) {
-        viewModel.searchResultIndex = index
-        skipToSearch(searchResult)
-    }
-
-    override fun onMenuShow() {
-        binding.readView.autoPager.pause()
-    }
-
-    override fun onMenuHide() {
-        binding.readView.autoPager.resume()
-    }
-
-    override fun refresh() {
-        recreate()
-    }
-
-    override fun onLayoutPageCompleted(index: Int, page: TextPage) {
-        upSeekBarThrottle.invoke()
-        binding.readView.onLayoutPageCompleted(index, page)
-    }
-
-    /* 全文搜索跳转 */
-    private fun skipToSearch(searchResult: SearchResult) {
-        if (searchResult.chapterIndex != ReadBook.durChapterIndex) {
-            viewModel.openChapter(searchResult.chapterIndex) {
-                jumpToPosition(searchResult)
-            }
-        } else {
-            jumpToPosition(searchResult)
-        }
-    }
-
-    private fun jumpToPosition(searchResult: SearchResult) {
-        val curTextChapter = ReadBook.curTextChapter ?: return
-        binding.searchMenu.updateSearchInfo()
-        val (pageIndex, lineIndex, charIndex, addLine, charIndex2) =
-            viewModel.searchResultPositions(curTextChapter, searchResult)
-        ReadBook.skipToPage(pageIndex) {
-            isSelectingSearchResult = true
-            binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex)
-            when (addLine) {
-                0 -> binding.readView.curPage.selectEndMoveIndex(
-                    0,
-                    lineIndex,
-                    charIndex + viewModel.searchContentQuery.length - 1
-                )
-
-                1 -> binding.readView.curPage.selectEndMoveIndex(
-                    0, lineIndex + 1, charIndex2
-                )
-                //consider change page, jump to scroll position
-                -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2)
-            }
-            binding.readView.isTextSelected = true
-            isSelectingSearchResult = false
-        }
-    }
-
-    override fun addBookmark() {
-        val book = ReadBook.book
-        val page = ReadBook.curTextChapter?.getPage(ReadBook.durPageIndex)
-        if (book != null && page != null) {
-            val bookmark = book.createBookMark().apply {
-                chapterIndex = ReadBook.durChapterIndex
-                chapterPos = ReadBook.durChapterPos
-                chapterName = page.title
-                bookText = page.text.replace(Regex("[袮꧁]"), "").trim()
-            }
-            showDialogFragment(BookmarkDialog(bookmark))
-        }
-    }
-
-    override fun changeReplaceRuleState() {
-        binding.readMenu.runMenuOut()
-        ReadBook.book?.let {
-            it.setUseReplaceRule(!it.getUseReplaceRule())
-            ReadBook.saveRead()
-            menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.getUseReplaceRule()
-            binding.readMenu.changeReplace(it.getUseReplaceRule())
-            viewModel.replaceRuleChanged()
-        }
-    }
-
-    private fun startBackupJob() {
-        backupJob?.cancel()
-        backupJob = lifecycleScope.launch(IO) {
-            delay(300000)
-            ReadBook.book?.let {
-                AppWebDav.uploadBookProgress(it)
-                ensureActive()
-                it.update()
-                Backup.autoBack(this@ReadBookActivity)
-            }
-        }
-    }
-
-    override fun sureNewProgress(progress: BookProgress) {
-        syncDialog?.dismiss()
-        syncDialog = alert(R.string.get_book_progress) {
-            setMessage(R.string.cloud_progress_exceeds_current)
-            okButton {
-                ReadBook.setProgress(progress)
-            }
-            noButton()
-        }
-    }
-
-    override fun onDestroy() {
-        super.onDestroy()
-        tts?.clearTts()
-        textActionMenu.dismiss()
-        popupAction.dismiss()
-        binding.readView.onDestroy()
-        ReadBook.unregister(this)
-        if (!ReadBook.inBookshelf && !isChangingConfigurations) {
-            viewModel.removeFromBookshelf(null)
-        }
-        if (!BuildConfig.DEBUG) {
-            Backup.autoBack(this)
-        }
-    }
-
-    override fun observeLiveBus() = binding.run {
-        observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() }
-        observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) }
-        observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {
-            if (it) {
-                onClickReadAloud()
-            } else {
-                ReadBook.readAloud(!BaseReadAloudService.pause)
-            }
-        }
-        observeEvent<ArrayList<Int>>(EventBus.UP_CONFIG) {
-            it.forEach { value ->
-                when (value) {
-                    0 -> upSystemUiVisibility()
-                    1 -> readView.upBg()
-                    2 -> {
-                        readView.upStyle()
-                        binding.readMenu.reset()
-                    }
-                    3 -> readView.upBgAlpha()
-                    4 -> readView.upPageSlopSquare()
-                    5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)
-                    6 -> readView.upContent(resetPageOffset = false)
-                    8 -> ChapterProvider.upStyle()
-                    9 -> readView.invalidateTextPage()
-                    10 -> ChapterProvider.upLayout()
-                    11 -> readView.submitRenderTask()
-                }
-            }
-        }
-        observeEvent<Int>(EventBus.ALOUD_STATE) {
-            if (it == Status.STOP || it == Status.PAUSE) {
-                ReadBook.curTextChapter?.let { textChapter ->
-                    val page = textChapter.getPageByReadPos(ReadBook.durChapterPos)
-                    if (page != null) {
-                        page.removePageAloudSpan()
-                        readView.upContent(resetPageOffset = false)
-                    }
-                }
-            }
-        }
-        observeEventSticky<Int>(EventBus.TTS_PROGRESS) { chapterStart ->
-            lifecycleScope.launch(IO) {
-                if (BaseReadAloudService.isPlay()) {
-                    ReadBook.curTextChapter?.let { textChapter ->
-                        val pageIndex = ReadBook.durPageIndex
-                        val aloudSpanStart = chapterStart - textChapter.getReadLength(pageIndex)
-                        textChapter.getPage(pageIndex)
-                            ?.upPageAloudSpan(aloudSpanStart)
-                        upContent()
-                    }
-                }
-            }
-        }
-        observeEvent<Boolean>(PreferKey.keepLight) {
-            upScreenTimeOut()
-        }
-        observeEvent<Boolean>(PreferKey.textSelectAble) {
-            readView.curPage.upSelectAble(it)
-        }
-        observeEvent<String>(PreferKey.showBrightnessView) {
-            readMenu.upBrightnessState()
-        }
-        observeEvent<List<SearchResult>>(EventBus.SEARCH_RESULT) {
-            viewModel.searchResultList = it
-        }
-        observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) {
-            readMenu.reset()
-        }
-        observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {
-            readMenu.upSeekBar()
-        }
-        observeEvent<Boolean>(EventBus.REFRESH_BOOK_CONTENT) {
-            ReadBook.book?.let {
-                ReadBook.curTextChapter = null
-                binding.readView.upContent()
-                viewModel.refreshContentDur(it)
-            }
-        }
-    }
-
-    private fun upScreenTimeOut() {
-        val keepLightPrefer = getPrefString(PreferKey.keepLight)?.toInt() ?: 0
-        screenTimeOut = keepLightPrefer * 1000L
-        screenOffTimerStart()
-    }
-
-    /**
-     * 重置黑屏时间
-     */
-    override fun screenOffTimerStart() {
-        handler.post {
-            if (screenTimeOut < 0) {
-                keepScreenOn(true)
-                return@post
-            }
-            val t = screenTimeOut - sysScreenOffTime
-            if (t > 0) {
-                keepScreenOn(true)
-                handler.removeCallbacks(screenOffRunnable)
-                handler.postDelayed(screenOffRunnable, screenTimeOut)
-            } else {
-                keepScreenOn(false)
-            }
-        }
-    }
-
-    override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
-        viewModel.addToBookshelf(book, toc) {
-            toastOnUi("已添加到书架")
-        }
-    }
-
-    companion object {
-        const val RESULT_DELETED = 100
-    }
-
-}
+package io.legado.app.ui.book.read
+
+//import io.legado.app.lib.theme.accentColor
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.res.Configuration
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.HapticFeedbackConstants
+import android.view.InputDevice
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import androidx.activity.addCallback
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.PopupMenu
+import androidx.core.view.get
+import androidx.core.view.size
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.transition.platform.MaterialContainerTransform
+import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
+import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
+import com.script.rhino.runScriptWithContext
+import io.legado.app.BuildConfig
+import io.legado.app.R
+import io.legado.app.constant.AppLog
+import io.legado.app.constant.BookType
+import io.legado.app.constant.EventBus
+import io.legado.app.constant.PreferKey
+import io.legado.app.constant.Status
+import io.legado.app.data.appDb
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookChapter
+import io.legado.app.data.entities.BookProgress
+import io.legado.app.data.entities.BookSource
+import io.legado.app.exception.NoStackTraceException
+import io.legado.app.help.AppWebDav
+import io.legado.app.help.IntentData
+import io.legado.app.help.TTS
+import io.legado.app.help.book.BookHelp
+import io.legado.app.help.book.ContentProcessor
+import io.legado.app.help.book.isAudio
+import io.legado.app.help.book.isEpub
+import io.legado.app.help.book.isLocal
+import io.legado.app.help.book.isLocalTxt
+import io.legado.app.help.book.isMobi
+import io.legado.app.help.book.removeType
+import io.legado.app.help.book.update
+import io.legado.app.help.config.AppConfig
+import io.legado.app.help.config.ReadBookConfig
+import io.legado.app.help.config.ReadTipConfig
+import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.help.source.getSourceType
+import io.legado.app.help.storage.Backup
+import io.legado.app.lib.dialogs.SelectItem
+import io.legado.app.lib.dialogs.alert
+import io.legado.app.lib.dialogs.selector
+import io.legado.app.model.ReadAloud
+import io.legado.app.model.ReadBook
+import io.legado.app.model.analyzeRule.AnalyzeRule
+import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter
+import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
+import io.legado.app.model.analyzeRule.AnalyzeUrl.Companion.paramPattern
+import io.legado.app.model.localBook.EpubFile
+import io.legado.app.model.localBook.MobiFile
+import io.legado.app.receiver.NetworkChangedListener
+import io.legado.app.receiver.TimeBatteryReceiver
+import io.legado.app.service.BaseReadAloudService
+import io.legado.app.ui.about.AppLogDialog
+import io.legado.app.ui.book.bookmark.BookmarkDialog
+import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
+import io.legado.app.ui.book.changesource.ChangeChapterSourceDialog
+import io.legado.app.ui.book.info.BookInfoActivity
+import io.legado.app.ui.book.read.config.AutoReadDialog
+import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
+import io.legado.app.ui.book.read.config.FontConfigDialog
+import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.S_COLOR
+import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.TEXT_ACCENT_COLOR
+import io.legado.app.ui.book.read.config.FontConfigDialog.Companion.TEXT_COLOR
+import io.legado.app.ui.book.read.config.FontSelectDialog
+import io.legado.app.ui.book.read.config.ReadAloudDialog
+import io.legado.app.ui.book.read.config.ReadStyleDialog
+import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.A_COLOR
+import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.B_COLOR
+import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_COLOR
+import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_COLOR
+import io.legado.app.ui.book.read.config.ToolButtonConfigDialog
+import io.legado.app.ui.book.read.config.UnderlineConfigDialog.Companion.U_COLOR
+import io.legado.app.ui.book.read.page.ContentTextView
+import io.legado.app.ui.book.read.page.ReadView
+import io.legado.app.ui.book.read.page.entities.PageDirection
+import io.legado.app.ui.book.read.page.entities.TextPage
+import io.legado.app.ui.book.read.page.provider.ChapterProvider
+import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
+import io.legado.app.ui.book.searchContent.SearchContentActivity
+import io.legado.app.ui.book.searchContent.SearchResult
+import io.legado.app.ui.book.source.edit.BookSourceEditActivity
+import io.legado.app.ui.book.toc.TocActivityResult
+import io.legado.app.ui.book.toc.rule.TxtTocRuleDialog
+import io.legado.app.ui.browser.WebViewActivity
+import io.legado.app.ui.dict.DictDialog
+import io.legado.app.ui.login.SourceLoginActivity
+import io.legado.app.ui.login.SourceLoginJsExtensions
+import io.legado.app.ui.replace.ReplaceEditRoute
+import io.legado.app.ui.replace.ReplaceRuleActivity
+import io.legado.app.ui.widget.PopupAction
+import io.legado.app.ui.widget.dialog.PhotoDialog
+import io.legado.app.utils.Debounce
+import io.legado.app.utils.GSON
+import io.legado.app.utils.LogUtils
+import io.legado.app.utils.NetworkUtils
+import io.legado.app.utils.StartActivityContract
+import io.legado.app.utils.applyOpenTint
+import io.legado.app.utils.buildMainHandler
+import io.legado.app.utils.dismissDialogFragment
+import io.legado.app.utils.fromJsonObject
+import io.legado.app.utils.getPrefBoolean
+import io.legado.app.utils.getPrefString
+import io.legado.app.utils.hexString
+import io.legado.app.utils.iconItemOnLongClick
+import io.legado.app.utils.invisible
+import io.legado.app.utils.isAbsUrl
+import io.legado.app.utils.isTrue
+import io.legado.app.utils.navigationBarGravity
+import io.legado.app.utils.observeEvent
+import io.legado.app.utils.observeEventSticky
+import io.legado.app.utils.postEvent
+import io.legado.app.utils.showDialogFragment
+import io.legado.app.utils.showHelp
+import io.legado.app.utils.startActivity
+import io.legado.app.utils.startActivityForBook
+import io.legado.app.utils.sysScreenOffTime
+import io.legado.app.utils.themeColor
+import io.legado.app.utils.throttle
+import io.legado.app.utils.toastOnUi
+import io.legado.app.utils.visible
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * 阅读界面
+ */
+class ReadBookActivity : BaseReadBookActivity(),
+    View.OnTouchListener,
+    ReadView.CallBack,
+    TextActionMenu.CallBack,
+    ContentTextView.CallBack,
+    PopupMenu.OnMenuItemClickListener,
+    ReadMenu.CallBack,
+    SearchMenu.CallBack,
+    ReadAloudDialog.CallBack,
+    ChangeBookSourceDialog.CallBack,
+    ChangeChapterSourceDialog.CallBack,
+    ReadBook.CallBack,
+    AutoReadDialog.CallBack,
+    ToolButtonConfigDialog.CallBack,
+    TxtTocRuleDialog.CallBack,
+    ColorPickerDialogListener,
+    FontConfigDialog.CallBack,
+    FontSelectDialog.CallBack,
+    LayoutProgressListener {
+
+    private val tocActivity =
+        registerForActivityResult(TocActivityResult()) {
+            it?.let {
+                viewModel.openChapter(it.first, it.second)
+            }
+        }
+    private val sourceEditActivity =
+        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
+            if (it.resultCode == RESULT_OK) {
+                viewModel.upBookSource {
+                    upMenuView()
+                }
+            }
+        }
+    private val replaceActivity =
+        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+            if (it.resultCode == RESULT_OK) {
+                viewModel.replaceRuleChanged()
+            }
+        }
+
+    private val searchContentActivity =
+        registerForActivityResult(StartActivityContract(SearchContentActivity::class.java)) {
+            val data = it.data ?: return@registerForActivityResult
+            val key = data.getLongExtra("key", System.currentTimeMillis())
+            val index = data.getIntExtra("index", 0)
+            val searchResult = IntentData.get<SearchResult>("searchResult$key")
+            val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
+            if (searchResult != null && searchResultList != null) {
+                viewModel.searchContentQuery = searchResult.query
+                binding.searchMenu.upSearchResultList(searchResultList)
+                isShowingSearchResult = true
+                viewModel.searchResultIndex = index
+                binding.searchMenu.updateSearchResultIndex(index)
+                binding.searchMenu.selectedSearchResult?.let { currentResult ->
+                    ReadBook.saveCurrentBookProgress() //退出全文搜索恢复此时进度
+                    skipToSearch(currentResult)
+                    showActionMenu()
+                }
+            }
+        }
+    private val bookInfoActivity =
+        registerForActivityResult(StartActivityContract(BookInfoActivity::class.java)) {
+            if (it.resultCode == RESULT_OK) {
+                setResult(RESULT_DELETED)
+                finish()
+            } else {
+                ReadBook.loadOrUpContent()
+            }
+        }
+    private var menu: Menu? = null
+    private var backupJob: Job? = null
+    private var tts: TTS? = null
+    val textActionMenu: TextActionMenu by lazy {
+        TextActionMenu(this, this)
+    }
+    private val popupAction: PopupAction by lazy {
+        PopupAction(this)
+    }
+
+    // 当前使用的字体路径(给 FontDialog 用)
+    override val curFontPath: String
+        get() = ReadBookConfig.textFont
+
+    // 当选择了字体时被调用
+    override fun selectFont(path: String) {
+        // path 为空表示恢复系统默认字体
+        ReadBookConfig.textFont = path
+        // 通知阅读界面刷新字体
+        postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5, 2))
+    }
+
+    override val isInitFinish: Boolean get() = viewModel.isInitFinish
+    override val isScroll: Boolean get() = binding.readView.isScroll
+    private val isAutoPage get() = binding.readView.isAutoPage
+    override var isShowingSearchResult = false
+    override var isSelectingSearchResult = false
+        set(value) {
+            field = value && isShowingSearchResult
+        }
+    private val timeBatteryReceiver = TimeBatteryReceiver()
+    private var screenTimeOut: Long = 0
+    private var loadStates: Boolean = false
+    override val pageFactory get() = binding.readView.pageFactory
+    override val pageDelegate get() = binding.readView.pageDelegate
+    override val headerHeight: Int get() = binding.readView.curPage.headerHeight
+    override val imgBgPaddingStart: Int get() = binding.readView.curPage.imgBgPaddingStart
+    private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
+    private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
+    private var bookChanged = false
+    private var pageChanged = false
+    private val handler by lazy { buildMainHandler() }
+    private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
+    private val executor = ReadBook.executor
+    private val upSeekBarThrottle = throttle(200) {
+        runOnUiThread {
+            upSeekBarProgress()
+            binding.readMenu.upSeekBar()
+        }
+    }
+
+    //恢复跳转前进度对话框的交互结果
+    private var confirmRestoreProcess: Boolean? = null
+    private val networkChangedListener by lazy {
+        NetworkChangedListener(this)
+    }
+    private var justInitData: Boolean = false
+    private var syncDialog: AlertDialog? = null
+
+    private val delayMillis: Long = 500
+    private var hasLoadedBook = false
+
+    private fun ensureLoadBook() {
+        if (!hasLoadedBook) {
+            hasLoadedBook = true
+            loadBook()
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onCreate(savedInstanceState: Bundle?) {
+        setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
+        setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
+        val transform = MaterialContainerTransform().apply {
+            addTarget(binding.root)
+            scrimColor = Color.TRANSPARENT
+            duration = delayMillis
+        }
+        window.sharedElementEnterTransition = transform
+        window.sharedElementReturnTransition = transform
+
+        super.onCreate(savedInstanceState)
+
+        binding.rootView.transitionName = intent.getStringExtra("transitionName")
+
+        upScreenTimeOut()
+        ReadBook.register(this)
+        binding.readMenu.colorSurfaceContainer =
+            themeColor(com.google.android.material.R.attr.colorSurfaceContainer)
+        binding.readMenu.colorSecondary =
+            themeColor(androidx.appcompat.R.attr.colorPrimary)
+        binding.readMenu.colorSecondaryContainer =
+            themeColor(com.google.android.material.R.attr.colorSecondaryContainer)
+        binding.cursorLeft.setOnTouchListener(this)
+        binding.cursorRight.setOnTouchListener(this)
+
+        onBackPressedDispatcher.addCallback(this) {
+            if (isShowingSearchResult) {
+                exitSearchMenu()
+                restoreLastBookProcess()
+                return@addCallback
+            }
+            //拦截返回供恢复阅读进度
+            if (ReadBook.lastBookProgress != null && confirmRestoreProcess != false) {
+                restoreLastBookProcess()
+                return@addCallback
+            }
+            if (BaseReadAloudService.isPlay()) {
+                ReadAloud.pause(this@ReadBookActivity)
+                toastOnUi(R.string.read_aloud_pause)
+                return@addCallback
+            }
+            if (isAutoPage) {
+                autoPageStop()
+                return@addCallback
+            }
+            if (getPrefBoolean("disableReturnKey") && !menuLayoutIsVisible) {
+                return@addCallback
+            }
+            if (savedInstanceState != null || !ReadBook.inBookshelf) {
+                ReadBook.commitReadSession()
+                finish()
+            } else {
+                ReadBook.commitReadSession()
+                supportFinishAfterTransition()
+            }
+            //TODO: 有关测量相关问题
+            //孩子们,我的水平不够,只能这样修复测量问题了
+        }
+    }
+
+    override fun onPostCreate(savedInstanceState: Bundle?) {
+        super.onPostCreate(savedInstanceState)
+        if (!AppConfig.sharedElementEnterTransitionEnable || !AppConfig.delayBookLoadEnable) {
+            ensureLoadBook()
+        }
+        //虽然很扯但有用
+        binding.rootView.postDelayed({
+            ensureLoadBook()
+        }, delayMillis)
+    }
+
+    override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        viewModel.initData(intent)
+    }
+
+    override fun onWindowFocusChanged(hasFocus: Boolean) {
+        super.onWindowFocusChanged(hasFocus)
+        upSystemUiVisibility()
+        if (hasFocus) {
+            binding.readMenu.upBrightnessState()
+        } else if (!menuLayoutIsVisible) {
+            ReadBook.cancelPreDownloadTask()
+        }
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        //upSystemUiVisibility()
+        binding.readView.upStyle()
+        recreate()
+    }
+
+
+    override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) {
+        if (!isTopResumedActivity) {
+            ReadBook.cancelPreDownloadTask()
+        }
+    }
+
+    @SuppressLint("UnspecifiedRegisterReceiverFlag")
+    override fun onResume() {
+        super.onResume()
+        ReadBook.readStartTime = System.currentTimeMillis()
+        ReadBook.initReadTime()
+        ReadBook.startAutoSaveSession()
+        if (bookChanged) {
+            bookChanged = false
+            ReadBook.callBack = this
+            viewModel.initData(intent)
+            justInitData = true
+        } else {
+            //web端阅读时,app处于阅读界面,本地记录会覆盖web保存的进度,在此处恢复
+            ReadBook.webBookProgress?.let {
+                ReadBook.setProgress(it)
+                ReadBook.webBookProgress = null
+            }
+        }
+        upSystemUiVisibility()
+        registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)
+        binding.readView.upTime()
+        screenOffTimerStart()
+        // 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作)
+        networkChangedListener.register()
+        networkChangedListener.onNetworkChanged = {
+            // 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑)
+            if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
+                ReadBook.syncProgress({ progress -> sureNewProgress(progress) })
+            }
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        autoPageStop()
+        backupJob?.cancel()
+        ReadBook.saveRead()
+        ReadBook.stopAutoSaveSession()
+        ReadBook.commitReadSession()
+        ReadBook.cancelPreDownloadTask()
+        unregisterReceiver(timeBatteryReceiver)
+        upSystemUiVisibility()
+        if (!BuildConfig.DEBUG) {
+            if (AppConfig.syncBookProgressPlus) {
+                ReadBook.syncProgress()
+            } else {
+                ReadBook.uploadProgress()
+            }
+            Backup.autoBack(this)
+        }
+        justInitData = false
+        networkChangedListener.unRegister()
+    }
+
+    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
+        menuInflater.inflate(R.menu.book_read, menu)
+        menu.iconItemOnLongClick(R.id.menu_change_source) {
+            PopupMenu(this, it).apply {
+                inflate(R.menu.book_read_change_source)
+                this.menu.applyOpenTint(this@ReadBookActivity)
+                setOnMenuItemClickListener(this@ReadBookActivity)
+            }.show()
+        }
+        menu.iconItemOnLongClick(R.id.menu_refresh) {
+            PopupMenu(this, it).apply {
+                inflate(R.menu.book_read_refresh)
+                this.menu.applyOpenTint(this@ReadBookActivity)
+                setOnMenuItemClickListener(this@ReadBookActivity)
+            }.show()
+        }
+        binding.readMenu.refreshMenuColorFilter()
+        return super.onCompatCreateOptionsMenu(menu)
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
+        this.menu = menu
+        upMenu()
+        return super.onPrepareOptionsMenu(menu)
+    }
+
+    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
+        menu.findItem(R.id.menu_same_title_removed)?.isChecked =
+            ReadBook.curTextChapter?.sameTitleRemoved == true
+        return super.onMenuOpened(featureId, menu)
+    }
+
+    private fun loadBook() {
+        lifecycleScope.launch(Dispatchers.Default) {
+            viewModel.initReadBookConfig(intent)
+            viewModel.initData(intent)
+            withContext(Main) {
+                justInitData = true
+            }
+        }
+    }
+
+    /**
+     * 更新菜单
+     */
+    private fun upMenu() {
+        val menu = menu ?: return
+        val book = ReadBook.book ?: return
+        val onLine = !book.isLocal
+        for (i in 0 until menu.size) {
+            val item = menu[i]
+            when (item.groupId) {
+                R.id.menu_group_on_line -> item.isVisible = onLine
+                R.id.menu_group_local -> item.isVisible = !onLine
+                R.id.menu_group_text -> item.isVisible = book.isLocalTxt
+                R.id.menu_group_epub -> item.isVisible = book.isEpub
+                else -> when (item.itemId) {
+                    R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
+                    R.id.menu_re_segment -> item.isChecked = book.getReSegment()
+//                    R.id.menu_enable_review -> {
+//                        item.isVisible = BuildConfig.DEBUG
+//                        item.isChecked = AppConfig.enableReview
+//                    }
+
+                    R.id.menu_reverse_content -> item.isVisible = onLine
+                    R.id.menu_del_ruby_tag -> item.isChecked = book.getDelTag(Book.rubyTag)
+                    R.id.menu_del_h_tag -> item.isChecked = book.getDelTag(Book.hTag)
+                }
+            }
+        }
+        lifecycleScope.launch {
+            menu.findItem(R.id.menu_get_progress)?.isVisible = withContext(IO) {
+                AppWebDav.isOk
+            }
+            menu.findItem(R.id.menu_cover_progress)?.isVisible = withContext(IO) {
+                AppWebDav.isOk
+            }
+        }
+    }
+
+    private fun defaultChangeSource() {
+        if (AppConfig.defaultSourceChangeAll)
+        {
+            binding.readMenu.runMenuOut()
+            ReadBook.book?.let {
+                showDialogFragment(ChangeBookSourceDialog(it.name, it.author))
+            }
+        } else {
+            lifecycleScope.launch {
+                val book = ReadBook.book ?: return@launch
+                val chapter =
+                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+                        ?: return@launch
+                binding.readMenu.runMenuOut()
+                showDialogFragment(
+                    ChangeChapterSourceDialog(book.name, book.author, chapter.index, chapter.title)
+                )
+            }
+        }
+    }
+
+    /**
+     * 菜单
+     */
+    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.menu_change_source -> { defaultChangeSource() }
+
+            R.id.menu_book_change_source -> {
+                binding.readMenu.runMenuOut()
+                ReadBook.book?.let {
+                    showDialogFragment(ChangeBookSourceDialog(it.name, it.author))
+                }
+            }
+
+            R.id.menu_chapter_change_source -> lifecycleScope.launch {
+                val book = ReadBook.book ?: return@launch
+                val chapter =
+                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+                        ?: return@launch
+                binding.readMenu.runMenuOut()
+                showDialogFragment(
+                    ChangeChapterSourceDialog(book.name, book.author, chapter.index, chapter.title)
+                )
+            }
+
+            R.id.menu_refresh,
+            R.id.menu_refresh_dur -> {
+                if (ReadBook.bookSource == null) {
+                    upContent()
+                } else {
+                    ReadBook.book?.let {
+                        ReadBook.curTextChapter = null
+                        binding.readView.upContent()
+                        viewModel.refreshContentDur(it)
+                    }
+                }
+            }
+
+            R.id.menu_refresh_after -> {
+                if (ReadBook.bookSource == null) {
+                    upContent()
+                } else {
+                    ReadBook.book?.let {
+                        ReadBook.clearTextChapter()
+                        binding.readView.upContent()
+                        viewModel.refreshContentAfter(it)
+                    }
+                }
+            }
+
+            R.id.menu_refresh_all -> {
+                if (ReadBook.bookSource == null) {
+                    upContent()
+                } else {
+                    ReadBook.book?.let {
+                        refreshContentAll(it)
+                    }
+                }
+            }
+
+            R.id.menu_setting_replace -> openReplaceRule()
+            R.id.menu_download -> showDownloadDialog()
+            R.id.menu_add_bookmark -> addBookmark()
+            R.id.menu_simulated_reading -> showSimulatedReading()
+            R.id.menu_edit_content -> {
+                binding.readMenu.runMenuOut()
+                showDialogFragment(ContentEditDialog())
+            }
+            R.id.menu_update_toc -> ReadBook.book?.let {
+                if (it.isEpub) {
+                    BookHelp.clearCache(it)
+                    EpubFile.clear()
+                }
+                if (it.isMobi) {
+                    MobiFile.clear()
+                }
+                loadChapterList(it)
+            }
+            R.id.menu_enable_replace -> {
+                changeReplaceRuleState()
+            }
+            R.id.menu_re_segment -> ReadBook.book?.let {
+                it.setReSegment(!it.getReSegment())
+                item.isChecked = it.getReSegment()
+                ReadBook.loadContent(false)
+            }
+
+            R.id.menu_tool_button -> {
+                binding.readMenu.runMenuOut()
+                showDialogFragment(ToolButtonConfigDialog())
+            }
+
+//            R.id.menu_enable_review -> {
+//                AppConfig.enableReview = !AppConfig.enableReview
+//                item.isChecked = AppConfig.enableReview
+//                ReadBook.loadContent(false)
+//            }
+
+            R.id.menu_del_ruby_tag -> ReadBook.book?.let {
+                item.isChecked = !item.isChecked
+                if (item.isChecked) {
+                    it.addDelTag(Book.rubyTag)
+                } else {
+                    it.removeDelTag(Book.rubyTag)
+                }
+                refreshContentAll(it)
+            }
+
+            R.id.menu_del_h_tag -> ReadBook.book?.let {
+                item.isChecked = !item.isChecked
+                if (item.isChecked) {
+                    it.addDelTag(Book.hTag)
+                } else {
+                    it.removeDelTag(Book.hTag)
+                }
+                refreshContentAll(it)
+            }
+
+            R.id.menu_page_anim -> showPageAnimConfig {
+                binding.readView.upPageAnim()
+                ReadBook.loadContent(false)
+            }
+
+            R.id.menu_log -> showDialogFragment<AppLogDialog>()
+            R.id.menu_toc_regex -> showDialogFragment(
+                TxtTocRuleDialog(ReadBook.book?.tocUrl)
+            )
+
+            R.id.menu_reverse_content -> ReadBook.book?.let {
+                viewModel.reverseContent(it)
+            }
+
+            R.id.menu_set_charset -> showCharsetConfig()
+            R.id.menu_image_style -> {
+                val imgStyles =
+                    arrayListOf(
+                        Book.imgStyleDefault, Book.imgStyleFull, Book.imgStyleText,
+                        Book.imgStyleSingle
+                    )
+                selector(
+                    R.string.image_style,
+                    imgStyles
+                ) { _, index ->
+                    val imageStyle = imgStyles[index]
+                    ReadBook.book?.setImageStyle(imageStyle)
+                    if (imageStyle == Book.imgStyleSingle) {
+                        ReadBook.book?.setPageAnim(0)  // 切换图片样式single后,自动切换为覆盖
+                        binding.readView.upPageAnim()
+                    }
+                    ReadBook.loadContent(false)
+                }
+            }
+
+            R.id.menu_get_progress -> ReadBook.book?.let {
+                viewModel.syncBookProgress(it) { progress ->
+                    sureSyncProgress(progress)
+                }
+            }
+
+            R.id.menu_cover_progress -> ReadBook.book?.let {
+                ReadBook.uploadProgress(true) { toastOnUi(R.string.upload_book_success) }
+            }
+
+            R.id.menu_same_title_removed -> {
+                ReadBook.book?.let {
+                    val contentProcessor = ContentProcessor.get(it)
+                    val textChapter = ReadBook.curTextChapter
+                    if (textChapter != null
+                        && !textChapter.sameTitleRemoved
+                        && !contentProcessor.removeSameTitleCache.contains(
+                            textChapter.chapter.getFileName("nr")
+                        )
+                    ) {
+                        toastOnUi("未找到可移除的重复标题")
+                    }
+                }
+                viewModel.reverseRemoveSameTitle()
+            }
+
+            R.id.menu_effective_replaces -> showDialogFragment<EffectiveReplacesDialog>()
+
+            R.id.menu_help -> showHelp()
+        }
+        return super.onCompatOptionsItemSelected(item)
+    }
+
+    private fun refreshContentAll(book: Book) {
+        ReadBook.clearTextChapter()
+        binding.readView.upContent()
+        viewModel.refreshContentAll(book)
+    }
+
+    override fun onMenuItemClick(item: MenuItem): Boolean {
+        return onCompatOptionsItemSelected(item)
+    }
+
+    /**
+     * 按键拦截,显示菜单
+     */
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        val keyCode = event.keyCode
+        val action = event.action
+        val isDown = action == 0
+
+        if (keyCode == KeyEvent.KEYCODE_MENU) {
+            if (isDown && !binding.readMenu.canShowMenu) {
+                binding.readMenu.runMenuIn()
+                return true
+            }
+            if (!isDown && !binding.readMenu.canShowMenu) {
+                binding.readMenu.canShowMenu = true
+                return true
+            }
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
+    /**
+     * 鼠标滚轮事件
+     */
+    override fun onGenericMotionEvent(event: MotionEvent): Boolean {
+        if (0 != (event.source and InputDevice.SOURCE_CLASS_POINTER)) {
+            if (event.action == MotionEvent.ACTION_SCROLL) {
+                val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
+                LogUtils.d("onGenericMotionEvent", "axisValue = $axisValue")
+                // 获得垂直坐标上的滚动方向
+                if (axisValue < 0.0f) { // 滚轮向下滚
+                    mouseWheelPage(PageDirection.NEXT)
+                } else { // 滚轮向上滚
+                    mouseWheelPage(PageDirection.PREV)
+                }
+                return true
+            }
+        }
+        // 手柄摇杆控制翻页
+        if (0 != (event.source and InputDevice.SOURCE_CLASS_JOYSTICK)) {
+            if (event.action == MotionEvent.ACTION_MOVE) {
+                // 左摇杆上下移动控制翻页
+                val yAxis = event.getAxisValue(MotionEvent.AXIS_Y)
+                if (Math.abs(yAxis) > 0.5f) { // 死区设置
+                    if (yAxis > 0) { // 摇杆向下
+                        handleKeyPage(PageDirection.NEXT, false)
+                    } else { // 摇杆向上
+                        handleKeyPage(PageDirection.PREV, false)
+                    }
+                    return true
+                }
+            }
+        }
+        return super.onGenericMotionEvent(event)
+    }
+
+    /**
+     * 按键事件
+     */
+    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+        if (menuLayoutIsVisible) {
+            return super.onKeyDown(keyCode, event)
+        }
+        val longPress = event.repeatCount > 0
+        when {
+            isPrevKey(keyCode) -> {
+                handleKeyPage(PageDirection.PREV, longPress)
+                return true
+            }
+
+            isNextKey(keyCode) -> {
+                handleKeyPage(PageDirection.NEXT, longPress)
+                return true
+            }
+        }
+        when (keyCode) {
+            KeyEvent.KEYCODE_VOLUME_UP -> if (volumeKeyPage(PageDirection.PREV, longPress)) {
+                return true
+            }
+
+            KeyEvent.KEYCODE_VOLUME_DOWN -> if (volumeKeyPage(PageDirection.NEXT, longPress)) {
+                return true
+            }
+
+            KeyEvent.KEYCODE_PAGE_UP -> {
+                handleKeyPage(PageDirection.PREV, longPress)
+                return true
+            }
+
+            KeyEvent.KEYCODE_PAGE_DOWN -> {
+                handleKeyPage(PageDirection.NEXT, longPress)
+                return true
+            }
+
+            KeyEvent.KEYCODE_SPACE -> {
+                handleKeyPage(PageDirection.NEXT, longPress)
+                return true
+            }
+             // 手柄方向键控制翻页
+            KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT -> {
+                handleKeyPage(PageDirection.PREV, longPress)
+                return true
+            }
+
+            KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT -> {
+                handleKeyPage(PageDirection.NEXT, longPress)
+                return true
+            }
+        }
+
+        return super.onKeyDown(keyCode, event)
+    }
+
+    /**
+     * 松开按键事件
+     */
+    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
+        when (keyCode) {
+            KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
+                if (volumeKeyPage(PageDirection.NONE, false)) {
+                    return true
+                }
+            }
+
+        }
+        return super.onKeyUp(keyCode, event)
+    }
+
+    /**
+     * view触摸,文字选择
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouch(v: View, event: MotionEvent): Boolean = binding.run {
+        if (!binding.readView.isTextSelected) {
+            return false
+        }
+        when (event.action) {
+            MotionEvent.ACTION_DOWN -> textActionMenu.dismiss()
+            MotionEvent.ACTION_MOVE -> {
+                when (v.id) {
+                    R.id.cursor_left -> if (!readView.curPage.getReverseStartCursor()) {
+                        readView.curPage.selectStartMove(
+                            event.rawX + cursorLeft.width,
+                            event.rawY - cursorLeft.height
+                        )
+                    } else {
+                        readView.curPage.selectEndMove(
+                            event.rawX - cursorRight.width,
+                            event.rawY - cursorRight.height
+                        )
+                    }
+
+                    R.id.cursor_right -> if (readView.curPage.getReverseEndCursor()) {
+                        readView.curPage.selectStartMove(
+                            event.rawX + cursorLeft.width,
+                            event.rawY - cursorLeft.height
+                        )
+                    } else {
+                        readView.curPage.selectEndMove(
+                            event.rawX - cursorRight.width,
+                            event.rawY - cursorRight.height
+                        )
+                    }
+                }
+            }
+
+            MotionEvent.ACTION_UP -> {
+                readView.curPage.resetReverseCursor()
+                showTextActionMenu()
+            }
+        }
+        return true
+    }
+
+    /**
+     * 更新文字选择开始位置
+     */
+    override fun upSelectedStart(x: Float, y: Float, top: Float): Unit = binding.run {
+        cursorLeft.x = x - cursorLeft.width
+        cursorLeft.y = y
+        cursorLeft.visible(true)
+        textMenuPosition.x = x
+        textMenuPosition.y = top
+
+        root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+    }
+
+    /**
+     * 更新文字选择结束位置
+     */
+    override fun upSelectedEnd(x: Float, y: Float): Unit = binding.run {
+        cursorRight.x = x
+        cursorRight.y = y
+        cursorRight.visible(true)
+
+        root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+    }
+
+    /**
+     * 取消文字选择
+     */
+    override fun onCancelSelect() = binding.run {
+        cursorLeft.invisible()
+        cursorRight.invisible()
+        textActionMenu.dismiss()
+    }
+
+    override fun onLongScreenshotTouchEvent(event: MotionEvent): Boolean {
+        return binding.readView.onTouchEvent(event)
+    }
+
+    /**
+     * 显示文本操作菜单
+     */
+    override fun showTextActionMenu() {
+        val navigationBarHeight =
+            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)
+                binding.navigationBar.height else 0
+        textActionMenu.show(
+            binding.textMenuPosition,
+            binding.root.height + navigationBarHeight,
+            binding.textMenuPosition.x.toInt(),
+            binding.textMenuPosition.y.toInt(),
+            binding.cursorLeft.y.toInt() + binding.cursorLeft.height,
+            binding.cursorRight.x.toInt(),
+            binding.cursorRight.y.toInt() + binding.cursorRight.height
+        )
+    }
+
+    /**
+     * 当前选择的文本
+     */
+    override val selectedText: String get() = binding.readView.getSelectText()
+
+    /**
+     * 文本选择菜单操作
+     */
+    override fun onMenuItemSelected(itemId: Int): Boolean {
+        when (itemId) {
+            R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) {
+                1 -> lifecycleScope.launch {
+                    binding.readView.aloudStartSelect()
+                }
+
+                else -> speak(binding.readView.getSelectText())
+            }
+
+            R.id.menu_bookmark -> binding.readView.curPage.let {
+                val bookmark = it.createBookmark()
+                if (bookmark == null) {
+                    toastOnUi(R.string.create_bookmark_error)
+                } else {
+                    showDialogFragment(BookmarkDialog(bookmark))
+                }
+                return true
+            }
+
+            R.id.menu_edit -> {
+                val startPos = ReadBook.durChapterPos
+                showDialogFragment<ContentEditDialog> {
+                    putInt("start_position", startPos)
+                    putString("selected_text", binding.readView.getSelectText())
+                }
+                return true
+            }
+
+            R.id.menu_replace -> {
+                val scopes = arrayListOf<String>()
+                ReadBook.book?.name?.let { scopes.add(it) }
+                ReadBook.bookSource?.bookSourceUrl?.let { scopes.add(it) }
+
+                val text = selectedText.lineSequence().map { it.trim() }.joinToString("\n")
+
+                val editRoute = ReplaceEditRoute(
+                    id = -1,
+                    pattern = text,
+                    scope = scopes.joinToString(";"),
+                    isScopeTitle = false,
+                    isScopeContent = true
+                )
+
+                replaceActivity.launch(
+                    ReplaceRuleActivity.startIntent(
+                        context = this,
+                        editRoute = editRoute
+                    )
+                )
+                return true
+            }
+
+            R.id.menu_search_content -> {
+                viewModel.searchContentQuery = selectedText
+                openSearchActivity(selectedText)
+                return true
+            }
+
+            R.id.menu_dict -> {
+                showDialogFragment(DictDialog(selectedText))
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun onEditTextAction(selectedText: String, startPos: Int) {
+        val bundle = Bundle().apply {
+            putString("selected_text", selectedText)
+            putInt("start_position", startPos)
+        }
+
+        val dialog = ContentEditDialog().apply {
+            arguments = bundle
+        }
+
+        dialog.show(supportFragmentManager, "ContentEditDialog")
+    }
+
+    /**
+     * 文本选择菜单操作完成
+     */
+    override fun onMenuActionFinally() = binding.run {
+        textActionMenu.dismiss()
+        readView.cancelSelect()
+    }
+
+    private fun speak(text: String) {
+        if (tts == null) {
+            tts = TTS()
+        }
+        tts?.speak(text)
+    }
+
+    /**
+     * 鼠标滚轮翻页
+     */
+    private fun mouseWheelPage(direction: PageDirection) {
+        if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) {
+            return
+        }
+        keyPageDebounce(direction, mouseWheel = true, longPress = false)
+    }
+
+    /**
+     * 音量键翻页
+     */
+    private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {
+        if (!AppConfig.volumeKeyPage) {
+            return false
+        }
+        if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
+            return false
+        }
+        handleKeyPage(direction, longPress)
+        return true
+    }
+
+    private fun handleKeyPage(direction: PageDirection, longPress: Boolean) {
+        if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
+            keyPage(direction)
+        } else {
+            keyPageDebounce(direction, longPress = longPress)
+        }
+    }
+
+    private fun keyPageDebounce(
+        direction: PageDirection,
+        mouseWheel: Boolean = false,
+        longPress: Boolean
+    ) {
+        if (longPress) {
+            return
+        }
+        nextPageDebounce.apply {
+            wait = if (mouseWheel) 200L else 600L
+            leading = !mouseWheel
+            trailing = mouseWheel
+        }
+        prevPageDebounce.apply {
+            wait = if (mouseWheel) 200L else 600L
+            leading = !mouseWheel
+            trailing = mouseWheel
+        }
+        when (direction) {
+            PageDirection.NEXT -> nextPageDebounce.invoke()
+            PageDirection.PREV -> prevPageDebounce.invoke()
+            else -> {}
+        }
+    }
+
+    private fun keyPage(direction: PageDirection) {
+        binding.readView.cancelSelect()
+        binding.readView.pageDelegate?.isCancel = false
+        binding.readView.pageDelegate?.keyTurnPage(direction)
+    }
+
+    override fun upMenuView() {
+        handler.post {
+            upMenu()
+            binding.readMenu.upBookView()
+        }
+    }
+
+    override fun loadChapterList(book: Book) {
+        ReadBook.upMsg(getString(R.string.toc_updateing))
+        viewModel.loadChapterList(book)
+    }
+
+    /**
+     * 内容加载完成
+     */
+    override fun contentLoadFinish() {
+        if (intent.getBooleanExtra("readAloud", false)) {
+            intent.removeExtra("readAloud")
+            ReadBook.readAloud()
+        }
+        loadStates = true
+    }
+
+    /**
+     * 更新内容
+     */
+    override fun upContent(
+        relativePosition: Int,
+        resetPageOffset: Boolean,
+        success: (() -> Unit)?
+    ) {
+        lifecycleScope.launch {
+            binding.readView.upContent(relativePosition, resetPageOffset)
+            if (relativePosition == 0) {
+                upSeekBarProgress()
+            }
+            loadStates = false
+            success?.invoke()
+        }
+    }
+
+    override suspend fun upContentAwait(
+        relativePosition: Int,
+        resetPageOffset: Boolean,
+        success: (() -> Unit)?
+    ) = withContext(Main.immediate) {
+        binding.readView.upContent(relativePosition, resetPageOffset)
+        if (relativePosition == 0) {
+            upSeekBarProgress()
+        }
+        loadStates = false
+    }
+
+    override fun upPageAnim(upRecorder: Boolean) {
+        lifecycleScope.launch {
+            binding.readView.upPageAnim(upRecorder)
+        }
+    }
+
+    override fun notifyBookChanged() {
+        bookChanged = true
+        if (!ReadBook.inBookshelf) {
+            viewModel.removeFromBookshelf { super.finish() }
+        }
+    }
+
+    override fun cancelSelect() {
+        runOnUiThread {
+            binding.readView.cancelSelect()
+        }
+    }
+
+    /**
+     * 页面改变
+     */
+    override fun pageChanged() {
+        pageChanged = true
+        binding.readView.onPageChange()
+        handler.post {
+            upSeekBarProgress()
+        }
+        executor.execute {
+            startBackupJob()
+        }
+    }
+
+    /**
+     * 更新进度条位置
+     */
+    private fun upSeekBarProgress() {
+        val progress = when (AppConfig.progressBarBehavior) {
+            "page" -> ReadBook.durPageIndex
+            else /* chapter */ -> ReadBook.durChapterIndex
+        }
+        if (progress >= 0) {
+            binding.readMenu.setSeekPage(progress)
+        }
+    }
+
+    /**
+     * 显示菜单
+     */
+    override fun showMenuBar() {
+        binding.readMenu.runMenuIn()
+    }
+
+    override val oldBook: Book?
+        get() = ReadBook.book
+
+    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
+        if (!book.isAudio) {
+            viewModel.changeTo(book, toc)
+        } else {
+            ReadAloud.stop(this)
+            lifecycleScope.launch {
+                withContext(IO) {
+                    ReadBook.book?.migrateTo(book, toc)
+                    book.removeType(BookType.updateError)
+                    ReadBook.book?.delete()
+                    appDb.bookDao.insert(book)
+                }
+                startActivityForBook(book)
+                finish()
+            }
+        }
+    }
+
+    override fun replaceContent(content: String) {
+        ReadBook.book?.let {
+            viewModel.saveContent(it, content)
+        }
+    }
+
+    override fun showActionMenu() {
+        when {
+            BaseReadAloudService.isRun -> showReadAloudDialog()
+            isAutoPage -> showDialogFragment<AutoReadDialog>()
+            isShowingSearchResult -> binding.searchMenu.runMenuIn()
+            else -> binding.readMenu.runMenuIn()
+        }
+    }
+
+    /**
+     * 显示朗读菜单
+     */
+    override fun showReadAloudDialog() {
+        showDialogFragment<ReadAloudDialog>()
+    }
+
+    /**
+     * 自动翻页
+     */
+    override fun autoPage() {
+        ReadAloud.stop(this)
+        if (isAutoPage) {
+            autoPageStop()
+        } else {
+            binding.readView.autoPager.start()
+            binding.readMenu.setAutoPage(true)
+            screenTimeOut = -1L
+            screenOffTimerStart()
+        }
+    }
+
+    override fun autoPageStop() {
+        if (isAutoPage) {
+            binding.readView.autoPager.stop()
+            binding.readMenu.setAutoPage(false)
+            dismissDialogFragment<AutoReadDialog>()
+            upScreenTimeOut()
+        }
+    }
+
+    override fun openSourceEditActivity() {
+        ReadBook.bookSource?.let {
+            sourceEditActivity.launch {
+                putExtra("sourceUrl", it.bookSourceUrl)
+            }
+        }
+    }
+
+    override fun openBookInfoActivity() {
+        ReadBook.book?.let {
+            bookInfoActivity.launch {
+                putExtra("name", it.name)
+                putExtra("author", it.author)
+                putExtra("bookUrl", it.bookUrl)
+            }
+        }
+    }
+
+    /**
+     * 替换
+     */
+    override fun openReplaceRule() {
+        replaceActivity.launch(Intent(this, ReplaceRuleActivity::class.java))
+    }
+
+    /**
+     * 打开目录
+     */
+    override fun openChapterList() {
+        ReadBook.book?.let {
+            tocActivity.launch(it.bookUrl)
+        }
+    }
+
+    /**
+     * 打开搜索界面
+     */
+    override fun openSearchActivity(searchWord: String?) {
+        val book = ReadBook.book ?: return
+        searchContentActivity.launch {
+            putExtra("bookUrl", book.bookUrl)
+            putExtra("searchWord", searchWord)
+            putExtra("searchResultIndex", viewModel.searchResultIndex)
+            viewModel.searchResultList?.first()?.let {
+                if (it.query == viewModel.searchContentQuery) {
+                    IntentData.put("searchResultList", viewModel.searchResultList)
+                }
+            }
+        }
+    }
+
+    /**
+     * 禁用书源
+     */
+    override fun disableSource() {
+        viewModel.disableSource()
+    }
+
+    /**
+     * 显示阅读样式配置
+     */
+//    override fun showReadStyle() {
+//        showDialogFragment<ReadStyleDialog>()
+//    }
+//
+//    /**
+//     * 显示更多设置
+//     */
+//    override fun showMoreSetting() {
+//        showDialogFragment<MoreConfigDialog>()
+//    }
+//
+    override fun showSearchSetting() {
+        showDialogFragment<ReadStyleDialog>()
+    }
+
+    override fun showReadStyle() {
+        showDialogFragment<ReadStyleDialog>()
+    }
+
+    /**
+     * 更新状态栏,导航栏
+     */
+    override fun upSystemUiVisibility() {
+        upSystemUiVisibility(isInMultiWindow, !menuLayoutIsVisible)
+        upNavigationBarColor()
+    }
+
+    // 退出全文搜索
+    override fun exitSearchMenu() {
+        if (isShowingSearchResult) {
+            isShowingSearchResult = false
+            binding.searchMenu.invalidate()
+            binding.searchMenu.invisible()
+            ReadBook.clearSearchResult()
+            binding.readView.cancelSelect(true)
+        }
+    }
+
+    /* 恢复到 全文搜索/进度条跳转前的位置 */
+    private fun restoreLastBookProcess() {
+        if (confirmRestoreProcess == true) {
+            ReadBook.restoreLastBookProgress()
+        } else if (confirmRestoreProcess == null) {
+            alert(R.string.draw) {
+                setMessage(R.string.restore_last_book_process)
+                yesButton {
+                    confirmRestoreProcess = true
+                    ReadBook.restoreLastBookProgress() //恢复启动全文搜索前的进度
+                }
+                noButton {
+                    ReadBook.lastBookProgress = null
+                    confirmRestoreProcess = false
+                }
+                onCancelled {
+                    ReadBook.lastBookProgress = null
+                    confirmRestoreProcess = false
+                }
+            }
+        }
+    }
+
+    override fun showLogin() {
+        ReadBook.bookSource?.let {
+            startActivity<SourceLoginActivity> {
+                putExtra("bookType", BookType.text)
+            }
+        }
+    }
+
+    override fun payAction() {
+        val book = ReadBook.book ?: return
+        if (book.isLocal) return
+        val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+        if (chapter == null) {
+            toastOnUi("no chapter")
+            return
+        }
+        alert(R.string.chapter_pay) {
+            setMessage(chapter.title)
+            yesButton {
+                Coroutine.async(lifecycleScope) {
+                    val source =
+                        ReadBook.bookSource ?: throw NoStackTraceException("no book source")
+                    val payAction = source.getContentRule().payAction
+                    if (payAction.isNullOrBlank()) {
+                        throw NoStackTraceException("no pay action")
+                    }
+                    val analyzeRule = AnalyzeRule(book, source)
+                    analyzeRule.setCoroutineContext(coroutineContext)
+                    analyzeRule.setBaseUrl(chapter.url)
+                    analyzeRule.setChapter(chapter)
+                    analyzeRule.evalJS(payAction).toString()
+                }.onSuccess(IO) {
+                    if (it.isAbsUrl()) {
+                        startActivity<WebViewActivity> {
+                            val bookSource = ReadBook.bookSource
+                            putExtra("title", getString(R.string.chapter_pay))
+                            putExtra("url", it)
+                            putExtra("sourceOrigin", bookSource?.bookSourceUrl)
+                            putExtra("sourceName", bookSource?.bookSourceName)
+                            putExtra("sourceType", bookSource?.getSourceType())
+                        }
+                    } else if (it.isTrue()) {
+                        //购买成功后刷新目录
+                        ReadBook.book?.let {
+                            ReadBook.curTextChapter = null
+                            BookHelp.delContent(book, chapter)
+                            loadChapterList(book)
+                        }
+                    }
+                }.onError {
+                    AppLog.put("执行购买操作出错\n${it.localizedMessage}", it, true)
+                }
+            }
+            noButton()
+        }
+    }
+
+    /**
+     * 朗读按钮
+     */
+    override fun onClickReadAloud() {
+        autoPageStop()
+        when {
+            !BaseReadAloudService.isRun -> {
+                ReadAloud.upReadAloudClass()
+                val scrollPageAnim = ReadBook.pageAnim() == 3
+                if (scrollPageAnim) {
+                    val pos = binding.readView.getReadAloudPos()
+                    if (pos != null) {
+                        val (index, line) = pos
+                        if (ReadBook.durChapterIndex != index) {
+                            ReadBook.openChapter(index, line.chapterPosition, false) {
+                                ReadBook.readAloud(startPos = line.pagePosition)
+                            }
+                        } else {
+                            ReadBook.durChapterPos = line.chapterPosition
+                            ReadBook.readAloud(startPos = line.pagePosition)
+                        }
+                    } else {
+                        ReadBook.readAloud()
+                    }
+                } else {
+                    ReadBook.readAloud()
+                }
+            }
+
+            BaseReadAloudService.pause -> {
+                val scrollPageAnim = ReadBook.pageAnim() == 3
+                if (scrollPageAnim && pageChanged) {
+                    pageChanged = false
+                    val pos = binding.readView.getReadAloudPos()
+                    if (pos != null) {
+                        val (index, line) = pos
+                        if (ReadBook.durChapterIndex != index) {
+                            ReadBook.openChapter(index, line.chapterPosition, false) {
+                                ReadBook.readAloud(startPos = line.pagePosition)
+                            }
+                        } else {
+                            ReadBook.durChapterPos = line.chapterPosition
+                            ReadBook.readAloud(startPos = line.pagePosition)
+                        }
+                    } else {
+                        ReadBook.readAloud()
+                    }
+                } else {
+                    ReadAloud.resume(this)
+                }
+            }
+
+            else -> ReadAloud.pause(this)
+        }
+    }
+
+    override fun showHelp() {
+        showHelp("readMenuHelp")
+    }
+
+    /**
+     * 点击图片
+     */
+    override fun oldClickImg(src: String): Boolean {
+        val urlMatcher = paramPattern.matcher(src)
+        if (urlMatcher.find()) {
+            val urlOptionStr = src.substring(urlMatcher.end())
+            val urlOptionMap = GSON.fromJsonObject<Map<String, String>>(urlOptionStr).getOrNull()
+            val click = urlOptionMap?.get("click")
+            if (click != null) {
+                Coroutine.async(lifecycleScope, IO) {
+                    val source = ReadBook.bookSource ?: return@async
+                    val java = SourceLoginJsExtensions(this@ReadBookActivity, source, BookType.text)
+                    val book = ReadBook.book ?: return@async
+                    val chapter =
+                        appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+                            ?: throw Exception("no find chapter")
+                    runScriptWithContext {
+                        source.evalJS(click) {
+                            put("java", java)
+                            put("book", book)
+                            put("chapter", chapter)
+                            put("result", src)
+                        }
+                    }
+                }.onError {
+                    AppLog.put("执行图片链接click键值出错\n${it.localizedMessage}", it, true)
+                }
+                return true
+            }
+            val jsStr = urlOptionMap?.get("js") ?: return false
+            Coroutine.async(lifecycleScope, IO) {
+                val source = ReadBook.bookSource ?: return@async
+                val book = ReadBook.book ?: return@async
+                val chapter =
+                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+                        ?: throw Exception("no find chapter")
+                val urlNoOption = src.take(urlMatcher.start())
+                AnalyzeRule(book, source).apply {
+                    setCoroutineContext(coroutineContext)
+                    setBaseUrl(chapter.url)
+                    setChapter(chapter)
+                    evalJS(jsStr, urlNoOption)
+                }
+            }.onError {
+                AppLog.put("执行图片链接js键值出错\n${it.localizedMessage}", it, true)
+            }
+            return true
+        }
+        return false
+    }
+
+    override fun clickImg(click: String, src: String) {
+        Coroutine.async(lifecycleScope, IO) {
+            val source = ReadBook.bookSource ?: return@async
+            val java = SourceLoginJsExtensions(this@ReadBookActivity, source, BookType.text)
+            val book = ReadBook.book ?: return@async
+            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)
+                ?: throw Exception("no find chapter")
+            runScriptWithContext {
+                source.evalJS(click) {
+                    put("java", java)
+                    put("book", book)
+                    put("chapter", chapter)
+                    put("result", src)
+                }
+            }
+        }.onError {
+            AppLog.put("执行图片链接click键值出错\n${it.localizedMessage}", it, true)
+        }
+    }
+
+    /**
+     * 长按图片
+     */
+    @SuppressLint("RtlHardcoded")
+    override fun onImageLongPress(x: Float, y: Float, src: String) {
+        binding.root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+        popupAction.setItems(
+            listOf(
+                SelectItem(getString(R.string.show), "show"),
+                SelectItem(getString(R.string.refresh), "refresh"),
+                SelectItem("保存到相册", "save"),
+                SelectItem(getString(R.string.menu), "menu"),
+            )
+        )
+        popupAction.onActionClick = {
+            when (it) {
+                "show" -> showDialogFragment(PhotoDialog(src))
+                "refresh" -> viewModel.refreshImage(src)
+                "save" -> {
+                    viewModel.saveImage(src)
+                }
+                "menu" -> showActionMenu()
+            }
+            popupAction.dismiss()
+        }
+        val navigationBarHeight =
+            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)
+                binding.navigationBar.height else 0
+        popupAction.showAtLocation(
+            binding.readView, Gravity.BOTTOM or Gravity.LEFT, x.toInt(),
+            binding.root.height + navigationBarHeight - y.toInt()
+        )
+    }
+
+    /**
+     * colorSelectDialog
+     */
+    override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run {
+        when (dialogId) {
+            S_COLOR -> {
+                setCurShadColor(color)
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
+            }
+
+            TEXT_COLOR -> {
+                setCurTextColor(color)
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
+                if (AppConfig.readBarStyleFollowPage) {
+                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
+                }
+            }
+
+            TEXT_ACCENT_COLOR -> {
+                setCurTextAccentColor(color)
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))
+                if (AppConfig.readBarStyleFollowPage) {
+                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
+                }
+            }
+
+            BG_COLOR -> {
+                setCurBg(0, "#${color.hexString}")
+                postEvent(EventBus.UP_CONFIG, arrayListOf(1))
+                if (AppConfig.readBarStyleFollowPage) {
+                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
+                }
+            }
+
+            TIP_COLOR -> {
+                ReadTipConfig.tipColor = color
+                postEvent(EventBus.TIP_COLOR, "")
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
+            }
+
+            TIP_DIVIDER_COLOR -> {
+                ReadTipConfig.tipDividerColor = color
+                postEvent(EventBus.TIP_COLOR, "")
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
+            }
+
+            B_COLOR -> {
+                setMenuCurBg(color)
+                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
+            }
+
+            A_COLOR -> {
+                setMenuCurAc(color)
+                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
+            }
+
+            U_COLOR -> {
+                setUnderlineColor(color)
+                postEvent(EventBus.UP_CONFIG, arrayListOf(2))
+                postEvent(EventBus.UP_CONFIG, arrayListOf(6, 9, 11))
+            }
+        }
+    }
+
+    /**
+     * colorSelectDialog
+     */
+    override fun onDialogDismissed(dialogId: Int) = Unit
+
+    override fun onTocRegexDialogResult(tocRegex: String) {
+        ReadBook.book?.let {
+            it.tocUrl = tocRegex
+            loadChapterList(it)
+        }
+    }
+
+    private fun sureSyncProgress(progress: BookProgress) {
+        alert(R.string.get_book_progress) {
+            setMessage(R.string.current_progress_exceeds_cloud)
+            okButton {
+                ReadBook.setProgress(progress)
+            }
+            noButton()
+        }
+    }
+
+    /* 进度条跳转到指定章节 */
+    override fun skipToChapter(index: Int) {
+        ReadBook.saveCurrentBookProgress() //退出章节跳转恢复此时进度
+        viewModel.openChapter(index)
+    }
+
+    /* 全文搜索跳转 */
+    override fun navigateToSearch(searchResult: SearchResult, index: Int) {
+        viewModel.searchResultIndex = index
+        skipToSearch(searchResult)
+    }
+
+    override fun onMenuShow() {
+        binding.readView.autoPager.pause()
+    }
+
+    override fun onMenuHide() {
+        binding.readView.autoPager.resume()
+    }
+
+    override fun refresh() {
+        recreate()
+    }
+
+    override fun onLayoutPageCompleted(index: Int, page: TextPage) {
+        upSeekBarThrottle.invoke()
+        binding.readView.onLayoutPageCompleted(index, page)
+    }
+
+    /* 全文搜索跳转 */
+    private fun skipToSearch(searchResult: SearchResult) {
+        if (searchResult.chapterIndex != ReadBook.durChapterIndex) {
+            viewModel.openChapter(searchResult.chapterIndex) {
+                jumpToPosition(searchResult)
+            }
+        } else {
+            jumpToPosition(searchResult)
+        }
+    }
+
+    private fun jumpToPosition(searchResult: SearchResult) {
+        val curTextChapter = ReadBook.curTextChapter ?: return
+        binding.searchMenu.updateSearchInfo()
+        val (pageIndex, lineIndex, charIndex, addLine, charIndex2) =
+            viewModel.searchResultPositions(curTextChapter, searchResult)
+        ReadBook.skipToPage(pageIndex) {
+            isSelectingSearchResult = true
+            binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex)
+            when (addLine) {
+                0 -> binding.readView.curPage.selectEndMoveIndex(
+                    0,
+                    lineIndex,
+                    charIndex + viewModel.searchContentQuery.length - 1
+                )
+
+                1 -> binding.readView.curPage.selectEndMoveIndex(
+                    0, lineIndex + 1, charIndex2
+                )
+                //consider change page, jump to scroll position
+                -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2)
+            }
+            binding.readView.isTextSelected = true
+            isSelectingSearchResult = false
+        }
+    }
+
+    override fun addBookmark() {
+        val book = ReadBook.book
+        val page = ReadBook.curTextChapter?.getPage(ReadBook.durPageIndex)
+        if (book != null && page != null) {
+            val bookmark = book.createBookMark().apply {
+                chapterIndex = ReadBook.durChapterIndex
+                chapterPos = ReadBook.durChapterPos
+                chapterName = page.title
+                bookText = page.text.replace(Regex("[袮꧁]"), "").trim()
+            }
+            showDialogFragment(BookmarkDialog(bookmark))
+        }
+    }
+
+    override fun changeReplaceRuleState() {
+        binding.readMenu.runMenuOut()
+        ReadBook.book?.let {
+            it.setUseReplaceRule(!it.getUseReplaceRule())
+            ReadBook.saveRead()
+            menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.getUseReplaceRule()
+            binding.readMenu.changeReplace(it.getUseReplaceRule())
+            viewModel.replaceRuleChanged()
+        }
+    }
+
+    private fun startBackupJob() {
+        backupJob?.cancel()
+        backupJob = lifecycleScope.launch(IO) {
+            delay(300000)
+            ReadBook.book?.let {
+                AppWebDav.uploadBookProgress(it)
+                ensureActive()
+                it.update()
+                Backup.autoBack(this@ReadBookActivity)
+            }
+        }
+    }
+
+    override fun sureNewProgress(progress: BookProgress) {
+        syncDialog?.dismiss()
+        syncDialog = alert(R.string.get_book_progress) {
+            setMessage(R.string.cloud_progress_exceeds_current)
+            okButton {
+                ReadBook.setProgress(progress)
+            }
+            noButton()
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        tts?.clearTts()
+        textActionMenu.dismiss()
+        popupAction.dismiss()
+        binding.readView.onDestroy()
+        ReadBook.unregister(this)
+        if (!ReadBook.inBookshelf && !isChangingConfigurations) {
+            viewModel.removeFromBookshelf(null)
+        }
+        if (!BuildConfig.DEBUG) {
+            Backup.autoBack(this)
+        }
+    }
+
+    override fun observeLiveBus() = binding.run {
+        observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() }
+        observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) }
+        observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {
+            if (it) {
+                onClickReadAloud()
+            } else {
+                ReadBook.readAloud(!BaseReadAloudService.pause)
+            }
+        }
+        observeEvent<ArrayList<Int>>(EventBus.UP_CONFIG) {
+            it.forEach { value ->
+                when (value) {
+                    0 -> upSystemUiVisibility()
+                    1 -> readView.upBg()
+                    2 -> {
+                        readView.upStyle()
+                        binding.readMenu.reset()
+                    }
+                    3 -> readView.upBgAlpha()
+                    4 -> readView.upPageSlopSquare()
+                    5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)
+                    6 -> readView.upContent(resetPageOffset = false)
+                    8 -> ChapterProvider.upStyle()
+                    9 -> readView.invalidateTextPage()
+                    10 -> ChapterProvider.upLayout()
+                    11 -> readView.submitRenderTask()
+                }
+            }
+        }
+        observeEvent<Int>(EventBus.ALOUD_STATE) {
+            if (it == Status.STOP || it == Status.PAUSE) {
+                ReadBook.curTextChapter?.let { textChapter ->
+                    val page = textChapter.getPageByReadPos(ReadBook.durChapterPos)
+                    if (page != null) {
+                        page.removePageAloudSpan()
+                        readView.upContent(resetPageOffset = false)
+                    }
+                }
+            }
+        }
+        observeEventSticky<Int>(EventBus.TTS_PROGRESS) { chapterStart ->
+            lifecycleScope.launch(IO) {
+                if (BaseReadAloudService.isPlay()) {
+                    ReadBook.curTextChapter?.let { textChapter ->
+                        val pageIndex = ReadBook.durPageIndex
+                        val aloudSpanStart = chapterStart - textChapter.getReadLength(pageIndex)
+                        textChapter.getPage(pageIndex)
+                            ?.upPageAloudSpan(aloudSpanStart)
+                        upContent()
+                    }
+                }
+            }
+        }
+        observeEvent<Boolean>(PreferKey.keepLight) {
+            upScreenTimeOut()
+        }
+        observeEvent<Boolean>(PreferKey.textSelectAble) {
+            readView.curPage.upSelectAble(it)
+        }
+        observeEvent<String>(PreferKey.showBrightnessView) {
+            readMenu.upBrightnessState()
+        }
+        observeEvent<List<SearchResult>>(EventBus.SEARCH_RESULT) {
+            viewModel.searchResultList = it
+        }
+        observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) {
+            readMenu.reset()
+        }
+        observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {
+            readMenu.upSeekBar()
+        }
+        observeEvent<Boolean>(EventBus.REFRESH_BOOK_CONTENT) {
+            ReadBook.book?.let {
+                ReadBook.curTextChapter = null
+                binding.readView.upContent()
+                viewModel.refreshContentDur(it)
+            }
+        }
+    }
+
+    private fun upScreenTimeOut() {
+        val keepLightPrefer = getPrefString(PreferKey.keepLight)?.toInt() ?: 0
+        screenTimeOut = keepLightPrefer * 1000L
+        screenOffTimerStart()
+    }
+
+    /**
+     * 重置黑屏时间
+     */
+    override fun screenOffTimerStart() {
+        handler.post {
+            if (screenTimeOut < 0) {
+                keepScreenOn(true)
+                return@post
+            }
+            val t = screenTimeOut - sysScreenOffTime
+            if (t > 0) {
+                keepScreenOn(true)
+                handler.removeCallbacks(screenOffRunnable)
+                handler.postDelayed(screenOffRunnable, screenTimeOut)
+            } else {
+                keepScreenOn(false)
+            }
+        }
+    }
+
+    override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
+        viewModel.addToBookshelf(book, toc) {
+            toastOnUi("已添加到书架")
+        }
+    }
+
+    companion object {
+        const val RESULT_DELETED = 100
+    }
+
+}