fix: 补全修复阅读界面缺失的一些功能和同步问题

This commit is contained in:
HapeLee committed 2026-06-12 03:26:36 +08:00
1 parent 5ff9f3145a
commit 35875f7dd3
62 files changed
+1493 -677

No files matched your search

+2 -1
View File
@@ -113,7 +113,8 @@
"Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")", "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")",
"Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")", "Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")",
"PowerShell(grep *)", "PowerShell(grep *)",
"Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")" "Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")",
"Bash(git revert *)"
] ]
} }
} }
-5
View File
@@ -234,11 +234,6 @@
android:name=".ui.book.toc.TocActivity" android:name=".ui.book.toc.TocActivity"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<!-- 正文搜索 -->
<activity
android:name=".ui.book.searchContent.SearchContentActivity"
android:enableOnBackInvokedCallback="true"
android:launchMode="singleTop" />
<!-- 订阅源调试 --> <!-- 订阅源调试 -->
<activity <activity
android:name=".ui.rss.source.debug.RssSourceDebugActivity" android:name=".ui.rss.source.debug.RssSourceDebugActivity"
@@ -163,6 +163,7 @@ object PreferKey {
const val clearWebViewData = "clearWebViewData" const val clearWebViewData = "clearWebViewData"
const val onlyLatestBackup = "onlyLatestBackup" const val onlyLatestBackup = "onlyLatestBackup"
const val brightnessVwPos = "brightnessVwPos" const val brightnessVwPos = "brightnessVwPos"
const val brightnessAuto = "brightnessAuto"
const val shrinkDatabase = "shrinkDatabase" const val shrinkDatabase = "shrinkDatabase"
const val batchChangeSourceDelay = "batchChangeSourceDelay" const val batchChangeSourceDelay = "batchChangeSourceDelay"
const val openBookInfoByClickTitle = "openBookInfoByClickTitle" const val openBookInfoByClickTitle = "openBookInfoByClickTitle"
@@ -80,7 +80,9 @@ class ReadAloudSettingsRepository(
} }
suspend fun setTtsTimer(value: Int) { suspend fun setTtsTimer(value: Int) {
ReadConfig.ttsTimer = value.coerceIn(0, 180) val timer = value.coerceIn(0, 180)
ReadConfig.ttsTimer = timer
settingsRepository.putInt(PreferKey.ttsTimer, timer)
} }
suspend fun setTtsFollowSys(value: Boolean) { suspend fun setTtsFollowSys(value: Boolean) {
@@ -29,7 +29,10 @@ data class ReadPreferences(
val textBottomJustify: Boolean = true, val textBottomJustify: Boolean = true,
val adaptSpecialStyle: Boolean = true, val adaptSpecialStyle: Boolean = true,
val useZhLayout: Boolean = false, val useZhLayout: Boolean = false,
val showBrightnessView: Boolean = true, val showBrightnessView: String = "1",
val brightnessVwPos: String = "1",
val readBrightness: Int = 100,
val brightnessAuto: Boolean = false,
val useUnderline: Boolean = false, val useUnderline: Boolean = false,
val readSliderMode: String = "0", val readSliderMode: String = "0",
val doubleHorizontalPage: String = "0", val doubleHorizontalPage: String = "0",
@@ -157,8 +160,17 @@ class ReadSettingsRepository(
suspend fun setUseZhLayout(value: Boolean) = suspend fun setUseZhLayout(value: Boolean) =
settingsRepository.putBoolean(PreferKey.useZhLayout, value) settingsRepository.putBoolean(PreferKey.useZhLayout, value)
suspend fun setShowBrightnessView(value: Boolean) = suspend fun setShowBrightnessView(value: String) =
settingsRepository.putBoolean(PreferKey.showBrightnessView, value) settingsRepository.putString(PreferKey.showBrightnessView, value)
suspend fun setBrightnessVwPos(value: String) =
settingsRepository.putString(PreferKey.brightnessVwPos, value)
suspend fun setReadBrightness(value: Int) =
settingsRepository.putInt(PreferKey.brightness, value)
suspend fun setBrightnessAuto(value: Boolean) =
settingsRepository.putBoolean(PreferKey.brightnessAuto, value)
suspend fun setUseUnderline(value: Boolean) = suspend fun setUseUnderline(value: Boolean) =
settingsRepository.putBoolean(PreferKey.useUnderline, value) settingsRepository.putBoolean(PreferKey.useUnderline, value)
@@ -370,7 +382,10 @@ class ReadSettingsRepository(
textBottomJustify = this[Keys.TextBottomJustify] ?: true, textBottomJustify = this[Keys.TextBottomJustify] ?: true,
adaptSpecialStyle = this[Keys.AdaptSpecialStyle] ?: true, adaptSpecialStyle = this[Keys.AdaptSpecialStyle] ?: true,
useZhLayout = this[Keys.UseZhLayout] ?: false, useZhLayout = this[Keys.UseZhLayout] ?: false,
showBrightnessView = this[Keys.ShowBrightnessView] ?: true, showBrightnessView = this[Keys.ShowBrightnessView] ?: "1",
brightnessVwPos = this[Keys.BrightnessVwPos] ?: "1",
readBrightness = this[Keys.ReadBrightness] ?: 100,
brightnessAuto = this[Keys.BrightnessAuto] ?: false,
useUnderline = this[Keys.UseUnderline] ?: false, useUnderline = this[Keys.UseUnderline] ?: false,
readSliderMode = this[Keys.ReadSliderMode] ?: "0", readSliderMode = this[Keys.ReadSliderMode] ?: "0",
doubleHorizontalPage = this[Keys.DoubleHorizontalPage] ?: "0", doubleHorizontalPage = this[Keys.DoubleHorizontalPage] ?: "0",
@@ -461,7 +476,10 @@ class ReadSettingsRepository(
val TextBottomJustify = booleanPreferencesKey(PreferKey.textBottomJustify) val TextBottomJustify = booleanPreferencesKey(PreferKey.textBottomJustify)
val AdaptSpecialStyle = booleanPreferencesKey(PreferKey.adaptSpecialStyle) val AdaptSpecialStyle = booleanPreferencesKey(PreferKey.adaptSpecialStyle)
val UseZhLayout = booleanPreferencesKey(PreferKey.useZhLayout) val UseZhLayout = booleanPreferencesKey(PreferKey.useZhLayout)
val ShowBrightnessView = booleanPreferencesKey(PreferKey.showBrightnessView) val ShowBrightnessView = stringPreferencesKey(PreferKey.showBrightnessView)
val BrightnessVwPos = stringPreferencesKey(PreferKey.brightnessVwPos)
val ReadBrightness = intPreferencesKey(PreferKey.brightness)
val BrightnessAuto = booleanPreferencesKey(PreferKey.brightnessAuto)
val UseUnderline = booleanPreferencesKey(PreferKey.useUnderline) val UseUnderline = booleanPreferencesKey(PreferKey.useUnderline)
val ReadSliderMode = stringPreferencesKey(PreferKey.readSliderMode) val ReadSliderMode = stringPreferencesKey(PreferKey.readSliderMode)
val DoubleHorizontalPage = stringPreferencesKey(PreferKey.doublePageHorizontal) val DoubleHorizontalPage = stringPreferencesKey(PreferKey.doublePageHorizontal)
@@ -118,6 +118,7 @@ import io.legado.app.ui.config.readConfig.ReadConfigViewModel
import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel
import io.legado.app.ui.dict.DictViewModel import io.legado.app.ui.dict.DictViewModel
import io.legado.app.ui.dict.rule.DictRuleViewModel import io.legado.app.ui.dict.rule.DictRuleViewModel
import io.legado.app.ui.main.MainRouteSearchContent
import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.MainViewModel
import io.legado.app.ui.main.bookshelf.BookshelfViewModel import io.legado.app.ui.main.bookshelf.BookshelfViewModel
import io.legado.app.ui.main.explore.ExploreViewModel import io.legado.app.ui.main.explore.ExploreViewModel
@@ -307,6 +308,14 @@ val appModule = module {
) )
} }
viewModelOf(::SearchContentViewModel) viewModel { (route: MainRouteSearchContent) ->
SearchContentViewModel(
bookUrl = route.bookUrl,
initialSearchWord = route.searchWord,
searchResultIndex = route.searchResultIndex,
bookRepository = get(),
searchContentRepository = get()
)
}
} }
@@ -17,6 +17,7 @@ import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException import io.legado.app.lib.webdav.WebDavException
import io.legado.app.lib.webdav.WebDavFile import io.legado.app.lib.webdav.WebDavFile
import io.legado.app.model.remote.RemoteBookWebDav import io.legado.app.model.remote.RemoteBookWebDav
import io.legado.app.ui.config.backupConfig.BackupConfig
import io.legado.app.utils.AlphanumComparator import io.legado.app.utils.AlphanumComparator
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@@ -24,7 +25,6 @@ import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.UrlUtil import io.legado.app.utils.UrlUtil
import io.legado.app.utils.compress.ZipUtils import io.legado.app.utils.compress.ZipUtils
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import io.legado.app.ui.config.backupConfig.BackupConfig
import io.legado.app.utils.isJson import io.legado.app.utils.isJson
import io.legado.app.utils.normalizeFileName import io.legado.app.utils.normalizeFileName
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -33,7 +33,6 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
import kotlin.coroutines.coroutineContext
/** /**
* webDav初始化会访问网络,不要放到主线程 * webDav初始化会访问网络,不要放到主线程
@@ -359,10 +358,10 @@ object AppWebDav {
appDb.bookDao.all.forEach { book -> appDb.bookDao.all.forEach { book ->
val progressFileName = getProgressFileName(book.name, book.author) val progressFileName = getProgressFileName(book.name, book.author)
val webDavFile = map[progressFileName] val webDavFile = map[progressFileName]
webDavFile ?: return webDavFile ?: return@forEach
if (webDavFile.lastModify <= book.syncTime) { if (webDavFile.lastModify <= book.syncTime) {
//本地同步时间大于上传时间不用同步 //本地同步时间大于上传时间不用同步
return return@forEach
} }
getBookProgress(book)?.let { bookProgress -> getBookProgress(book)?.let { bookProgress ->
if (bookProgress.durChapterIndex > book.durChapterIndex if (bookProgress.durChapterIndex > book.durChapterIndex
@@ -4,7 +4,6 @@ import android.content.Context
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build
import android.text.Html import android.text.Html
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@@ -95,10 +94,11 @@ class GlideImageGetter(private val context: Context, textView: TextView, private
} }
fun clear() { fun clear() {
targets.forEach { val snapshot = targets.toList()
targets.clear()
snapshot.forEach {
Glide.with(context).clear(it) Glide.with(context).clear(it)
} }
targets.clear()
textViewRef.clear() textViewRef.clear()
} }
@@ -3,7 +3,6 @@ package io.legado.app.help
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.config.backupConfig.BackupConfig
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookProgress import io.legado.app.data.entities.BookProgress
@@ -18,6 +17,7 @@ import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException import io.legado.app.lib.webdav.WebDavException
import io.legado.app.lib.webdav.WebDavFile import io.legado.app.lib.webdav.WebDavFile
import io.legado.app.model.remote.RemoteBookWebDav import io.legado.app.model.remote.RemoteBookWebDav
import io.legado.app.ui.config.backupConfig.BackupConfig
import io.legado.app.utils.AlphanumComparator import io.legado.app.utils.AlphanumComparator
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@@ -253,9 +253,9 @@ class WebDavManager(
} }
appDb.bookDao.all.forEach { book -> appDb.bookDao.all.forEach { book ->
val progressFileName = getProgressFileName(book.name, book.author) val progressFileName = getProgressFileName(book.name, book.author)
val webDavFile = map[progressFileName] ?: return val webDavFile = map[progressFileName] ?: return@forEach
if (webDavFile.lastModify <= book.syncTime) { if (webDavFile.lastModify <= book.syncTime) {
return return@forEach
} }
getBookProgress(book)?.let { bookProgress -> getBookProgress(book)?.let { bookProgress ->
if (bookProgress.durChapterIndex > book.durChapterIndex if (bookProgress.durChapterIndex > book.durChapterIndex
@@ -12,11 +12,11 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.localBook.TextFile import io.legado.app.model.localBook.TextFile
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.ImageUtils import io.legado.app.utils.ImageUtils
@@ -133,12 +133,12 @@ object BookHelp {
private fun clearComicCache(book: Book) { private fun clearComicCache(book: Book) {
//只处理漫画 //只处理漫画
//为0的时候,不清除已缓存数据 //为0的时候,不清除已缓存数据
if (!book.isImage || AppConfig.imageRetainNum == 0) { if (!book.isImage || ReadConfig.imageRetainNum == 0) {
return return
} }
//向前保留设定数量,向后保留预下载数量 //向前保留设定数量,向后保留预下载数量
val startIndex = book.durChapterIndex - AppConfig.imageRetainNum val startIndex = book.durChapterIndex - ReadConfig.imageRetainNum
val endIndex = book.durChapterIndex + AppConfig.preDownloadNum val endIndex = book.durChapterIndex + ReadConfig.preDownloadNum
val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl, startIndex, endIndex) val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl, startIndex, endIndex)
val imgNames = hashSetOf<String>() val imgNames = hashSetOf<String>()
//获取需要保留章节的图片信息 //获取需要保留章节的图片信息
@@ -202,7 +202,7 @@ object BookHelp {
book.getFolderName(), book.getFolderName(),
bookChapter.getFileName(), bookChapter.getFileName(),
).writeText(content) ).writeText(content)
if (book.isOnLineTxt && AppConfig.tocCountWords) { if (book.isOnLineTxt && ReadConfig.tocCountWords) {
val wordCount = StringUtils.wordCountFormat(content.length) val wordCount = StringUtils.wordCountFormat(content.length)
bookChapter.wordCount = wordCount bookChapter.wordCount = wordCount
appDb.bookChapterDao.update(bookChapter) appDb.bookChapterDao.update(bookChapter)
@@ -132,6 +132,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
ReadConfig.defaultSourceChangeAll = preferences.defaultSourceChangeAll ReadConfig.defaultSourceChangeAll = preferences.defaultSourceChangeAll
ReadConfig.sliderVibrator = preferences.sliderVibrator ReadConfig.sliderVibrator = preferences.sliderVibrator
ReadConfig.selectVibrator = preferences.selectVibrator ReadConfig.selectVibrator = preferences.selectVibrator
ReadConfig.brightnessVwPos = preferences.brightnessVwPos
ReadConfig.readBrightness = preferences.readBrightness
} }
fun updateReadBarStyleCache(value: Int) { fun updateReadBarStyleCache(value: Int) {
@@ -19,6 +19,7 @@ import io.legado.app.help.DefaultData
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.ui.config.PrefDelegate import io.legado.app.ui.config.PrefDelegate
import io.legado.app.ui.config.prefDelegate import io.legado.app.ui.config.prefDelegate
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getMeanColor import io.legado.app.utils.getMeanColor
@@ -208,6 +209,10 @@ object ReadBookConfig {
var readMenuBorderColorNight by prefDelegate(PreferKey.readMenuBorderColorNight, 0) var readMenuBorderColorNight by prefDelegate(PreferKey.readMenuBorderColorNight, 0)
var showTitleBarIcons by prefDelegate(PreferKey.showTitleBarIcons, true) var showTitleBarIcons by prefDelegate(PreferKey.showTitleBarIcons, true)
var readSliderMode by prefDelegate(PreferKey.readSliderMode, "0") var readSliderMode by prefDelegate(PreferKey.readSliderMode, "0")
var showBrightnessView by prefDelegate(PreferKey.showBrightnessView, "1")
var brightnessVwPos by prefDelegate(PreferKey.brightnessVwPos, "1")
var readBrightness by prefDelegate(PreferKey.brightness, 100)
var brightnessAuto by prefDelegate(PreferKey.brightnessAuto, false)
var styleSelect: Int var styleSelect: Int
get() = if (isComic) comicStyleSelect else readStyleSelect get() = if (isComic) comicStyleSelect else readStyleSelect
@@ -301,7 +306,7 @@ object ReadBookConfig {
val resolvedMenuBgColor: Int val resolvedMenuBgColor: Int
get() { get() {
val isNight = ReadStyleResolver.isNightTheme() val isNight = ReadStyleResolver.isNightTheme()
return when (AppConfig.readBarStyle) { return when (ReadConfig.readBarStyle) {
1 -> { // 跟随阅读背景 1 -> { // 跟随阅读背景
val background = ReadStyleResolver.currentBackground(durConfig) val background = ReadStyleResolver.currentBackground(durConfig)
if (background.type == 0) { if (background.type == 0) {
@@ -5,6 +5,7 @@ import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.BitmapUtils import io.legado.app.utils.BitmapUtils
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.externalFiles import io.legado.app.utils.externalFiles
@@ -28,14 +29,14 @@ object ReadStyleResolver {
fun currentMode(): ReadStyleMode { fun currentMode(): ReadStyleMode {
return when { return when {
AppConfig.isEInkMode -> ReadStyleMode.EInk ReadConfig.isEInkMode -> ReadStyleMode.EInk
AppConfig.isNightTheme -> ReadStyleMode.Night ReadConfig.isNightTheme -> ReadStyleMode.Night
else -> ReadStyleMode.Day else -> ReadStyleMode.Day
} }
} }
fun isNightTheme(): Boolean { fun isNightTheme(): Boolean {
return AppConfig.isNightTheme return ReadConfig.isNightTheme
} }
fun setCurrentBackground( fun setCurrentBackground(
@@ -8,10 +8,10 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.HttpTTS import io.legado.app.data.entities.HttpTTS
import io.legado.app.help.config.AppConfig
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.HttpReadAloudService import io.legado.app.service.HttpReadAloudService
import io.legado.app.service.TTSReadAloudService import io.legado.app.service.TTSReadAloudService
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
import io.legado.app.utils.StringUtils import io.legado.app.utils.StringUtils
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
@@ -21,7 +21,7 @@ import splitties.init.appCtx
object ReadAloud { object ReadAloud {
private var aloudClass: Class<*> = getReadAloudClass() private var aloudClass: Class<*> = getReadAloudClass()
val ttsEngine get() = ReadBook.book?.getTtsEngine() ?: AppConfig.ttsEngine val ttsEngine get() = ReadBook.book?.getTtsEngine() ?: ReadConfig.ttsEngine
var httpTTS: HttpTTS? = null var httpTTS: HttpTTS? = null
private fun getReadAloudClass(): Class<*> { private fun getReadAloudClass(): Class<*> {
@@ -21,7 +21,6 @@ import io.legado.app.help.book.isSameNameAuthor
import io.legado.app.help.book.readSimulating import io.legado.app.help.book.readSimulating
import io.legado.app.help.book.simulatedTotalChapterNum import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.book.update 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.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.globalExecutor import io.legado.app.help.globalExecutor
@@ -29,7 +28,6 @@ import io.legado.app.model.localBook.TextFile
import io.legado.app.model.translation.TranslationChapterState import io.legado.app.model.translation.TranslationChapterState
import io.legado.app.model.translation.TranslationChapterStatus import io.legado.app.model.translation.TranslationChapterStatus
import io.legado.app.model.translation.TranslationManager import io.legado.app.model.translation.TranslationManager
import kotlinx.coroutines.flow.MutableStateFlow
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.CacheBookService import io.legado.app.service.CacheBookService
@@ -37,6 +35,7 @@ import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextPage 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.ChapterProvider
import io.legado.app.ui.book.read.page.provider.LayoutProgressListener import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.stackTraceStr import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -51,6 +50,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@@ -211,7 +211,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
ReadBookConfig.isComic = book.isImage ReadBookConfig.isComic = book.isImage
if (oldIndex != ReadBookConfig.styleSelect) { if (oldIndex != ReadBookConfig.styleSelect) {
postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5)) postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))
if (AppConfig.readBarStyleFollowPage) { if (ReadConfig.readBarStyleFollowPage) {
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
} }
} }
@@ -288,7 +288,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
uploadSuccessAction: (() -> Unit)? = null, uploadSuccessAction: (() -> Unit)? = null,
syncSuccessAction: (() -> Unit)? = null syncSuccessAction: (() -> Unit)? = null
) { ) {
if (!AppConfig.syncBookProgress) return if (!ReadConfig.syncBookProgress) return
val book = book ?: return val book = book ?: return
Coroutine.async { Coroutine.async {
AppWebDav.getBookProgress(book) AppWebDav.getBookProgress(book)
@@ -301,7 +301,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
) { ) {
// 服务器没有进度或者进度比服务器快,上传现有进度 // 服务器没有进度或者进度比服务器快,上传现有进度
Coroutine.async { Coroutine.async {
AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction) AppWebDav.uploadBookProgress(book, onSuccess = uploadSuccessAction)
book.update() book.update()
} }
} else if (progress.durChapterIndex > book.durChapterIndex || } else if (progress.durChapterIndex > book.durChapterIndex ||
@@ -560,7 +560,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
} }
fun recycleRecorders(beforeIndex: Int, afterIndex: Int) { fun recycleRecorders(beforeIndex: Int, afterIndex: Int) {
if (!AppConfig.optimizeRender) { if (!ReadConfig.optimizeRender) {
return return
} }
executor.execute { executor.execute {
@@ -1131,7 +1131,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
private fun preDownload() { private fun preDownload() {
if (book?.isLocal == true) return if (book?.isLocal == true) return
executor.execute { executor.execute {
if (AppConfig.preDownloadNum < 2) { if (ReadConfig.preDownloadNum < 2) {
return@execute return@execute
} }
preDownloadTask?.cancel() preDownloadTask?.cancel()
@@ -1139,7 +1139,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
//预下载 //预下载
launch { launch {
val maxChapterIndex = val maxChapterIndex =
min(durChapterIndex + AppConfig.preDownloadNum, chapterSize) min(durChapterIndex + ReadConfig.preDownloadNum, chapterSize)
for (i in durChapterIndex.plus(2)..maxChapterIndex) { for (i in durChapterIndex.plus(2)..maxChapterIndex) {
if (downloadedChapters.contains(i)) continue if (downloadedChapters.contains(i)) continue
if ((downloadFailChapters[i] ?: 0) >= 3) continue if ((downloadFailChapters[i] ?: 0) >= 3) continue
@@ -1147,7 +1147,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
} }
} }
launch { launch {
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) val minChapterIndex = durChapterIndex - min(5, ReadConfig.preDownloadNum)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) { for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
if (downloadedChapters.contains(i)) continue if (downloadedChapters.contains(i)) continue
if ((downloadFailChapters[i] ?: 0) >= 3) continue if ((downloadFailChapters[i] ?: 0) >= 3) continue
@@ -18,8 +18,6 @@ import io.legado.app.help.book.isSameNameAuthor
import io.legado.app.help.book.readSimulating import io.legado.app.help.book.readSimulating
import io.legado.app.help.book.simulatedTotalChapterNum import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.book.update import io.legado.app.help.book.update
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.readMangaConfig.ReadMangaConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.globalExecutor import io.legado.app.help.globalExecutor
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
@@ -28,6 +26,8 @@ import io.legado.app.ui.book.manga.entities.MangaChapter
import io.legado.app.ui.book.manga.entities.MangaContent import io.legado.app.ui.book.manga.entities.MangaContent
import io.legado.app.ui.book.manga.entities.MangaPage import io.legado.app.ui.book.manga.entities.MangaPage
import io.legado.app.ui.book.manga.entities.ReaderLoading import io.legado.app.ui.book.manga.entities.ReaderLoading
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.config.readMangaConfig.ReadMangaConfig
import io.legado.app.utils.mapIndexed import io.legado.app.utils.mapIndexed
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
@@ -45,7 +45,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import kotlin.getValue
import kotlin.math.min import kotlin.math.min
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@@ -472,7 +471,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
private fun preDownload() { private fun preDownload() {
if (book?.isLocal == true) return if (book?.isLocal == true) return
executor.execute { executor.execute {
if (AppConfig.preDownloadNum < 2) { if (ReadConfig.preDownloadNum < 2) {
return@execute return@execute
} }
preDownloadTask?.cancel() preDownloadTask?.cancel()
@@ -480,7 +479,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
//预下载 //预下载
launch { launch {
val maxChapterIndex = val maxChapterIndex =
min(durChapterIndex + AppConfig.preDownloadNum, chapterSize) min(durChapterIndex + ReadConfig.preDownloadNum, chapterSize)
for (i in durChapterIndex.plus(2)..maxChapterIndex) { for (i in durChapterIndex.plus(2)..maxChapterIndex) {
if (downloadedChapters.contains(i)) continue if (downloadedChapters.contains(i)) continue
if ((downloadFailChapters[i] ?: 0) >= 3) continue if ((downloadFailChapters[i] ?: 0) >= 3) continue
@@ -488,7 +487,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
} }
} }
launch { launch {
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) val minChapterIndex = durChapterIndex - min(5, ReadConfig.preDownloadNum)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) { for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
if (downloadedChapters.contains(i)) continue if (downloadedChapters.contains(i)) continue
if ((downloadFailChapters[i] ?: 0) >= 3) continue if ((downloadFailChapters[i] ?: 0) >= 3) continue
@@ -594,7 +593,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
uploadSuccessAction: (() -> Unit)? = null, uploadSuccessAction: (() -> Unit)? = null,
syncSuccessAction: (() -> Unit)? = null, syncSuccessAction: (() -> Unit)? = null,
) { ) {
if (!AppConfig.syncBookProgress) return if (!ReadConfig.syncBookProgress) return
val book = book ?: return val book = book ?: return
Coroutine.async { Coroutine.async {
AppWebDav.getBookProgress(book) AppWebDav.getBookProgress(book)
@@ -607,7 +606,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
) { ) {
// 服务器没有进度或者进度比服务器快,上传现有进度 // 服务器没有进度或者进度比服务器快,上传现有进度
Coroutine.async { Coroutine.async {
AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction) AppWebDav.uploadBookProgress(book, onSuccess = uploadSuccessAction)
book.update() book.update()
} }
} else if (progress.durChapterIndex > book.durChapterIndex || } else if (progress.durChapterIndex > book.durChapterIndex ||
@@ -34,8 +34,6 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.HttpTTS import io.legado.app.data.entities.HttpTTS
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.exoplayer.InputStreamDataSource import io.legado.app.help.exoplayer.InputStreamDataSource
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
@@ -43,6 +41,7 @@ import io.legado.app.model.ReadAloud
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
@@ -98,7 +97,7 @@ class HttpReadAloudService : BaseReadAloudService(),
private val loadErrorHandlingPolicy by lazy { private val loadErrorHandlingPolicy by lazy {
CustomLoadErrorHandlingPolicy() CustomLoadErrorHandlingPolicy()
} }
private var speechRate: Int = AppConfig.speechRatePlay + 5 private var speechRate: Int = ReadConfig.speechRatePlay + 5
private var downloadTask: Coroutine<*>? = null private var downloadTask: Coroutine<*>? = null
private var playIndexJob: Job? = null private var playIndexJob: Job? = null
private var downloadErrorNo: Int = 0 private var downloadErrorNo: Int = 0
@@ -210,7 +209,7 @@ class HttpReadAloudService : BaseReadAloudService(),
private suspend fun preDownloadAudios(httpTts: HttpTTS) { private suspend fun preDownloadAudios(httpTts: HttpTTS) {
val book = ReadBook.book ?: return val book = ReadBook.book ?: return
val currentIdx = ReadBook.durChapterIndex val currentIdx = ReadBook.durChapterIndex
val limit = AppConfig.audioPreDownloadNum val limit = ReadConfig.audioPreDownloadNum
try { try {
for (i in 1..limit) { for (i in 1..limit) {
@@ -300,7 +299,7 @@ class HttpReadAloudService : BaseReadAloudService(),
) { ) {
val book = ReadBook.book ?: return val book = ReadBook.book ?: return
val currentIdx = ReadBook.durChapterIndex val currentIdx = ReadBook.durChapterIndex
val limit = AppConfig.audioPreDownloadNum val limit = ReadConfig.audioPreDownloadNum
try { try {
for (i in 1..limit) { for (i in 1..limit) {
@@ -491,7 +490,7 @@ class HttpReadAloudService : BaseReadAloudService(),
* 如果时间设置为0,则不再保护当前章节,退出即全删。 * 如果时间设置为0,则不再保护当前章节,退出即全删。
*/ */
private fun removeCacheFile() { private fun removeCacheFile() {
val keepTime = AppConfig.audioCacheCleanTime val keepTime = ReadConfig.audioCacheCleanTime
// 只有当时间大于0时,才需要保护当前章节。如果为0,说明用户想彻底不留缓存。 // 只有当时间大于0时,才需要保护当前章节。如果为0,说明用户想彻底不留缓存。
val protectCurrentChapter = keepTime > 0 val protectCurrentChapter = keepTime > 0
val titleMd5 = if (protectCurrentChapter) MD5Utils.md5Encode16(this.textChapter?.chapter?.title ?: "") else "" val titleMd5 = if (protectCurrentChapter) MD5Utils.md5Encode16(this.textChapter?.chapter?.title ?: "") else ""
@@ -572,7 +571,7 @@ class HttpReadAloudService : BaseReadAloudService(),
override fun upSpeechRate(reset: Boolean) { override fun upSpeechRate(reset: Boolean) {
downloadTask?.cancel() downloadTask?.cancel()
exoPlayer.stop() exoPlayer.stop()
speechRate = AppConfig.speechRatePlay + 5 speechRate = ReadConfig.speechRatePlay + 5
if (ReadConfig.streamReadAloudAudio) { if (ReadConfig.streamReadAloudAudio) {
downloadAndPlayAudiosStream() downloadAndPlayAudiosStream()
} else { } else {
@@ -38,12 +38,9 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.databinding.ActivityMangaBinding import io.legado.app.databinding.ActivityMangaBinding
import io.legado.app.databinding.ViewLoadMoreBinding import io.legado.app.databinding.ViewLoadMoreBinding
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.AppFreezeMonitor.handler
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.isImage import io.legado.app.help.book.isImage
import io.legado.app.help.book.isLocal import io.legado.app.help.book.isLocal
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.readMangaConfig.ReadMangaConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.source.getSourceType import io.legado.app.help.source.getSourceType
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
@@ -78,6 +75,8 @@ import io.legado.app.ui.book.read.observeEyeProtectionEvents
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.browser.WebViewActivity import io.legado.app.ui.browser.WebViewActivity
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.config.readMangaConfig.ReadMangaConfig
import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.LoadMoreView
@@ -220,7 +219,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
binding.llRetry.isGone = true binding.llRetry.isGone = true
ReadManga.loadOrUpContent() ReadManga.loadOrUpContent()
} }
binding.flLoading.isVisible = !AppConfig.isEInkMode binding.flLoading.isVisible = !ReadConfig.isEInkMode
binding.eyeProtectionOverlay.refresh() binding.eyeProtectionOverlay.refresh()
mAdapter.addFooterView { mAdapter.addFooterView {
ViewLoadMoreBinding.bind(loadMoreView) ViewLoadMoreBinding.bind(loadMoreView)
@@ -458,7 +457,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
networkChangedListener.register() networkChangedListener.register()
networkChangedListener.onNetworkChanged = { networkChangedListener.onNetworkChanged = {
// 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑) // 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑)
if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) { if (ReadConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
ReadManga.syncProgress({ progress -> sureNewProgress(progress) }) ReadManga.syncProgress({ progress -> sureNewProgress(progress) })
} }
} }
@@ -480,7 +479,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
if (ReadManga.inBookshelf) { if (ReadManga.inBookshelf) {
ReadManga.saveRead() ReadManga.saveRead()
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
if (AppConfig.syncBookProgressPlus) { if (ReadConfig.syncBookProgressPlus) {
ReadManga.syncProgress() ReadManga.syncProgress()
} else { } else {
ReadManga.uploadProgress() ReadManga.uploadProgress()
@@ -23,9 +23,7 @@ import io.legado.app.help.book.isLocal
import io.legado.app.help.book.isLocalModified import io.legado.app.help.book.isLocalModified
import io.legado.app.help.book.removeType import io.legado.app.help.book.removeType
import io.legado.app.help.book.simulatedTotalChapterNum import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook
import io.legado.app.model.ReadManga import io.legado.app.model.ReadManga
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
@@ -48,6 +46,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import io.legado.app.ui.config.readConfig.ReadConfig as GlobalReadConfig
class ReadMangaViewModel( class ReadMangaViewModel(
application: Application, application: Application,
@@ -112,7 +111,7 @@ class ReadMangaViewModel(
// 有章节跳转不同步阅读进度 // 有章节跳转不同步阅读进度
ReadManga.chapterChanged = false ReadManga.chapterChanged = false
} else if (!isSameBook) { } else if (!isSameBook) {
if (AppConfig.syncBookProgressPlus) { if (GlobalReadConfig.syncBookProgressPlus) {
ReadManga.syncProgress( ReadManga.syncProgress(
{ progress -> ReadManga.mCallback?.sureNewProgress(progress) }) { progress -> ReadManga.mCallback?.sureNewProgress(progress) })
} else { } else {
@@ -195,7 +194,7 @@ class ReadMangaViewModel(
* 自动换源 * 自动换源
*/ */
private fun autoChangeSource(name: String, author: String) { private fun autoChangeSource(name: String, author: String) {
if (!AppConfig.autoChangeSource) return if (!GlobalReadConfig.autoChangeSource) return
execute { execute {
val sources = appDb.bookSourceDao.allTextEnabledPart val sources = appDb.bookSourceDao.allTextEnabledPart
flow { flow {
@@ -246,7 +245,7 @@ class ReadMangaViewModel(
book: Book, book: Book,
alertSync: ((progress: BookProgress) -> Unit)? = null alertSync: ((progress: BookProgress) -> Unit)? = null
) { ) {
if (!AppConfig.syncBookProgress) return if (!GlobalReadConfig.syncBookProgress) return
execute { execute {
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress() getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
}.onError { }.onError {
@@ -14,12 +14,12 @@ import androidx.core.view.isVisible
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import io.legado.app.R import io.legado.app.R
import io.legado.app.databinding.ViewMangaMenuBinding import io.legado.app.databinding.ViewMangaMenuBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.help.source.getSourceType import io.legado.app.help.source.getSourceType
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.model.ReadManga import io.legado.app.model.ReadManga
import io.legado.app.ui.browser.WebViewActivity import io.legado.app.ui.browser.WebViewActivity
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.applyNavigationBarPadding import io.legado.app.utils.applyNavigationBarPadding
import io.legado.app.utils.gone import io.legado.app.utils.gone
@@ -112,13 +112,13 @@ class MangaMenu @JvmOverloads constructor(
// val brightnessBackground = GradientDrawable() // val brightnessBackground = GradientDrawable()
// brightnessBackground.cornerRadius = 5F.dpToPx() // brightnessBackground.cornerRadius = 5F.dpToPx()
// //brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f)) // //brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f))
// if (AppConfig.isEInkMode) { // if (ReadConfig.isEInkMode) {
// titleBar.setBackgroundResource(R.drawable.bg_eink_border_bottom) // titleBar.setBackgroundResource(R.drawable.bg_eink_border_bottom)
// bottomMenu.setBackgroundResource(R.drawable.bg_eink_border_top) // bottomMenu.setBackgroundResource(R.drawable.bg_eink_border_top)
// } else { // } else {
// //bottomMenu.setBackgroundColor(bgColor) // //bottomMenu.setBackgroundColor(bgColor)
// } // }
if (AppConfig.showReadTitleBarAddition) { if (ReadConfig.showReadTitleAddition) {
titleBarAddition.visible() titleBarAddition.visible()
} else { } else {
titleBarAddition.gone() titleBarAddition.gone()
@@ -134,7 +134,7 @@ class MangaMenu @JvmOverloads constructor(
menuTopOut.setAnimationListener(menuOutListener) menuTopOut.setAnimationListener(menuOutListener)
} }
fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode) { fun runMenuOut(anim: Boolean = !ReadConfig.isEInkMode) {
if (isMenuOutAnimating) { if (isMenuOutAnimating) {
return return
} }
@@ -149,7 +149,7 @@ class MangaMenu @JvmOverloads constructor(
} }
} }
fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) { fun runMenuIn(anim: Boolean = !ReadConfig.isEInkMode) {
this.visible() this.visible()
binding.titleBar.visible() binding.titleBar.visible()
binding.bottomMenu.visible() binding.bottomMenu.visible()
@@ -181,7 +181,7 @@ class MangaMenu @JvmOverloads constructor(
callBack.openBookInfoActivity() callBack.openBookInfoActivity()
} }
val chapterViewClickListener = OnClickListener { val chapterViewClickListener = OnClickListener {
if (AppConfig.readUrlInBrowser) { if (ReadConfig.readUrlInBrowser) {
context.openUrl(tvChapterUrl.text.toString().substringBefore(",{")) context.openUrl(tvChapterUrl.text.toString().substringBefore(",{"))
} else { } else {
context.startActivity<WebViewActivity> { context.startActivity<WebViewActivity> {
@@ -199,10 +199,10 @@ class MangaMenu @JvmOverloads constructor(
context.alert(R.string.open_fun) { context.alert(R.string.open_fun) {
setMessage(R.string.use_browser_open) setMessage(R.string.use_browser_open)
okButton { okButton {
AppConfig.readUrlInBrowser = true ReadConfig.readUrlInBrowser = true
} }
noButton { noButton {
AppConfig.readUrlInBrowser = false ReadConfig.readUrlInBrowser = false
} }
} }
true true
@@ -196,6 +196,10 @@ data class ReadMenuConfig(
val readMenuCustomIcons: ImmutableMap<String, String> = persistentMapOf(), val readMenuCustomIcons: ImmutableMap<String, String> = persistentMapOf(),
val titleBarButtons: ImmutableList<ReadBookButtonConfigItem> = persistentListOf(), val titleBarButtons: ImmutableList<ReadBookButtonConfigItem> = persistentListOf(),
val bottomBarButtons: ImmutableList<ReadBookButtonConfigItem> = persistentListOf(), val bottomBarButtons: ImmutableList<ReadBookButtonConfigItem> = persistentListOf(),
val showBrightnessView: String = "1",
val brightnessVwPos: String = "1",
val readBrightness: Int = 100,
val brightnessAuto: Boolean = false,
) )
@Immutable @Immutable
@@ -300,7 +304,7 @@ sealed interface ReadBookIntent {
// Brightness // Brightness
data class SetBrightness(val value: Int) : ReadBookIntent data class SetBrightness(val value: Int) : ReadBookIntent
data object ToggleBrightnessAuto : ReadBookIntent data class ToggleBrightnessAuto(val auto: Boolean) : ReadBookIntent
// Seek bar jump // Seek bar jump
data class SeekToChapter(val index: Int) : ReadBookIntent data class SeekToChapter(val index: Int) : ReadBookIntent
@@ -526,7 +530,7 @@ sealed interface ReadBookEffect {
data object CancelSelect : ReadBookEffect data object CancelSelect : ReadBookEffect
data object UpSystemUiVisibility : ReadBookEffect data object UpSystemUiVisibility : ReadBookEffect
data class SetBrightness(val value: Int) : ReadBookEffect data class SetBrightness(val value: Int) : ReadBookEffect
data object ToggleBrightnessAuto : ReadBookEffect data class ToggleBrightnessAuto(val auto: Boolean, val value: Int) : ReadBookEffect
// Read aloud / auto page // Read aloud / auto page
data object ToggleReadAloud : ReadBookEffect data object ToggleReadAloud : ReadBookEffect
@@ -535,7 +539,15 @@ sealed interface ReadBookEffect {
// Search // Search
data class OpenSearchActivity(val word: String?, val bookUrl: String) : ReadBookEffect data class OpenSearchActivity(val word: String?, val bookUrl: String) : ReadBookEffect
data class NavigateToSearchResult(val result: SearchResult) : ReadBookEffect data class NavigateToSearchResult(
val result: SearchResult,
val chapterIndex: Int,
val pageIndex: Int,
val lineIndex: Int,
val startCharIndex: Int,
val endLineIndex: Int,
val endCharIndex: Int,
) : ReadBookEffect
data object ExitSearch : ReadBookEffect data object ExitSearch : ReadBookEffect
// Source actions // Source actions
@@ -1040,7 +1052,15 @@ sealed interface ConfigUpdate {
data class UseZhLayout(val value: Boolean) : ConfigUpdate { data class UseZhLayout(val value: Boolean) : ConfigUpdate {
override val actions = setOf(ConfigUpdateAction.ReloadContent) override val actions = setOf(ConfigUpdateAction.ReloadContent)
} }
data class ShowBrightnessView(val value: Boolean) : ConfigUpdate { data class ShowBrightnessView(val value: String) : ConfigUpdate {
override val actions = emptySet<ConfigUpdateAction>()
}
data class BrightnessVwPos(val value: String) : ConfigUpdate {
override val actions = emptySet<ConfigUpdateAction>()
}
data class BrightnessAuto(val value: Boolean) : ConfigUpdate {
override val actions = emptySet<ConfigUpdateAction>() override val actions = emptySet<ConfigUpdateAction>()
} }
data class UseUnderlineGlobal(val value: Boolean) : ConfigUpdate { data class UseUnderlineGlobal(val value: Boolean) : ConfigUpdate {
@@ -9,6 +9,7 @@ import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.HapticFeedbackConstantsCompat import androidx.core.view.HapticFeedbackConstantsCompat
@@ -23,7 +24,6 @@ import io.legado.app.constant.BookType
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookProgress import io.legado.app.data.entities.BookProgress
import io.legado.app.help.TTS import io.legado.app.help.TTS
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.lib.dialogs.SelectItem import io.legado.app.lib.dialogs.SelectItem
@@ -40,8 +40,10 @@ import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.book.read.page.ContentTextView 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.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.login.SourceLoginJsExtensions import io.legado.app.ui.login.SourceLoginJsExtensions
import io.legado.app.ui.widget.PopupAction import io.legado.app.ui.widget.PopupAction
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils
@@ -60,6 +62,7 @@ import io.legado.app.utils.throttle
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -341,7 +344,7 @@ class ReadBookController(
r.textMenuPosition.x = x r.textMenuPosition.x = x
r.textMenuPosition.y = top r.textMenuPosition.y = top
if (AppConfig.selectVibrator) { if (ReadConfig.selectVibrator) {
r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE) r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE)
} }
} }
@@ -351,7 +354,7 @@ class ReadBookController(
r.cursorRight.x = x r.cursorRight.x = x
r.cursorRight.y = y r.cursorRight.y = y
r.cursorRight.visible(true) r.cursorRight.visible(true)
if (AppConfig.selectVibrator) { if (ReadConfig.selectVibrator) {
r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE) r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE)
} }
} }
@@ -616,7 +619,7 @@ class ReadBookController(
is ReadBookEffect.LongToast -> activity.longToastOnUi(effect.message) is ReadBookEffect.LongToast -> activity.longToastOnUi(effect.message)
is ReadBookEffect.SetBrightness -> { is ReadBookEffect.SetBrightness -> {
val lp = activity.window.attributes val lp = activity.window.attributes
lp.screenBrightness = effect.value / 255f lp.screenBrightness = effect.value / 100f
activity.window.attributes = lp activity.window.attributes = lp
} }
@@ -682,7 +685,14 @@ class ReadBookController(
screenOffTimerStart() screenOffTimerStart()
} }
is ReadBookEffect.ToggleBrightnessAuto -> { /* TODO */ is ReadBookEffect.ToggleBrightnessAuto -> {
val lp = activity.window.attributes
if (effect.auto) {
lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
} else {
lp.screenBrightness = effect.value / 100f
}
activity.window.attributes = lp
} }
// ── Phase 4: Activity-dependent effects ── // ── Phase 4: Activity-dependent effects ──
@@ -695,15 +705,48 @@ class ReadBookController(
is ReadBookEffect.TextActionSpeak -> speak(effect.text) is ReadBookEffect.TextActionSpeak -> speak(effect.text)
is ReadBookEffect.NavigateToSearchResult -> { is ReadBookEffect.NavigateToSearchResult -> {
// Navigate handled by ReadView — no external callback needed if (effect.pageIndex < 0) {
// Chapter not loaded — open it, then mark in the success callback
ReadBook.openChapter(effect.chapterIndex) {
val tc = ReadBook.curTextChapter ?: return@openChapter
val query = effect.result.query
val pos = viewModel.searchResultPositions(tc, effect.result, query)
val lineIndex = pos[1]
val charIndex = pos[2]
val addLine = pos[3]
val charIndex2 = pos[4]
val queryLength = query.length
val endLineIndex = lineIndex + addLine.coerceAtLeast(0)
val endCharIndex = if (addLine == 0) {
charIndex + queryLength
} else {
charIndex2 + 1
}
activity.lifecycleScope.launch(Main) {
navigatePageByPos(tc, pos[0])
markSearchResultOnPage(
intArrayOf(
pos[0], lineIndex, charIndex, endLineIndex, endCharIndex
)
)
}
}
} else {
// Same chapter — navigate to page, then mark
val tc = ReadBook.curTextChapter ?: return
navigatePageByPos(tc, effect.pageIndex)
markSearchResultOnPage(
intArrayOf(
effect.pageIndex, effect.lineIndex,
effect.startCharIndex, effect.endLineIndex, effect.endCharIndex
)
)
}
} }
is ReadBookEffect.ExitSearch -> { is ReadBookEffect.ExitSearch -> {
if (viewModel.uiState.value.isShowingSearchResult) { ReadBook.clearSearchResult()
viewModel.onIntent(ReadBookIntent.SetShowingSearchResult(false)) refs?.readView?.cancelSelect(clearSearchResult = true)
ReadBook.clearSearchResult()
refs?.readView?.cancelSelect(true)
}
} }
is ReadBookEffect.SyncBookProgress -> { is ReadBookEffect.SyncBookProgress -> {
@@ -935,17 +978,17 @@ class ReadBookController(
} }
override fun mouseWheelPage(direction: PageDirection) { override fun mouseWheelPage(direction: PageDirection) {
if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) { if (menuLayoutIsVisible || !ReadConfig.mouseWheelPage) {
return return
} }
keyPageDebounce(direction, mouseWheel = true, longPress = false) keyPageDebounce(direction, mouseWheel = true, longPress = false)
} }
private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean { private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {
if (!AppConfig.volumeKeyPage) { if (!ReadConfig.volumeKeyPage) {
return false return false
} }
if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) { if (!ReadConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
return false return false
} }
handleKeyPage(direction, longPress) handleKeyPage(direction, longPress)
@@ -953,7 +996,7 @@ class ReadBookController(
} }
override fun handleKeyPage(direction: PageDirection, longPress: Boolean) { override fun handleKeyPage(direction: PageDirection, longPress: Boolean) {
if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) { if (ReadConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
keyPage(direction) keyPage(direction)
} else { } else {
keyPageDebounce(direction, longPress = longPress) keyPageDebounce(direction, longPress = longPress)
@@ -1057,7 +1100,7 @@ class ReadBookController(
} }
fun setOrientation() { fun setOrientation() {
when (AppConfig.screenOrientation) { when (ReadConfig.screenOrientation) {
"0" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED "0" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
"1" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT "1" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
"2" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE "2" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
@@ -1066,4 +1109,38 @@ class ReadBookController(
"5" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE "5" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
} }
} }
// ── Search result navigation helpers ─────────────────────────────
/**
* Navigate to the page at [pageIndex] within the current chapter.
*/
private fun navigatePageByPos(textChapter: TextChapter, pageIndex: Int) {
val pagePos = textChapter.getReadLength(pageIndex)
if (ReadBook.durChapterPos != pagePos) {
ReadBook.durChapterPos = pagePos
ReadBook.callBack?.upContent()
ReadBook.callBack?.cancelSelect()
}
}
/**
* Mark search result columns on the current page for highlighting.
* @param pos array of [pageIndex, lineIndex, startCharIndex, endLineIndex, endCharIndex]
*/
private fun markSearchResultOnPage(pos: IntArray) {
val readView = refs?.readView ?: return
val lineIndex = pos[1]
val startCharIndex = pos[2]
val endLineIndex = pos[3]
val endCharIndex = pos[4]
readView.cancelSelect(clearSearchResult = true)
isSelectingSearchResult = true
try {
readView.curPage.selectStartMoveIndex(0, lineIndex, startCharIndex)
readView.curPage.selectEndMoveIndex(0, endLineIndex, endCharIndex)
} finally {
isSelectingSearchResult = false
}
}
} }
@@ -6,11 +6,15 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform import androidx.compose.animation.SizeTransform
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
@@ -27,6 +31,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
@@ -54,10 +59,14 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material.icons.filled.Block import androidx.compose.material.icons.filled.Block
import androidx.compose.material.icons.filled.BrightnessAuto
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Payment import androidx.compose.material.icons.filled.Payment
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -126,7 +135,6 @@ import io.legado.app.R
import io.legado.app.constant.ReadMenuBlurMode import io.legado.app.constant.ReadMenuBlurMode
import io.legado.app.constant.ReadMenuBlurStyle import io.legado.app.constant.ReadMenuBlurStyle
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadStyleResolver import io.legado.app.help.config.ReadStyleResolver
import io.legado.app.ui.animation.DampedDragAnimation import io.legado.app.ui.animation.DampedDragAnimation
import io.legado.app.ui.animation.InteractiveHighlight import io.legado.app.ui.animation.InteractiveHighlight
@@ -137,9 +145,11 @@ import io.legado.app.ui.book.read.sheet.ReadMenuButtonInfo
import io.legado.app.ui.book.read.sheet.ReadStyleContent import io.legado.app.ui.book.read.sheet.ReadStyleContent
import io.legado.app.ui.book.read.sheet.ReadStyleTextTitleContent import io.legado.app.ui.book.read.sheet.ReadStyleTextTitleContent
import io.legado.app.ui.book.read.sheet.readMenuButtonInfos import io.legado.app.ui.book.read.sheet.readMenuButtonInfos
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.hazeStyle.HazeLegado import io.legado.app.ui.theme.hazeStyle.HazeLegado
import io.legado.app.ui.widget.components.AppSlider import io.legado.app.ui.widget.components.AppSlider
import io.legado.app.ui.widget.components.AppVerticalSlider
import io.legado.app.ui.widget.components.bookmark.BookmarkEditContent import io.legado.app.ui.widget.components.bookmark.BookmarkEditContent
import io.legado.app.ui.widget.components.button.series.SmallTonalButton import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
@@ -168,12 +178,29 @@ fun ReadBookMenuBar(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val currentRoute = state.menuState.currentRoute val currentRoute = state.menuState.currentRoute
val searchMenuVisible = state.isShowingSearchResult &&
state.searchMenuVisible &&
!state.menuVisible
val contentTarget = if (searchMenuVisible) {
ReadBookMenuContent.Search
} else {
ReadBookMenuContent.Route(currentRoute)
}
val dialogLikeRoute = currentRoute == ReadBookMenuRoute.PaddingConfig val dialogLikeRoute = currentRoute == ReadBookMenuRoute.PaddingConfig
var readStylePage by remember { mutableIntStateOf(0) }
LaunchedEffect(currentRoute) {
if (currentRoute != ReadBookMenuRoute.ReadStyle) {
readStylePage = 0
}
}
val hideTopBar = dialogLikeRoute ||
currentRoute == ReadBookMenuRoute.ReadStyle && readStylePage >= 1 ||
currentRoute == ReadBookMenuRoute.TextTitle
val menuColors = readMenuColors() val menuColors = readMenuColors()
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
AnimatedVisibility( AnimatedVisibility(
visible = state.menuVisible, visible = state.menuVisible || searchMenuVisible,
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
) { ) {
@@ -183,13 +210,19 @@ fun ReadBookMenuBar(
.clickable( .clickable(
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
) { onIntent(ReadBookIntent.HideMenu) } ) {
if (searchMenuVisible) {
onIntent(ReadBookIntent.HideSearchMenu)
} else {
onIntent(ReadBookIntent.HideMenu)
}
}
) )
} }
// Top title bar + floating icon row (top positions) // Top title bar + floating icon row (top positions)
AnimatedVisibility( AnimatedVisibility(
visible = state.menuVisible && !dialogLikeRoute, visible = state.menuVisible && !hideTopBar,
enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(), enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(), exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(),
modifier = Modifier.align(Alignment.TopCenter), modifier = Modifier.align(Alignment.TopCenter),
@@ -218,15 +251,97 @@ fun ReadBookMenuBar(
} }
} }
// Vertical brightness bar (right or left side)
val brightnessMode = state.menuConfig.showBrightnessView
val brightnessVwPos = state.menuConfig.brightnessVwPos
val brightnessIsLeft = brightnessVwPos == "0"
val brightnessShape = RoundedCornerShape(40.dp)
val useBrightnessHaze = readMenuBottomBarHazeEnabled(
hazeState = hazeState,
menuConfig = state.menuConfig,
isFloating = false,
)
AnimatedVisibility(
visible = brightnessMode == "2" && state.menuVisible,
enter = slideInHorizontally(
initialOffsetX = { if (brightnessIsLeft) -it else it }
) + fadeIn(),
exit = slideOutHorizontally(
targetOffsetX = { if (brightnessIsLeft) -it else it }
) + fadeOut(),
modifier = Modifier.align(
if (brightnessIsLeft) Alignment.CenterStart else Alignment.CenterEnd
),
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(
start = if (brightnessIsLeft) 8.dp else 0.dp,
end = if (brightnessIsLeft) 0.dp else 8.dp,
),
contentAlignment = if (brightnessIsLeft) Alignment.CenterStart else Alignment.CenterEnd,
) {
Surface(
modifier = if (useBrightnessHaze && hazeState != null) {
Modifier.readMenuBottomBarHazeEffect(
state = hazeState,
colors = menuColors,
shape = brightnessShape,
menuConfig = state.menuConfig,
progressive = false,
)
} else {
Modifier
},
shape = brightnessShape,
color = if (useBrightnessHaze) {
Color.Transparent
} else {
menuColors.background.copy(
alpha = state.menuConfig.readMenuBlurAlpha.coerceIn(0, 100) / 100f
)
},
contentColor = LegadoTheme.colorScheme.onSurface,
) {
BrightnessBar(
brightness = state.menuConfig.readBrightness,
onBrightnessChange = { value ->
onIntent(ReadBookIntent.SetBrightness(value))
},
brightnessAuto = state.menuConfig.brightnessAuto,
onToggleAuto = {
onIntent(ReadBookIntent.ToggleBrightnessAuto(!state.menuConfig.brightnessAuto))
},
onTogglePosition = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.BrightnessVwPos(if (brightnessIsLeft) "1" else "0")))
},
vertical = true,
colors = menuColors,
menuConfig = state.menuConfig,
backdrop = backdrop,
buttonGlassEnabled = readMenuBottomBarButtonLiquidGlassEnabled(
backdrop = backdrop,
menuConfig = state.menuConfig,
),
glassThumbEnabled = false,
)
}
}
}
// Bottom menu + floating icon row (bottom positions) // Bottom menu + floating icon row (bottom positions)
AnimatedVisibility( AnimatedVisibility(
visible = state.menuVisible, visible = state.menuVisible || searchMenuVisible,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(), exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
modifier = Modifier.align(Alignment.BottomCenter), modifier = Modifier.align(Alignment.BottomCenter),
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (state.menuConfig.showTitleBarIcons && state.menuConfig.titleBarIconPosition >= 2) { if (state.menuVisible &&
state.menuConfig.showTitleBarIcons &&
state.menuConfig.titleBarIconPosition >= 2
) {
FloatingIconRow( FloatingIconRow(
state = state, state = state,
colors = menuColors, colors = menuColors,
@@ -240,29 +355,42 @@ fun ReadBookMenuBar(
) )
} }
ReadBookMenuSurface( ReadBookMenuSurface(
route = currentRoute, contentTarget = contentTarget,
state = state, state = state,
colors = menuColors, colors = menuColors,
onIntent = onIntent, onIntent = onIntent,
context = context, context = context,
backdrop = backdrop, backdrop = backdrop,
hazeState = hazeState, hazeState = hazeState,
readStylePage = readStylePage,
onReadStylePageChanged = { readStylePage = it },
) )
} }
} }
} }
} }
private sealed interface ReadBookMenuContent {
data object Search : ReadBookMenuContent
data class Route(val route: ReadBookMenuRoute) : ReadBookMenuContent
}
@Composable @Composable
private fun ReadBookMenuSurface( private fun ReadBookMenuSurface(
route: ReadBookMenuRoute, contentTarget: ReadBookMenuContent,
state: ReadBookUiState, state: ReadBookUiState,
colors: ReadMenuColors, colors: ReadMenuColors,
onIntent: (ReadBookIntent) -> Unit, onIntent: (ReadBookIntent) -> Unit,
context: Context, context: Context,
backdrop: Backdrop?, backdrop: Backdrop?,
hazeState: HazeState?, hazeState: HazeState?,
readStylePage: Int,
onReadStylePageChanged: (Int) -> Unit,
) { ) {
val route = when (contentTarget) {
ReadBookMenuContent.Search -> ReadBookMenuRoute.Main
is ReadBookMenuContent.Route -> contentTarget.route
}
val expanded = route != ReadBookMenuRoute.Main val expanded = route != ReadBookMenuRoute.Main
val dialogLikeRoute = route == ReadBookMenuRoute.PaddingConfig val dialogLikeRoute = route == ReadBookMenuRoute.PaddingConfig
val density = LocalDensity.current val density = LocalDensity.current
@@ -272,6 +400,18 @@ private fun ReadBookMenuSurface(
targetValue = if (dialogLikeRoute) 1f else 0f, targetValue = if (dialogLikeRoute) 1f else 0f,
label = "ReadBookMenuMorph", label = "ReadBookMenuMorph",
) )
val headerFooterLift by animateDpAsState(
targetValue = if (route == ReadBookMenuRoute.ReadStyle && readStylePage == 2) {
72.dp
} else {
0.dp
},
animationSpec = tween(
durationMillis = 280,
easing = LinearOutSlowInEasing,
),
label = "ReadBookMenuHeaderFooterLift",
)
val maxHeight = with(density) { val maxHeight = with(density) {
windowSize.height.toDp() * 0.64f windowSize.height.toDp() * 0.64f
} }
@@ -377,7 +517,8 @@ private fun ReadBookMenuSurface(
.heightIn(max = maxHeight) .heightIn(max = maxHeight)
.onSizeChanged { surfaceHeightPx = it.height } .onSizeChanged { surfaceHeightPx = it.height }
.offset { .offset {
val liftPx = ((windowSize.height - surfaceHeightPx) / 2f) * morphProgress val dialogLiftPx = ((windowSize.height - surfaceHeightPx) / 2f) * morphProgress
val liftPx = dialogLiftPx + headerFooterLift.toPx()
IntOffset(x = 0, y = -liftPx.roundToInt()) IntOffset(x = 0, y = -liftPx.roundToInt())
} }
.then( .then(
@@ -433,16 +574,27 @@ private fun ReadBookMenuSurface(
contentColor = colors.content contentColor = colors.content
) { ) {
AnimatedContent( AnimatedContent(
targetState = route, targetState = contentTarget,
transitionSpec = { transitionSpec = {
(slideInVertically { it / 4 } + fadeIn()) (slideInVertically { it / 4 } + fadeIn())
.togetherWith(slideOutVertically { -it / 4 } + fadeOut()) .togetherWith(slideOutVertically { -it / 4 } + fadeOut())
.using(SizeTransform(clip = true)) .using(SizeTransform(clip = true))
}, },
label = "ReadBookMenuRoute", label = "ReadBookMenuRoute",
) { targetRoute -> ) { target ->
when (targetRoute) { when (target) {
ReadBookMenuRoute.Main -> { ReadBookMenuContent.Search -> {
SearchBottomMenuContent(
state = state,
colors = colors,
onIntent = onIntent,
bottomPadding = if (extendSurfaceToNavigationBar) navBarHeight + 16.dp else 16.dp,
surfaceEffectEnabled = useLiquidGlass || useHaze,
)
}
is ReadBookMenuContent.Route -> when (val targetRoute = target.route) {
ReadBookMenuRoute.Main -> {
MenuBottomBar( MenuBottomBar(
state = state, state = state,
colors = colors, colors = colors,
@@ -482,6 +634,7 @@ private fun ReadBookMenuSurface(
onToggleDayNight = { onToggleDayNight = {
onIntent(ReadBookIntent.ToggleDayNight) onIntent(ReadBookIntent.ToggleDayNight)
}, },
onPageChanged = onReadStylePageChanged,
readMenuCustomIcons = state.menuConfig.readMenuCustomIcons, readMenuCustomIcons = state.menuConfig.readMenuCustomIcons,
bottomBarButtons = state.menuConfig.bottomBarButtons, bottomBarButtons = state.menuConfig.bottomBarButtons,
onIntent = onIntent, onIntent = onIntent,
@@ -597,6 +750,7 @@ private fun ReadBookMenuSurface(
} }
} }
} }
}
} }
} }
} }
@@ -663,7 +817,7 @@ private fun MenuTitleBar(
backdrop: Backdrop?, backdrop: Backdrop?,
hazeState: HazeState?, hazeState: HazeState?,
) { ) {
val titleBarMode = AppConfig.titleBarMode val titleBarMode = ReadConfig.titleBarMode
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@@ -1404,6 +1558,159 @@ private fun OverflowDropdownMenu(
} }
} }
@Composable
private fun SearchBottomMenuContent(
state: ReadBookUiState,
colors: ReadMenuColors,
onIntent: (ReadBookIntent) -> Unit,
bottomPadding: Dp = 0.dp,
surfaceEffectEnabled: Boolean = false,
) {
val totalResults = state.searchResultList.size
val currentIndex = state.searchResultIndex.coerceIn(0, (totalResults - 1).coerceAtLeast(0))
val percent = if (totalResults > 0) {
((currentIndex + 1) * 100 / totalResults)
} else {
0
}
Column(
modifier = Modifier
.fillMaxWidth()
.background(
if (surfaceEffectEnabled) Color.Transparent else colors.background.copy(
alpha = state.menuConfig.readMenuBlurAlpha.coerceIn(0, 100) / 100f
)
)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)
)
.padding(top = 12.dp, bottom = bottomPadding)
.animateContentSize(),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
SearchInfoPill(
modifier = Modifier.weight(0.7f),
) {
Text(
text = if (totalResults > 0) "${currentIndex + 1} / $totalResults" else "0 / 0",
style = LegadoTheme.typography.titleSmall,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
)
Spacer(Modifier.width(8.dp))
Text(
text = "$percent%",
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
)
}
SearchInfoPill(
modifier = Modifier.weight(1.3f),
) {
Text(
text = state.chapterName.ifBlank { "-" },
style = LegadoTheme.typography.bodyMedium,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Spacer(Modifier.height(8.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
SearchMenuActionButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.Search,
text = "搜索内容",
onClick = { onIntent(ReadBookIntent.OpenSearch(null)) },
)
SearchMenuActionButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.Menu,
text = "主菜单",
onClick = {
onIntent(ReadBookIntent.HideSearchMenu)
onIntent(ReadBookIntent.ShowMenu)
},
)
SearchMenuActionButton(
modifier = Modifier.weight(0.55f),
icon = Icons.Default.Close,
text = "退出",
onClick = { onIntent(ReadBookIntent.ExitSearch) },
)
}
}
}
@Composable
private fun SearchInfoPill(
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
Row(
modifier = modifier
.height(40.dp)
.clip(RoundedCornerShape(16.dp))
.background(LegadoTheme.colorScheme.surfaceContainerLow)
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
content = content,
)
}
@Composable
private fun SearchMenuActionButton(
modifier: Modifier = Modifier,
icon: ImageVector,
text: String,
onClick: () -> Unit,
) {
Row(
modifier = modifier
.height(44.dp)
.clip(RoundedCornerShape(16.dp))
.background(LegadoTheme.colorScheme.surfaceContainerLow)
.clickable(role = Role.Button, onClick = onClick)
.padding(horizontal = 10.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = LegadoTheme.colorScheme.primary,
modifier = Modifier.size(18.dp),
)
Spacer(Modifier.width(6.dp))
Text(
text = text,
style = LegadoTheme.typography.labelMediumEmphasized,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
@Composable @Composable
private fun MenuBottomBar( private fun MenuBottomBar(
state: ReadBookUiState, state: ReadBookUiState,
@@ -1431,7 +1738,7 @@ private fun MenuBottomBar(
val target = value.roundToInt().coerceIn(0, seekMax) val target = value.roundToInt().coerceIn(0, seekMax)
sliderDragging = false sliderDragging = false
sliderValue = target.toFloat() sliderValue = target.toFloat()
val behavior = AppConfig.progressBarBehavior val behavior = ReadConfig.progressBarBehavior
if (behavior == "page") { if (behavior == "page") {
onIntent(ReadBookIntent.SkipToPage(target)) onIntent(ReadBookIntent.SkipToPage(target))
} else { } else {
@@ -1459,6 +1766,27 @@ private fun MenuBottomBar(
.padding(top = 8.dp, bottom = contentBottomPadding) .padding(top = 8.dp, bottom = contentBottomPadding)
.animateContentSize(), .animateContentSize(),
) { ) {
if (state.menuConfig.showBrightnessView == "1") {
BrightnessBar(
brightness = state.menuConfig.readBrightness,
onBrightnessChange = { value ->
onIntent(ReadBookIntent.SetBrightness(value))
},
brightnessAuto = state.menuConfig.brightnessAuto,
onToggleAuto = {
onIntent(ReadBookIntent.ToggleBrightnessAuto(!state.menuConfig.brightnessAuto))
},
onTogglePosition = {},
vertical = false,
colors = colors,
menuConfig = state.menuConfig,
backdrop = backdrop,
buttonGlassEnabled = buttonGlassEnabled,
glassThumbEnabled = buttonGlassEnabled,
)
Spacer(Modifier.height(4.dp))
}
// Seek bar row: prev + slider + next // Seek bar row: prev + slider + next
AnimatedVisibility(visible = state.menuConfig.readSliderMode != "1") { AnimatedVisibility(visible = state.menuConfig.readSliderMode != "1") {
Row( Row(
@@ -2181,8 +2509,8 @@ private fun Modifier.readMenuLiquidGlass(
} }
}, },
) )
.then(if (interactiveHighlight != null) interactiveHighlight.modifier else Modifier) .then(interactiveHighlight?.modifier ?: Modifier)
.then(if (interactiveHighlight != null) interactiveHighlight.gestureModifier else Modifier) .then(interactiveHighlight?.gestureModifier ?: Modifier)
} }
@OptIn(ExperimentalHazeMaterialsApi::class) @OptIn(ExperimentalHazeMaterialsApi::class)
@@ -2288,7 +2616,7 @@ private fun readMenuBottomBarSurfaceBrush(
private fun readMenuColors(): ReadMenuColors { private fun readMenuColors(): ReadMenuColors {
val themeBackground = LegadoTheme.colorScheme.surfaceContainerHigh val themeBackground = LegadoTheme.colorScheme.surfaceContainerHigh
val themeContent = LegadoTheme.colorScheme.onSurface val themeContent = LegadoTheme.colorScheme.onSurface
return when (AppConfig.readBarStyle) { return when (ReadConfig.readBarStyle) {
1 -> ReadMenuColors( 1 -> ReadMenuColors(
background = themeBackground, background = themeBackground,
content = themeContent, content = themeContent,
@@ -2370,3 +2698,116 @@ private fun loadFloatingIcons(
} }
.toList() .toList()
} }
@Composable
private fun BrightnessBar(
brightness: Int,
onBrightnessChange: (Int) -> Unit,
brightnessAuto: Boolean,
onToggleAuto: () -> Unit,
onTogglePosition: () -> Unit,
modifier: Modifier = Modifier,
vertical: Boolean = false,
colors: ReadMenuColors,
menuConfig: ReadMenuConfig,
backdrop: Backdrop? = null,
buttonGlassEnabled: Boolean = false,
glassThumbEnabled: Boolean = false,
) {
var sliderValue by remember { mutableFloatStateOf(brightness.toFloat()) }
var sliderDragging by remember { mutableStateOf(false) }
LaunchedEffect(brightness) {
if (!sliderDragging) {
sliderValue = brightness.toFloat()
}
}
fun commitSliderValue(value: Float) {
val target = value.roundToInt().coerceIn(0, 100)
sliderDragging = false
sliderValue = target.toFloat()
onBrightnessChange(target)
}
if (vertical) {
Column(
modifier = modifier
.width(if (buttonGlassEnabled) 64.dp else 56.dp)
.padding(vertical = 12.dp, horizontal = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
ReadMenuGlassIconButton(
onClick = onToggleAuto,
icon = Icons.Filled.BrightnessAuto,
colors = colors,
backdrop = backdrop,
menuConfig = menuConfig,
glassEnabled = buttonGlassEnabled,
selected = brightnessAuto,
contentDescription = stringResource(R.string.brightness_follow_system),
)
AppVerticalSlider(
value = sliderValue.coerceIn(0f, 100f),
onValueChange = { value ->
sliderDragging = true
sliderValue = value.coerceIn(0f, 100f)
},
onValueChangeFinished = {
commitSliderValue(sliderValue)
},
valueRange = 0f..100f,
enabled = !brightnessAuto,
height = 168.dp,
)
ReadMenuGlassIconButton(
onClick = onTogglePosition,
icon = Icons.Filled.SwapHoriz,
colors = colors,
backdrop = backdrop,
menuConfig = menuConfig,
glassEnabled = buttonGlassEnabled,
contentDescription = stringResource(R.string.brightness_bar_position),
)
}
} else {
val buttonSize = if (buttonGlassEnabled) 48.dp else 40.dp
Row(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
ReadMenuGlassIconButton(
onClick = onToggleAuto,
icon = Icons.Filled.BrightnessAuto,
colors = colors,
backdrop = backdrop,
menuConfig = menuConfig,
glassEnabled = buttonGlassEnabled,
selected = brightnessAuto,
contentDescription = stringResource(R.string.brightness_follow_system),
)
ReadMenuSlider(
value = sliderValue.coerceIn(0f, 100f),
onValueChange = { value ->
sliderDragging = true
sliderValue = value.coerceIn(0f, 100f)
},
onValueChangeFinished = {
commitSliderValue(sliderValue)
},
valueRange = 0f..100f,
enabled = !brightnessAuto,
backdrop = backdrop,
glassThumbEnabled = glassThumbEnabled,
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
)
Spacer(Modifier.width(buttonSize))
}
}
}
@@ -27,14 +27,12 @@ import dev.chrisbanes.haze.hazeSource
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.ReadMenuBlurMode import io.legado.app.constant.ReadMenuBlurMode
import io.legado.app.help.IntentData
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.page.ContentTextView 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.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.book.searchContent.SearchContentActivity import io.legado.app.ui.book.searchContent.SearchContentResult
import io.legado.app.ui.book.searchContent.SearchResult
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
@@ -45,6 +43,7 @@ import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.utils.StartActivityContract import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.takePersistablePermissionSafely import io.legado.app.utils.takePersistablePermissionSafely
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -95,10 +94,12 @@ fun ReadBookRouteScreen(
host: ReadBookRouteHost, host: ReadBookRouteHost,
controller: ReadBookController, controller: ReadBookController,
onEffectsReady: () -> Unit = {}, onEffectsReady: () -> Unit = {},
onOpenSearch: (word: String?, bookUrl: String) -> Unit = { _, _ -> },
) { ) {
val state by viewModel.uiState.collectAsStateWithLifecycle() val state by viewModel.uiState.collectAsStateWithLifecycle()
val readPreferences by viewModel.readPreferences.collectAsStateWithLifecycle() val readPreferences by viewModel.readPreferences.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
val effectsReady = remember(viewModel) { CompletableDeferred<Unit>() }
val menuBackdrop = rememberLayerBackdrop() val menuBackdrop = rememberLayerBackdrop()
val menuHazeState = remember { HazeState() } val menuHazeState = remember { HazeState() }
val useMenuHazeSource = state.menuConfig.readMenuTopBarBlurMode == ReadMenuBlurMode.Haze || val useMenuHazeSource = state.menuConfig.readMenuTopBarBlurMode == ReadMenuBlurMode.Haze ||
@@ -209,19 +210,6 @@ fun ReadBookRouteScreen(
} }
} }
val searchContentLauncher = rememberLauncherForActivityResult(
StartActivityContract(SearchContentActivity::class.java)
) { result ->
val data = result.data ?: return@rememberLauncherForActivityResult
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.onIntent(ReadBookIntent.SetSearchResults(searchResultList, index, searchResult.query))
}
}
val importHttpTtsPicker = rememberLauncherForActivityResult( val importHttpTtsPicker = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument() ActivityResultContracts.OpenDocument()
) { uri -> ) { uri ->
@@ -245,7 +233,10 @@ fun ReadBookRouteScreen(
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
launch { launch {
viewModel.effects viewModel.effects
.onSubscription { onEffectsReady() } .onSubscription {
effectsReady.complete(Unit)
onEffectsReady()
}
.collect { effect -> .collect { effect ->
try { try {
when (effect) { when (effect) {
@@ -292,18 +283,7 @@ fun ReadBookRouteScreen(
) )
} }
is ReadBookEffect.OpenSearchActivity -> { is ReadBookEffect.OpenSearchActivity -> {
val currentState = viewModel.uiState.value onOpenSearch(effect.word, effect.bookUrl)
val lambda: (Intent.() -> Unit)? = { intent ->
intent.putExtra("bookUrl", effect.bookUrl)
intent.putExtra("searchWord", effect.word)
intent.putExtra("searchResultIndex", currentState.searchResultIndex)
currentState.searchResultList.firstOrNull()?.let {
if (it.query == currentState.searchContentQuery) {
IntentData.put("searchResultList", currentState.searchResultList)
}
}
}
searchContentLauncher.launch(lambda)
} }
is ReadBookEffect.MenuSettingReplace -> { is ReadBookEffect.MenuSettingReplace -> {
replaceLauncher.launch(Intent(context, ReplaceRuleActivity::class.java)) replaceLauncher.launch(Intent(context, ReplaceRuleActivity::class.java))
@@ -392,6 +372,24 @@ fun ReadBookRouteScreen(
LaunchedEffect(state.menuVisible) { LaunchedEffect(state.menuVisible) {
host.upSystemUiVisibility(host.isInMultiWindowModeCompat, !state.menuVisible) host.upSystemUiVisibility(host.isInMultiWindowModeCompat, !state.menuVisible)
} }
// ── Search result collection (from Navigation3 search route) ──────
LaunchedEffect(viewModel) {
SearchContentResult.results.collect { result ->
effectsReady.await()
viewModel.onIntent(
ReadBookIntent.SetSearchResults(result.searchResults, result.index, result.query)
)
result.searchResults.getOrNull(result.index)?.let { searchResult ->
viewModel.onIntent(
ReadBookIntent.NavigateToSearchResult(searchResult, result.index)
)
}
SearchContentResult.resetReplayCache()
}
}
// ── View layer + Compose UI ─────────────────────────────────────── // ── View layer + Compose UI ───────────────────────────────────────
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
@@ -23,7 +23,6 @@ import io.legado.app.ui.book.read.sheet.ContentEditSheet
import io.legado.app.ui.book.read.sheet.DictSheet import io.legado.app.ui.book.read.sheet.DictSheet
import io.legado.app.ui.book.read.sheet.DownloadSheet import io.legado.app.ui.book.read.sheet.DownloadSheet
import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet
import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet
import io.legado.app.ui.book.read.sheet.MoreConfigSheet import io.legado.app.ui.book.read.sheet.MoreConfigSheet
@@ -38,6 +37,7 @@ import io.legado.app.ui.book.read.sheet.SpeakEngineConfigSheet
import io.legado.app.ui.book.read.sheet.TitleBarIconSheet import io.legado.app.ui.book.read.sheet.TitleBarIconSheet
import io.legado.app.ui.book.read.sheet.ToolButtonConfigSheet import io.legado.app.ui.book.read.sheet.ToolButtonConfigSheet
import io.legado.app.ui.book.read.sheet.UnderlineConfigSheet import io.legado.app.ui.book.read.sheet.UnderlineConfigSheet
import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
import io.legado.app.ui.widget.components.log.AppLogSheet import io.legado.app.ui.widget.components.log.AppLogSheet
@@ -135,6 +135,10 @@ fun ReadBookScreen(
onIntent(ReadBookIntent.OpenReplaceEditor(id, pattern)) onIntent(ReadBookIntent.OpenReplaceEditor(id, pattern))
}, },
onReplaceRuleChanged = { onIntent(ReadBookIntent.ReplaceRuleChanged) }, onReplaceRuleChanged = { onIntent(ReadBookIntent.ReplaceRuleChanged) },
onNavigateToTextEffects = {
onIntent(ReadBookIntent.DismissSheet)
onIntent(ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.TextTitle))
},
) )
UnderlineConfigSheet( UnderlineConfigSheet(
show = state.activeSheet is ReadBookSheet.UnderlineConfig, show = state.activeSheet is ReadBookSheet.UnderlineConfig,
@@ -3,44 +3,23 @@ package io.legado.app.ui.book.read
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppFloatingActionButton
/** /**
* Compose replacement for SearchMenu — search result navigation overlay. * Search result navigation overlay. The bottom search menu is hosted by [ReadBookMenuBar].
*/ */
@Composable @Composable
fun ReadBookSearchBar( fun ReadBookSearchBar(
@@ -52,9 +31,6 @@ fun ReadBookSearchBar(
val hasResults = state.searchResultList.isNotEmpty() val hasResults = state.searchResultList.isNotEmpty()
val currentIndex = state.searchResultIndex val currentIndex = state.searchResultIndex
val totalResults = state.searchResultList.size val totalResults = state.searchResultList.size
val currentResult = if (hasResults && currentIndex in state.searchResultList.indices) {
state.searchResultList[currentIndex]
} else null
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
// Left FAB - previous result // Left FAB - previous result
@@ -66,7 +42,7 @@ fun ReadBookSearchBar(
.align(Alignment.CenterStart) .align(Alignment.CenterStart)
.padding(start = 16.dp), .padding(start = 16.dp),
) { ) {
SmallFloatingActionButton( AppFloatingActionButton(
onClick = { onClick = {
val prevIndex = currentIndex - 1 val prevIndex = currentIndex - 1
onIntent( onIntent(
@@ -75,11 +51,13 @@ fun ReadBookSearchBar(
) )
) )
}, },
containerColor = MaterialTheme.colorScheme.surfaceContainerLow, tooltipText = "上一个结果",
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant,
) { ) {
Icon( Icon(
Icons.AutoMirrored.Filled.ArrowBack, Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Previous", contentDescription = "上一个结果",
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
) )
} }
@@ -94,7 +72,7 @@ fun ReadBookSearchBar(
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
.padding(end = 16.dp), .padding(end = 16.dp),
) { ) {
SmallFloatingActionButton( AppFloatingActionButton(
onClick = { onClick = {
val nextIndex = currentIndex + 1 val nextIndex = currentIndex + 1
onIntent( onIntent(
@@ -103,144 +81,16 @@ fun ReadBookSearchBar(
) )
) )
}, },
containerColor = MaterialTheme.colorScheme.surfaceContainerLow, tooltipText = "下一个结果",
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
contentColor = LegadoTheme.colorScheme.onSurfaceVariant,
) { ) {
Icon( Icon(
Icons.AutoMirrored.Filled.ArrowForward, Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = "Next", contentDescription = "下一个结果",
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
) )
} }
} }
// Tap background to dismiss search menu
AnimatedVisibility(
visible = searchVisible && state.searchMenuVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
Box(
Modifier
.fillMaxSize()
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
) { onIntent(ReadBookIntent.HideSearchMenu) }
)
}
// Bottom menu
AnimatedVisibility(
visible = searchVisible && state.searchMenuVisible,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
modifier = Modifier.align(Alignment.BottomCenter),
) {
SearchBottomMenu(
state = state,
currentResult = currentResult,
onIntent = onIntent,
)
}
}
}
@Composable
private fun SearchBottomMenu(
state: ReadBookUiState,
currentResult: io.legado.app.ui.book.searchContent.SearchResult?,
onIntent: (ReadBookIntent) -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceContainer)
.padding(bottom = 16.dp),
) {
// Search progress info
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
// Fraction: "3 / 10"
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "${state.searchResultIndex + 1} / ${state.searchResultList.size}",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.width(8.dp))
val percent = if (state.searchResultList.isNotEmpty()) {
((state.searchResultIndex + 1) * 100 / state.searchResultList.size)
} else 0
Text(
text = "$percent%",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
// Current chapter
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = state.chapterName,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
)
}
}
Spacer(Modifier.height(4.dp))
// Action buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
TextButton(
onClick = { onIntent(ReadBookIntent.OpenSearch(null)) },
modifier = Modifier.weight(1f),
) {
Icon(
Icons.Default.Search,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.width(4.dp))
Text("搜索内容")
}
TextButton(
onClick = { onIntent(ReadBookIntent.ShowMenu) },
modifier = Modifier.weight(1f),
) {
Icon(Icons.Default.Menu, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(4.dp))
Text("主菜单")
}
IconButton(
onClick = { onIntent(ReadBookIntent.ExitSearch) },
) {
Icon(Icons.Default.Close, contentDescription = "Exit search")
}
}
} }
} }
@@ -35,7 +35,6 @@ import io.legado.app.domain.usecase.GetReadingProgressUseCase
import io.legado.app.domain.usecase.UploadReadingProgressUseCase import io.legado.app.domain.usecase.UploadReadingProgressUseCase
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isEpub import io.legado.app.help.book.isEpub
@@ -45,7 +44,6 @@ import io.legado.app.help.book.isLocalTxt
import io.legado.app.help.book.isMobi import io.legado.app.help.book.isMobi
import io.legado.app.help.book.removeType import io.legado.app.help.book.removeType
import io.legado.app.help.book.simulatedTotalChapterNum import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.source.getSourceType import io.legado.app.help.source.getSourceType
@@ -66,6 +64,7 @@ import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextChapterLayout import io.legado.app.ui.book.read.page.provider.TextChapterLayout
import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.searchContent.SearchResult
import io.legado.app.ui.config.otherConfig.OtherConfig import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.ImageSaveUtils import io.legado.app.utils.ImageSaveUtils
@@ -78,7 +77,6 @@ import io.legado.app.utils.isTrue
import io.legado.app.utils.mapParallelSafe import io.legado.app.utils.mapParallelSafe
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefInt
import io.legado.app.utils.toStringArray import io.legado.app.utils.toStringArray
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -155,7 +153,7 @@ class ReadBookViewModel(
} }
init { init {
AppConfig.detectClickArea() ReadConfig.detectClickArea()
ReadBook.register(this) ReadBook.register(this)
refreshButtonConfigs() refreshButtonConfigs()
collectReadPreferences() collectReadPreferences()
@@ -253,7 +251,7 @@ class ReadBookViewModel(
is ReadBookIntent.NavigateToSearchResult -> { is ReadBookIntent.NavigateToSearchResult -> {
_uiState.update { it.copy(searchResultIndex = intent.index) } _uiState.update { it.copy(searchResultIndex = intent.index) }
_effects.tryEmit(ReadBookEffect.NavigateToSearchResult(intent.result)) navigateToSearchResult(intent.result)
} }
is ReadBookIntent.ToggleReadAloud -> { is ReadBookIntent.ToggleReadAloud -> {
@@ -296,8 +294,28 @@ class ReadBookViewModel(
is ReadBookIntent.CancelSelect -> _effects.tryEmit(ReadBookEffect.CancelSelect) is ReadBookIntent.CancelSelect -> _effects.tryEmit(ReadBookEffect.CancelSelect)
is ReadBookIntent.UpSystemUiVisibility -> _effects.tryEmit(ReadBookEffect.UpSystemUiVisibility) is ReadBookIntent.UpSystemUiVisibility -> _effects.tryEmit(ReadBookEffect.UpSystemUiVisibility)
is ReadBookIntent.UpContent -> ReadBook.loadOrUpContent() is ReadBookIntent.UpContent -> ReadBook.loadOrUpContent()
is ReadBookIntent.SetBrightness -> _effects.tryEmit(ReadBookEffect.SetBrightness(intent.value)) is ReadBookIntent.SetBrightness -> {
is ReadBookIntent.ToggleBrightnessAuto -> _effects.tryEmit(ReadBookEffect.ToggleBrightnessAuto) ReadBookConfig.readBrightness = intent.value
_uiState.update { it.copy(menuConfig = it.menuConfig.copy(readBrightness = intent.value)) }
viewModelScope.launch {
readSettingsRepository.setReadBrightness(intent.value)
}
_effects.tryEmit(ReadBookEffect.SetBrightness(intent.value))
}
is ReadBookIntent.ToggleBrightnessAuto -> {
ReadBookConfig.brightnessAuto = intent.auto
_uiState.update { it.copy(menuConfig = it.menuConfig.copy(brightnessAuto = intent.auto)) }
viewModelScope.launch {
readSettingsRepository.setBrightnessAuto(intent.auto)
}
_effects.tryEmit(
ReadBookEffect.ToggleBrightnessAuto(
intent.auto,
_uiState.value.menuConfig.readBrightness
)
)
}
is ReadBookIntent.SeekToChapter -> { is ReadBookIntent.SeekToChapter -> {
ReadBook.saveCurrentBookProgress() ReadBook.saveCurrentBookProgress()
openChapter(intent.index) openChapter(intent.index)
@@ -627,7 +645,7 @@ class ReadBookViewModel(
is ReadBookIntent.OpenPreDownloadNumPicker -> { is ReadBookIntent.OpenPreDownloadNumPicker -> {
_uiState.update { _uiState.update {
it.copy( it.copy(
preDownloadNum = AppConfig.preDownloadNum, preDownloadNum = ReadConfig.preDownloadNum,
activeSheet = ReadBookSheet.PreDownloadConfig, activeSheet = ReadBookSheet.PreDownloadConfig,
) )
} }
@@ -636,7 +654,7 @@ class ReadBookViewModel(
is ReadBookIntent.OpenCacheCleanTimePicker -> { is ReadBookIntent.OpenCacheCleanTimePicker -> {
_uiState.update { _uiState.update {
it.copy( it.copy(
audioCacheCleanTime = AppConfig.audioCacheCleanTimeOrgin, audioCacheCleanTime = ReadConfig.audioCacheCleanTimeOrgin,
activeSheet = ReadBookSheet.AudioCacheCleanConfig, activeSheet = ReadBookSheet.AudioCacheCleanConfig,
) )
} }
@@ -644,7 +662,8 @@ class ReadBookViewModel(
is ReadBookIntent.ApplySpeakEngine -> { is ReadBookIntent.ApplySpeakEngine -> {
ReadBook.book?.setTtsEngine(null) ReadBook.book?.setTtsEngine(null)
AppConfig.ttsEngine = intent.value ReadConfig.ttsEngine = intent.value
ReadAloud.upReadAloudClass()
_uiState.update { _uiState.update {
it.copy( it.copy(
selectedTtsEngine = ReadAloud.ttsEngine, selectedTtsEngine = ReadAloud.ttsEngine,
@@ -655,7 +674,7 @@ class ReadBookViewModel(
} }
is ReadBookIntent.ApplyPreDownloadNum -> { is ReadBookIntent.ApplyPreDownloadNum -> {
AppConfig.preDownloadNum = intent.value ReadConfig.preDownloadNum = intent.value
_uiState.update { _uiState.update {
it.copy( it.copy(
preDownloadNum = intent.value, preDownloadNum = intent.value,
@@ -665,7 +684,7 @@ class ReadBookViewModel(
} }
is ReadBookIntent.ApplyAudioCacheCleanTime -> { is ReadBookIntent.ApplyAudioCacheCleanTime -> {
context.putPrefInt(PreferKey.audioCacheCleanTime, intent.value) ReadConfig.audioCacheCleanTimeOrgin = intent.value
_uiState.update { _uiState.update {
it.copy( it.copy(
audioCacheCleanTime = intent.value, audioCacheCleanTime = intent.value,
@@ -704,7 +723,7 @@ class ReadBookViewModel(
}.onSuccess { }.onSuccess {
loadTtsEngineItems() loadTtsEngineItems()
if (ReadAloud.ttsEngine == intent.engineId.toString()) { if (ReadAloud.ttsEngine == intent.engineId.toString()) {
AppConfig.ttsEngine = null ReadConfig.ttsEngine = null
ReadAloud.upReadAloudClass() ReadAloud.upReadAloudClass()
} }
} }
@@ -726,6 +745,7 @@ class ReadBookViewModel(
is ReadBookIntent.ApplySpeakEnginePerBook -> { is ReadBookIntent.ApplySpeakEnginePerBook -> {
ReadBook.book?.setTtsEngine(intent.value) ReadBook.book?.setTtsEngine(intent.value)
ReadAloud.upReadAloudClass()
_uiState.update { _uiState.update {
it.copy( it.copy(
selectedTtsEngine = ReadAloud.ttsEngine, selectedTtsEngine = ReadAloud.ttsEngine,
@@ -841,7 +861,7 @@ class ReadBookViewModel(
is ReadBookIntent.SelectFont -> selectFont(intent.path) is ReadBookIntent.SelectFont -> selectFont(intent.path)
is ReadBookIntent.SelectSystemTypeface -> { is ReadBookIntent.SelectSystemTypeface -> {
AppConfig.systemTypefaces = intent.index ReadConfig.systemTypefaces = intent.index
ReadBookConfig.textFont = "" ReadBookConfig.textFont = ""
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig( _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.ReloadContent) setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.ReloadContent)
@@ -937,7 +957,7 @@ class ReadBookViewModel(
is ReadBookIntent.ToggleDayNight -> toggleDayNight() is ReadBookIntent.ToggleDayNight -> toggleDayNight()
// Text action menu // Text action menu
is ReadBookIntent.TextActionAloud -> { is ReadBookIntent.TextActionAloud -> {
when (AppConfig.contentSelectSpeakMod) { when (ReadConfig.contentSelectSpeakMod) {
1 -> _effects.tryEmit(ReadBookEffect.TextActionAloudSelect) 1 -> _effects.tryEmit(ReadBookEffect.TextActionAloudSelect)
else -> _effects.tryEmit(ReadBookEffect.TextActionSpeak(intent.text)) else -> _effects.tryEmit(ReadBookEffect.TextActionSpeak(intent.text))
} }
@@ -1052,7 +1072,7 @@ class ReadBookViewModel(
_effects.tryEmit(ReadBookEffect.UnregisterNetworkListener) _effects.tryEmit(ReadBookEffect.UnregisterNetworkListener)
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
if (AppConfig.syncBookProgressPlus) { if (ReadConfig.syncBookProgressPlus) {
ReadBook.syncProgress() ReadBook.syncProgress()
} else { } else {
ReadBook.uploadProgress() ReadBook.uploadProgress()
@@ -1123,7 +1143,7 @@ class ReadBookViewModel(
* Called from the network changed listener (registered by route). * Called from the network changed listener (registered by route).
*/ */
fun onNetworkChanged() { fun onNetworkChanged() {
if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) { if (ReadConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
ReadBook.syncProgress(newProgressAction = { progress -> ReadBook.syncProgress(newProgressAction = { progress ->
sureNewProgress(progress) sureNewProgress(progress)
}) })
@@ -1149,7 +1169,7 @@ class ReadBookViewModel(
private fun handleChangeSource() { private fun handleChangeSource() {
viewModelScope.launch { viewModelScope.launch {
if (AppConfig.defaultSourceChangeAll) { if (ReadConfig.defaultSourceChangeAll) {
_uiState.update { it.copy(activeSheet = ReadBookSheet.ChangeBookSource) } _uiState.update { it.copy(activeSheet = ReadBookSheet.ChangeBookSource) }
} else { } else {
val book = ReadBook.book ?: return@launch val book = ReadBook.book ?: return@launch
@@ -1393,10 +1413,10 @@ class ReadBookViewModel(
readSettingsRepository.preferences.collect { preferences -> readSettingsRepository.preferences.collect { preferences ->
val old = previous val old = previous
previous = preferences previous = preferences
AppConfig.syncReadPreferences(preferences) ReadConfig.syncReadPreferences(preferences)
_readPreferences.value = preferences _readPreferences.value = preferences
if (!preferences.hasMenuClickArea()) { if (!preferences.hasMenuClickArea()) {
AppConfig.detectClickArea() ReadConfig.detectClickArea()
readSettingsRepository.setClickAction(PreferKey.clickActionMC, 0) readSettingsRepository.setClickAction(PreferKey.clickActionMC, 0)
} }
if (old != null && old.keepLight != preferences.keepLight) { if (old != null && old.keepLight != preferences.keepLight) {
@@ -1423,11 +1443,7 @@ class ReadBookViewModel(
readAloudStreamAudio = prefs.streamReadAloudAudio, readAloudStreamAudio = prefs.streamReadAloudAudio,
readAloudTtsFollowSys = prefs.ttsFollowSys, readAloudTtsFollowSys = prefs.ttsFollowSys,
readAloudTtsSpeechRate = prefs.ttsSpeechRate, readAloudTtsSpeechRate = prefs.ttsSpeechRate,
readAloudTtsTimer = if (BaseReadAloudService.timeMinute > 0) { readAloudTtsTimer = prefs.ttsTimer,
BaseReadAloudService.timeMinute
} else {
prefs.ttsTimer
},
) )
} }
} }
@@ -1445,11 +1461,12 @@ class ReadBookViewModel(
} }
private fun setReadAloudTtsTimer(value: Int) { private fun setReadAloudTtsTimer(value: Int) {
val timer = value.coerceIn(0, 180)
viewModelScope.launch { viewModelScope.launch {
readAloudSettingsRepository.setTtsTimer(value) readAloudSettingsRepository.setTtsTimer(timer)
} }
ReadAloud.setTimer(context, value) ReadAloud.setTimer(context, timer)
_uiState.update { it.copy(readAloudTtsTimer = value) } _uiState.update { it.copy(readAloudTtsTimer = timer) }
} }
private fun setReadAloudTtsSpeechRate(value: Int) { private fun setReadAloudTtsSpeechRate(value: Int) {
@@ -1557,6 +1574,10 @@ class ReadBookViewModel(
readMenuCustomIcons = ReadBookConfig.readMenuCustomIcons.toImmutableMap(), readMenuCustomIcons = ReadBookConfig.readMenuCustomIcons.toImmutableMap(),
titleBarButtons = current.menuConfig.titleBarButtons, titleBarButtons = current.menuConfig.titleBarButtons,
bottomBarButtons = current.menuConfig.bottomBarButtons, bottomBarButtons = current.menuConfig.bottomBarButtons,
showBrightnessView = ReadBookConfig.showBrightnessView,
brightnessVwPos = ReadBookConfig.brightnessVwPos,
readBrightness = ReadBookConfig.readBrightness,
brightnessAuto = ReadBookConfig.brightnessAuto,
), ),
) )
} }
@@ -1659,14 +1680,14 @@ class ReadBookViewModel(
} }
private fun calculateSeekProgress(): Int { private fun calculateSeekProgress(): Int {
return when (AppConfig.progressBarBehavior) { return when (ReadConfig.progressBarBehavior) {
"page" -> ReadBook.durPageIndex "page" -> ReadBook.durPageIndex
else -> ReadBook.durChapterIndex else -> ReadBook.durChapterIndex
} }
} }
private fun calculateSeekMax(): Int { private fun calculateSeekMax(): Int {
return when (AppConfig.progressBarBehavior) { return when (ReadConfig.progressBarBehavior) {
"page" -> (ReadBook.curTextChapter?.pages?.size ?: 1) - 1 "page" -> (ReadBook.curTextChapter?.pages?.size ?: 1) - 1
else -> ReadBook.chapterSize - 1 else -> ReadBook.chapterSize - 1
} }
@@ -1761,7 +1782,7 @@ class ReadBookViewModel(
if (ReadBook.chapterChanged) { if (ReadBook.chapterChanged) {
ReadBook.chapterChanged = false ReadBook.chapterChanged = false
} else if (!(isSameBook && BaseReadAloudService.isRun) && ReadBook.inBookshelf) { } else if (!(isSameBook && BaseReadAloudService.isRun) && ReadBook.inBookshelf) {
if (AppConfig.syncBookProgressPlus) { if (ReadConfig.syncBookProgressPlus) {
ReadBook.syncProgress({ progress -> sureNewProgress(progress) }) ReadBook.syncProgress({ progress -> sureNewProgress(progress) })
} else { } else {
syncBookProgress(book) syncBookProgress(book)
@@ -1857,7 +1878,7 @@ class ReadBookViewModel(
book: Book, book: Book,
alertSync: ((progress: BookProgress) -> Unit)? = null alertSync: ((progress: BookProgress) -> Unit)? = null
) { ) {
if (!AppConfig.syncBookProgress) return if (!ReadConfig.syncBookProgress) return
execute { execute {
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress() getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
}.onError { }.onError {
@@ -1952,7 +1973,7 @@ class ReadBookViewModel(
} }
private fun autoChangeSource(name: String, author: String) { private fun autoChangeSource(name: String, author: String) {
if (!AppConfig.autoChangeSource) return if (!ReadConfig.autoChangeSource) return
execute { execute {
val sources = appDb.bookSourceDao.allTextEnabledPart val sources = appDb.bookSourceDao.allTextEnabledPart
flow { flow {
@@ -2164,11 +2185,11 @@ class ReadBookViewModel(
fun searchResultPositions( fun searchResultPositions(
textChapter: TextChapter, textChapter: TextChapter,
searchResult: SearchResult searchResult: SearchResult,
query: String = _uiState.value.searchContentQuery,
): Array<Int> { ): Array<Int> {
val pages = textChapter.pages val pages = textChapter.pages
val content = textChapter.getContent() val content = textChapter.getContent()
val query = _uiState.value.searchContentQuery
val queryLength = query.length val queryLength = query.length
var count = 0 var count = 0
@@ -2178,6 +2199,7 @@ class ReadBookViewModel(
count += 1 count += 1
} }
val contentPosition = index val contentPosition = index
if (contentPosition < 0) return arrayOf(0, 0, 0, 0, 0)
var pageIndex = 0 var pageIndex = 0
var length = pages[pageIndex].text.length var length = pages[pageIndex].text.length
while (length < contentPosition && pageIndex + 1 < pages.size) { while (length < contentPosition && pageIndex + 1 < pages.size) {
@@ -2191,7 +2213,7 @@ class ReadBookViewModel(
var curLine = curTextLines[lineIndex] var curLine = curTextLines[lineIndex]
length = length - currentPage.text.length + curLine.text.length length = length - currentPage.text.length + curLine.text.length
if (curLine.isParagraphEnd) length++ if (curLine.isParagraphEnd) length++
while (length <= contentPosition && lineIndex + 1 < curTextLines.size) { while (length < contentPosition && lineIndex + 1 < curTextLines.size) {
lineIndex += 1 lineIndex += 1
curLine = curTextLines[lineIndex] curLine = curTextLines[lineIndex]
length += curLine.text.length length += curLine.text.length
@@ -2217,6 +2239,55 @@ class ReadBookViewModel(
return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2) return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
} }
/**
* Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
* so the Controller can navigate and highlight.
*/
private fun navigateToSearchResult(result: SearchResult) {
val query = _uiState.value.searchContentQuery
if (query.isEmpty()) return
val chapterIndex = result.chapterIndex
val textChapter = ReadBook.curTextChapter
if (textChapter != null && textChapter.chapter.index == chapterIndex) {
val pos = searchResultPositions(textChapter, result, query)
val lineIndex = pos[1]
val charIndex = pos[2]
val addLine = pos[3]
val charIndex2 = pos[4]
val queryLength = query.length
val endLineIndex = lineIndex + addLine.coerceAtLeast(0)
val endCharIndex = if (addLine == 0) {
charIndex + queryLength
} else {
charIndex2 + 1
}
_effects.tryEmit(
ReadBookEffect.NavigateToSearchResult(
result = result,
chapterIndex = chapterIndex,
pageIndex = pos[0],
lineIndex = lineIndex,
startCharIndex = charIndex,
endLineIndex = endLineIndex,
endCharIndex = endCharIndex,
)
)
} else {
// Chapter not loaded — emit with -1 so the Controller knows to open the chapter first
_effects.tryEmit(
ReadBookEffect.NavigateToSearchResult(
result = result,
chapterIndex = chapterIndex,
pageIndex = -1,
lineIndex = 0,
startCharIndex = 0,
endLineIndex = 0,
endCharIndex = 0,
)
)
}
}
fun reverseRemoveSameTitle() { fun reverseRemoveSameTitle() {
execute { execute {
val book = ReadBook.book ?: return@execute val book = ReadBook.book ?: return@execute
@@ -2423,7 +2494,7 @@ class ReadBookViewModel(
} }
is ConfigUpdate.ReadBarStyle -> { is ConfigUpdate.ReadBarStyle -> {
val value = update.value.coerceIn(0, 2) val value = update.value.coerceIn(0, 2)
AppConfig.updateReadBarStyleCache(value) ReadConfig.readBarStyle = value
viewModelScope.launch { viewModelScope.launch {
readSettingsRepository.setReadBarStyle(value) readSettingsRepository.setReadBarStyle(value)
} }
@@ -2760,11 +2831,41 @@ class ReadBookViewModel(
} }
} }
is ConfigUpdate.ShowBrightnessView -> { is ConfigUpdate.ShowBrightnessView -> {
ReadBookConfig.showBrightnessView = update.value
viewModelScope.launch { viewModelScope.launch {
readSettingsRepository.setShowBrightnessView(update.value) readSettingsRepository.setShowBrightnessView(update.value)
} }
_uiState.update {
it.copy(menuConfig = it.menuConfig.copy(showBrightnessView = update.value))
}
postEvent(PreferKey.showBrightnessView, "") postEvent(PreferKey.showBrightnessView, "")
} }
is ConfigUpdate.BrightnessVwPos -> {
ReadBookConfig.brightnessVwPos = update.value
viewModelScope.launch {
readSettingsRepository.setBrightnessVwPos(update.value)
}
_uiState.update {
it.copy(menuConfig = it.menuConfig.copy(brightnessVwPos = update.value))
}
}
is ConfigUpdate.BrightnessAuto -> {
ReadBookConfig.brightnessAuto = update.value
_uiState.update {
it.copy(menuConfig = it.menuConfig.copy(brightnessAuto = update.value))
}
viewModelScope.launch {
readSettingsRepository.setBrightnessAuto(update.value)
}
_effects.tryEmit(
ReadBookEffect.ToggleBrightnessAuto(
update.value,
_uiState.value.menuConfig.readBrightness
)
)
}
is ConfigUpdate.UseUnderlineGlobal -> { is ConfigUpdate.UseUnderlineGlobal -> {
ReadConfig.useUnderline = update.value ReadConfig.useUnderline = update.value
viewModelScope.launch { viewModelScope.launch {
@@ -3051,7 +3152,7 @@ class ReadBookViewModel(
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig( _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask) setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask)
)) ))
if (AppConfig.readBarStyleFollowPage) { if (ReadConfig.readBarStyleFollowPage) {
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
} }
} }
@@ -3061,7 +3162,7 @@ class ReadBookViewModel(
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig( _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask) setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask)
)) ))
if (AppConfig.readBarStyleFollowPage) { if (ReadConfig.readBarStyleFollowPage) {
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
} }
} }
@@ -3071,7 +3172,7 @@ class ReadBookViewModel(
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig( _effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
setOf(ConfigUpdateAction.UpdateBackground) setOf(ConfigUpdateAction.UpdateBackground)
)) ))
if (AppConfig.readBarStyleFollowPage) { if (ReadConfig.readBarStyleFollowPage) {
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
} }
} }
@@ -14,13 +14,15 @@ import android.widget.PopupWindow
import androidx.appcompat.view.SupportMenuInflater import androidx.appcompat.view.SupportMenuInflater
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuItemImpl import androidx.appcompat.view.menu.MenuItemImpl
import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.constant.AppLog
import io.legado.app.databinding.ItemTextBinding import io.legado.app.databinding.ItemTextBinding
import io.legado.app.databinding.PopupActionMenuBinding import io.legado.app.databinding.PopupActionMenuBinding
import io.legado.app.help.config.AppConfig import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.gone import io.legado.app.utils.gone
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
@@ -28,8 +30,6 @@ import io.legado.app.utils.sendToClip
import io.legado.app.utils.share import io.legado.app.utils.share
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible import io.legado.app.utils.visible
import androidx.core.net.toUri
import io.legado.app.constant.AppLog
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
class TextActionMenu( class TextActionMenu(
@@ -185,11 +185,11 @@ class TextActionMenu(
callBack.onMenuActionFinally() callBack.onMenuActionFinally()
} }
holder.itemView.setOnLongClickListener { holder.itemView.setOnLongClickListener {
if (AppConfig.contentSelectSpeakMod == 0) { if (ReadConfig.contentSelectSpeakMod == 0) {
AppConfig.contentSelectSpeakMod = 1 ReadConfig.contentSelectSpeakMod = 1
context.toastOnUi("切换为从选择的地方开始一直朗读") context.toastOnUi("切换为从选择的地方开始一直朗读")
} else { } else {
AppConfig.contentSelectSpeakMod = 0 ReadConfig.contentSelectSpeakMod = 0
context.toastOnUi("切换为朗读选择内容") context.toastOnUi("切换为朗读选择内容")
} }
true true
@@ -4,9 +4,9 @@ import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.os.SystemClock import android.os.SystemClock
import androidx.core.graphics.withClip import androidx.core.graphics.withClip
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded import io.legado.app.utils.canvasrecorder.recordIfNeeded
import io.legado.app.utils.themeColor import io.legado.app.utils.themeColor
@@ -30,7 +30,7 @@ class AutoPager(private val readView: ReadView) : Runnable {
fun start() { fun start() {
isRunning = true isRunning = true
isEInkMode = AppConfig.isEInkMode isEInkMode = ReadConfig.isEInkMode
readView.curPage.upSelectAble(false) readView.curPage.upSelectAble(false)
if (isEInkMode) { if (isEInkMode) {
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L) readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
@@ -49,7 +49,7 @@ class AutoPager(private val readView: ReadView) : Runnable {
isPausing = false isPausing = false
isEInkMode = false isEInkMode = false
readView.removeCallbacks(this) readView.removeCallbacks(this)
readView.curPage.upSelectAble(AppConfig.textSelectAble) readView.curPage.upSelectAble(ReadConfig.selectText)
readView.invalidate() readView.invalidate()
reset() reset()
canvasRecorder.recycle() canvasRecorder.recycle()
@@ -9,7 +9,6 @@ import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.book.isOnLineTxt import io.legado.app.help.book.isOnLineTxt
import io.legado.app.help.config.AppConfig
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.association.OpenUrlConfirmActivity import io.legado.app.ui.association.OpenUrlConfirmActivity
import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.read.page.delegate.PageDelegate
@@ -25,6 +24,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.entities.column.TextHtmlColumn import io.legado.app.ui.book.read.page.entities.column.TextHtmlColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.widget.dialog.PhotoDialog import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
@@ -40,7 +40,7 @@ import kotlin.math.min
* 阅读内容视图 * 阅读内容视图
*/ */
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
var selectAble = AppConfig.textSelectAble var selectAble = ReadConfig.selectText
val selectedPaint by lazy { val selectedPaint by lazy {
Paint().apply { Paint().apply {
color = context.getCompatColor(R.color.btn_bg_press_2) color = context.getCompatColor(R.color.btn_bg_press_2)
@@ -73,7 +73,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
//绘制图片的paint //绘制图片的paint
val imagePaint by lazy { val imagePaint by lazy {
Paint().apply { Paint().apply {
isAntiAlias = AppConfig.useAntiAlias isAntiAlias = ReadConfig.useAntiAlias
} }
} }
@@ -266,7 +266,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
handled = true handled = true
} }
is ImageColumn -> when (AppConfig.clickImgWay) { is ImageColumn -> when (ReadConfig.clickImgWay) {
"1" -> { //预览图片 "1" -> { //预览图片
activity?.showDialogFragment(PhotoDialog(column.src, isBook = true)) activity?.showDialogFragment(PhotoDialog(column.src, isBook = true))
handled = true handled = true
@@ -16,13 +16,13 @@ import io.legado.app.R
import io.legado.app.constant.AppConst.timeFormat import io.legado.app.constant.AppConst.timeFormat
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ViewBookPageBinding import io.legado.app.databinding.ViewBookPageBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.entities.TextPos import io.legado.app.ui.book.read.page.entities.TextPos
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.widget.BatteryView import io.legado.app.ui.widget.BatteryView
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.applyNavigationBarPadding import io.legado.app.utils.applyNavigationBarPadding
@@ -169,7 +169,7 @@ class PageView(
} }
fun upPaddingDisplayCutouts() { fun upPaddingDisplayCutouts() {
if (AppConfig.paddingDisplayCutouts) { if (ReadConfig.paddingDisplayCutouts) {
binding.vwRoot.setOnApplyWindowInsetsListenerCompat { _, windowInsets -> binding.vwRoot.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
binding.vwRoot.setPadding( binding.vwRoot.setPadding(
@@ -13,7 +13,6 @@ import android.widget.FrameLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PageAnim import io.legado.app.constant.PageAnim
import io.legado.app.data.entities.BookProgress import io.legado.app.data.entities.BookProgress
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadAloud import io.legado.app.model.ReadAloud
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
@@ -37,6 +36,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextBaseColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider 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.read.page.provider.LayoutProgressListener
import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import io.legado.app.utils.longToastOnUi import io.legado.app.utils.longToastOnUi
@@ -272,7 +272,7 @@ class ReadView(
} }
fun cancelSelect(clearSearchResult: Boolean = false) { fun cancelSelect(clearSearchResult: Boolean = false) {
if (isTextSelected) { if (isTextSelected || clearSearchResult) {
curPage.cancelSelect(clearSearchResult) curPage.cancelSelect(clearSearchResult)
isTextSelected = false isTextSelected = false
} }
@@ -398,39 +398,39 @@ class ReadView(
when { when {
isTextSelected -> Unit isTextSelected -> Unit
mcRect.contains(startX, startY) -> if (!isAbortAnim) { mcRect.contains(startX, startY) -> if (!isAbortAnim) {
click(AppConfig.clickActionMC) click(ReadConfig.clickActionMC)
} }
bcRect.contains(startX, startY) -> { bcRect.contains(startX, startY) -> {
click(AppConfig.clickActionBC) click(ReadConfig.clickActionBC)
} }
blRect.contains(startX, startY) -> { blRect.contains(startX, startY) -> {
click(AppConfig.clickActionBL) click(ReadConfig.clickActionBL)
} }
brRect.contains(startX, startY) -> { brRect.contains(startX, startY) -> {
click(AppConfig.clickActionBR) click(ReadConfig.clickActionBR)
} }
mlRect.contains(startX, startY) -> { mlRect.contains(startX, startY) -> {
click(AppConfig.clickActionML) click(ReadConfig.clickActionML)
} }
mrRect.contains(startX, startY) -> { mrRect.contains(startX, startY) -> {
click(AppConfig.clickActionMR) click(ReadConfig.clickActionMR)
} }
tlRect.contains(startX, startY) -> { tlRect.contains(startX, startY) -> {
click(AppConfig.clickActionTL) click(ReadConfig.clickActionTL)
} }
tcRect.contains(startX, startY) -> { tcRect.contains(startX, startY) -> {
click(AppConfig.clickActionTC) click(ReadConfig.clickActionTC)
} }
trRect.contains(startX, startY) -> { trRect.contains(startX, startY) -> {
click(AppConfig.clickActionTR) click(ReadConfig.clickActionTR)
} }
} }
} }
@@ -552,7 +552,7 @@ class ReadView(
pageDelegate = NoAnimPageDelegate(this) pageDelegate = NoAnimPageDelegate(this)
} }
} }
(pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage (pageDelegate as? ScrollPageDelegate)?.noAnim = ReadConfig.noAnimScrollPage
if (upRecorder) { if (upRecorder) {
(pageDelegate as? HorizontalPageDelegate)?.upRecorder() (pageDelegate as? HorizontalPageDelegate)?.upRecorder()
autoPager.upRecorder() autoPager.upRecorder()
@@ -603,7 +603,7 @@ class ReadView(
* 更新滑动距离 * 更新滑动距离
*/ */
fun upPageSlopSquare() { fun upPageSlopSquare() {
val pageTouchSlop = AppConfig.pageTouchSlop val pageTouchSlop = ReadConfig.pageTouchSlop
this.pageSlopSquare = if (pageTouchSlop == 0) slopSquare else pageTouchSlop this.pageSlopSquare = if (pageTouchSlop == 0) slopSquare else pageTouchSlop
pageSlopSquare2 = this.pageSlopSquare * this.pageSlopSquare pageSlopSquare2 = this.pageSlopSquare * this.pageSlopSquare
} }
@@ -697,7 +697,7 @@ class ReadView(
} }
fun invalidateTextPage() { fun invalidateTextPage() {
if (!AppConfig.optimizeRender) { if (!ReadConfig.optimizeRender) {
return return
} }
pageFactory.run { pageFactory.run {
@@ -722,7 +722,7 @@ class ReadView(
} }
fun submitRenderTask() { fun submitRenderTask() {
if (!AppConfig.optimizeRender) { if (!ReadConfig.optimizeRender) {
return return
} }
curPage.submitRenderTask() curPage.submitRenderTask()
@@ -9,11 +9,9 @@ import android.graphics.Paint
import android.graphics.Path import android.graphics.Path
import android.graphics.Shader import android.graphics.Shader
import android.os.Build import android.os.Build
import android.text.TextPaint
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.help.PaintPool import io.legado.app.help.PaintPool
import io.legado.app.help.book.isImage import io.legado.app.help.book.isImage
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
@@ -22,6 +20,7 @@ import io.legado.app.ui.book.read.page.entities.column.BaseColumn
import io.legado.app.ui.book.read.page.entities.column.TextBaseColumn import io.legado.app.ui.book.read.page.entities.column.TextBaseColumn
import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
@@ -77,7 +76,7 @@ data class TextLine(
var textPage: TextPage = emptyTextPage var textPage: TextPage = emptyTextPage
var isLeftLine = true var isLeftLine = true
val useUnderline: Boolean val useUnderline: Boolean
get() = AppConfig.useUnderline get() = ReadConfig.useUnderline
fun addColumn(column: BaseColumn) { fun addColumn(column: BaseColumn) {
if (column !is TextColumn) { if (column !is TextColumn) {
@@ -160,7 +159,7 @@ data class TextLine(
} }
fun draw(view: ContentTextView, canvas: Canvas) { fun draw(view: ContentTextView, canvas: Canvas) {
if (AppConfig.optimizeRender) { if (ReadConfig.optimizeRender) {
canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) { canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) {
drawTextLine(view, this) drawTextLine(view, this)
} }
@@ -236,7 +235,7 @@ data class TextLine(
val paint = ChapterProvider.contentPaint val paint = ChapterProvider.contentPaint
paint.color = ReadBookConfig.durConfig.curUnderlineColor() paint.color = ReadBookConfig.durConfig.curUnderlineColor()
paint.strokeWidth = ReadBookConfig.underlineHeight.toFloat() paint.strokeWidth = ReadBookConfig.underlineHeight.toFloat()
paint.pathEffect = if (dottedLine && !AppConfig.isEInkMode) paint.pathEffect = if (dottedLine && !ReadConfig.isEInkMode)
ChapterProvider.dashEffect ChapterProvider.dashEffect
else else
null null
@@ -557,7 +556,7 @@ data class TextLine(
} }
fun checkFastDraw(): Boolean { fun checkFastDraw(): Boolean {
if (!AppConfig.optimizeRender || exceed || !onlyTextColumn || textPage.isMsgPage) { if (!ReadConfig.optimizeRender || exceed || !onlyTextColumn || textPage.isMsgPage) {
return false return false
} }
if (wordSpacing != 0f && (!atLeastApi26 || !wordSpacingWorking)) { if (wordSpacing != 0f && (!atLeastApi26 || !wordSpacingWorking)) {
@@ -9,13 +9,13 @@ import androidx.annotation.Keep
import androidx.core.graphics.withTranslation import androidx.core.graphics.withTranslation
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.PaintPool import io.legado.app.help.PaintPool
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter
import io.legado.app.ui.book.read.page.entities.column.TextBaseColumn import io.legado.app.ui.book.read.page.entities.column.TextBaseColumn
import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded import io.legado.app.utils.canvasrecorder.recordIfNeeded
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
@@ -299,7 +299,7 @@ data class TextPage(
} }
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
if (AppConfig.optimizeRender) { if (ReadConfig.optimizeRender) {
render(view) render(view)
canvas.withTranslation(0f, relativeOffset) { canvas.withTranslation(0f, relativeOffset) {
canvasRecorder.draw(this) canvasRecorder.draw(this)
@@ -15,12 +15,12 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.BookContent import io.legado.app.help.book.BookContent
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.config.ReadBookConfig.dottedBase import io.legado.app.help.config.ReadBookConfig.dottedBase
import io.legado.app.help.config.ReadBookConfig.dottedRatio import io.legado.app.help.config.ReadBookConfig.dottedRatio
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.buildMainHandler import io.legado.app.utils.buildMainHandler
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
@@ -190,7 +190,7 @@ object ChapterProvider {
displayTitle.splitNotBlank("\n").forEach { text -> displayTitle.splitNotBlank("\n").forEach { text ->
setTypeText( setTypeText(
book, absStartX, durY, book, absStartX, durY,
if (AppConfig.enableReview) text + reviewChar else text, if (ReadConfig.enableReview) text + reviewChar else text,
textPages, textPages,
stringBuilder, stringBuilder,
titlePaint, titlePaint,
@@ -279,7 +279,7 @@ object ChapterProvider {
if (text.isNotBlank()) { if (text.isNotBlank()) {
setTypeText( setTypeText(
book, absStartX, durY, book, absStartX, durY,
if (AppConfig.enableReview) text + reviewChar else text, if (ReadConfig.enableReview) text + reviewChar else text,
textPages, textPages,
stringBuilder, stringBuilder,
contentPaint, contentPaint,
@@ -918,7 +918,7 @@ object ChapterProvider {
} }
else -> { else -> {
when (AppConfig.systemTypefaces) { when (ReadConfig.systemTypefaces) {
1 -> Typeface.SERIF 1 -> Typeface.SERIF
2 -> Typeface.MONOSPACE 2 -> Typeface.MONOSPACE
else -> Typeface.SANS_SERIF else -> Typeface.SANS_SERIF
@@ -1013,7 +1013,7 @@ object ChapterProvider {
tPaint.setFontVariationSettings("'wght' ${ReadBookConfig.titleBold}") tPaint.setFontVariationSettings("'wght' ${ReadBookConfig.titleBold}")
tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx() tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx()
tPaint.isAntiAlias = true tPaint.isAntiAlias = true
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && ReadConfig.optimizeRender) {
tPaint.isLinearText = true tPaint.isLinearText = true
} }
if (ReadBookConfig.textItalic) { if (ReadBookConfig.textItalic) {
@@ -1036,7 +1036,7 @@ object ChapterProvider {
cPaint.setFontVariationSettings("'wght' ${ReadBookConfig.textBold}") cPaint.setFontVariationSettings("'wght' ${ReadBookConfig.textBold}")
cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx() cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx()
cPaint.isAntiAlias = true cPaint.isAntiAlias = true
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && ReadConfig.optimizeRender) {
cPaint.isLinearText = true cPaint.isLinearText = true
} }
if (ReadBookConfig.textItalic) { if (ReadBookConfig.textItalic) {
@@ -1086,7 +1086,7 @@ object ChapterProvider {
* 更新绘制尺寸 * 更新绘制尺寸
*/ */
fun upLayout() { fun upLayout() {
when (AppConfig.doublePageHorizontal) { when (ReadConfig.doubleHorizontalPage) {
"0" -> doublePage = false "0" -> doublePage = false
"1" -> doublePage = true "1" -> doublePage = true
"2" -> { "2" -> {
@@ -27,7 +27,6 @@ import io.legado.app.data.repository.HighlightRuleRepository
import io.legado.app.help.book.BookContent import io.legado.app.help.book.BookContent
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.getBookSource import io.legado.app.help.book.getBookSource
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ImageProvider import io.legado.app.model.ImageProvider
@@ -45,6 +44,7 @@ import io.legado.app.ui.book.read.page.provider.ChapterProvider.reviewChar
import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceChar import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceChar
import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceCharC import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceCharC
import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceCharD import io.legado.app.ui.book.read.page.provider.ChapterProvider.srcReplaceCharD
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.StringUtils import io.legado.app.utils.StringUtils
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
@@ -141,7 +141,7 @@ class TextChapterLayout(
private val useZhLayout = ReadBookConfig.useZhLayout private val useZhLayout = ReadBookConfig.useZhLayout
private val isMiddleTitle = ReadBookConfig.isMiddleTitle private val isMiddleTitle = ReadBookConfig.isMiddleTitle
private val textFullJustify = ReadBookConfig.textFullJustify private val textFullJustify = ReadBookConfig.textFullJustify
private val adaptSpecialStyle = AppConfig.adaptSpecialStyle private val adaptSpecialStyle = ReadConfig.adaptSpecialStyle
private val pageAnim = book.getPageAnim() private val pageAnim = book.getPageAnim()
private val titleSegType = ReadBookConfig.titleSegType private val titleSegType = ReadBookConfig.titleSegType
private val titleSegDistance = ReadBookConfig.titleSegDistance private val titleSegDistance = ReadBookConfig.titleSegDistance
@@ -490,7 +490,7 @@ class TextChapterLayout(
text = sb.toString() text = sb.toString()
if (text.isNotBlank()) { if (text.isNotBlank()) {
wordCount += text.replace(noWordCountRegex,"").length wordCount += text.replace(noWordCountRegex,"").length
val textToType = if (AppConfig.enableReview) text + reviewChar else text val textToType = if (ReadConfig.enableReview) text + reviewChar else text
setTypeText( setTypeText(
book, book,
textToType, textToType,
@@ -1,24 +1,42 @@
package io.legado.app.ui.book.read.sheet package io.legado.app.ui.book.read.sheet
import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider import androidx.compose.material.icons.Icons
import androidx.compose.material3.MaterialTheme import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material3.Text import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.appDb
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.button.series.MediumTonalButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.text.AppText
import kotlinx.coroutines.launch
private const val CHINESE_CONVERT_ID = -1L
@Composable @Composable
fun EffectiveReplacesSheet( fun EffectiveReplacesSheet(
@@ -26,12 +44,26 @@ fun EffectiveReplacesSheet(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onOpenReplaceEditor: (id: Long, pattern: String?) -> Unit, onOpenReplaceEditor: (id: Long, pattern: String?) -> Unit,
onReplaceRuleChanged: () -> Unit, onReplaceRuleChanged: () -> Unit,
onNavigateToTextEffects: () -> Unit,
) { ) {
val effectiveRules = remember { val scope = rememberCoroutineScope()
val chineseConvertActive = ReadConfig.chineseConverterType > 0
val chineseConvertItem = remember { ReplaceRule(CHINESE_CONVERT_ID, "繁简转换") }
val effectiveRules = remember(show) {
ReadBook.curTextChapter?.effectiveReplaceRules ?: emptyList() ReadBook.curTextChapter?.effectiveReplaceRules ?: emptyList()
} }
var isEdited by remember { mutableStateOf(false) } val items = remember(show, effectiveRules, chineseConvertActive) {
if (chineseConvertActive) {
effectiveRules + chineseConvertItem
} else {
effectiveRules
}
}
var isEdited by remember(show) { mutableStateOf(false) }
var disabledIds by remember(show) { mutableStateOf(emptySet<Long>()) }
AppModalBottomSheet( AppModalBottomSheet(
show = show, show = show,
@@ -44,18 +76,80 @@ fun EffectiveReplacesSheet(
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
items(effectiveRules, key = { it.id }) { rule -> items(items, key = { it.id }) { rule ->
Text( if (rule.id !in disabledIds) {
text = rule.name, NormalCard(
style = MaterialTheme.typography.bodyLarge, onClick = {
modifier = Modifier if (rule.id == CHINESE_CONVERT_ID) {
.fillMaxWidth() onNavigateToTextEffects()
.clickable { } else {
onOpenReplaceEditor(rule.id, rule.pattern) onOpenReplaceEditor(rule.id, rule.pattern)
}
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
containerColor = LegadoTheme.colorScheme.onSheetContent
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
AppText(
text = rule.name,
style = LegadoTheme.typography.labelLargeEmphasized,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (rule.id != CHINESE_CONVERT_ID) {
Row(verticalAlignment = Alignment.CenterVertically) {
AppText(
text = rule.pattern,
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Spacer(Modifier.width(4.dp))
AppIcon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
modifier = Modifier.size(14.dp),
tint = LegadoTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.width(4.dp))
AppText(
text = rule.replacement.ifEmpty { "∅" },
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
)
}
}
}
MediumTonalButton(
onClick = {
disabledIds = disabledIds + rule.id
isEdited = true
if (rule.id == CHINESE_CONVERT_ID) {
ReadConfig.chineseConverterType = 0
} else {
scope.launch {
rule.isEnabled = false
appDb.replaceRuleDao.insert(rule)
}
}
},
icon = Icons.Default.Close
)
} }
.padding(vertical = 12.dp), }
) }
HorizontalDivider()
} }
} }
} }
@@ -87,9 +87,6 @@ fun MoreConfigSheet(
onUseZhLayoutChange = { onUseZhLayoutChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseZhLayout(it))) onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseZhLayout(it)))
}, },
onShowBrightnessViewChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowBrightnessView(it)))
},
onUseUnderlineChange = { onUseUnderlineChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseUnderlineGlobal(it))) onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseUnderlineGlobal(it)))
}, },
@@ -170,7 +167,6 @@ private fun ScreenSettings(
onTextBottomJustifyChange: (Boolean) -> Unit, onTextBottomJustifyChange: (Boolean) -> Unit,
onAdaptSpecialStyleChange: (Boolean) -> Unit, onAdaptSpecialStyleChange: (Boolean) -> Unit,
onUseZhLayoutChange: (Boolean) -> Unit, onUseZhLayoutChange: (Boolean) -> Unit,
onShowBrightnessViewChange: (Boolean) -> Unit,
onUseUnderlineChange: (Boolean) -> Unit, onUseUnderlineChange: (Boolean) -> Unit,
) { ) {
val screenDirectionEntries = stringArrayResource(R.array.screen_direction_title) val screenDirectionEntries = stringArrayResource(R.array.screen_direction_title)
@@ -232,11 +228,6 @@ private fun ScreenSettings(
checked = preferences.useZhLayout, checked = preferences.useZhLayout,
onCheckedChange = onUseZhLayoutChange, onCheckedChange = onUseZhLayoutChange,
) )
TinySwitchSettingItem(
title = stringResource(R.string.show_brightness_view),
checked = preferences.showBrightnessView,
onCheckedChange = onShowBrightnessViewChange,
)
TinySwitchSettingItem( TinySwitchSettingItem(
title = stringResource(R.string.use_underline), title = stringResource(R.string.use_underline),
checked = preferences.useUnderline, checked = preferences.useUnderline,
@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -23,62 +22,9 @@ import io.legado.app.ui.book.read.ConfigUpdate
import io.legado.app.ui.book.read.ReadBookButtonConfigItem import io.legado.app.ui.book.read.ReadBookButtonConfigItem
import io.legado.app.ui.book.read.ReadBookIntent import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.book.read.ReadBookStyleConfig import io.legado.app.ui.book.read.ReadBookStyleConfig
import io.legado.app.ui.widget.components.SectionTitle
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.tabRow.CardTabRow import io.legado.app.ui.widget.components.tabRow.CardTabRow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable
fun ReadStyleSheet(
onDismissRequest: () -> Unit,
onOpenPaddingConfig: () -> Unit,
onOpenMoreConfig: () -> Unit,
onOpenBgTextConfig: (Int) -> Unit,
onOpenShadowSet: () -> Unit,
onOpenUnderlineConfig: () -> Unit,
onOpenHighlightRule: () -> Unit,
onOpenFontSelect: () -> Unit,
onToggleDayNight: () -> Unit,
readMenuCustomIcons: Map<String, String> = emptyMap(),
bottomBarButtons: List<ReadBookButtonConfigItem> = emptyList(),
onIntent: (ReadBookIntent) -> Unit,
styleConfig: ReadBookStyleConfig = ReadBookStyleConfig(),
) {
var showTextTitle by remember { mutableStateOf(false) }
AppModalBottomSheet(
show = true,
onDismissRequest = {
onIntent(ReadBookIntent.SaveReadStyleConfig)
onDismissRequest()
},
title = stringResource(R.string.read_config),
) {
ReadStyleContent(
onOpenPaddingConfig = onOpenPaddingConfig,
onOpenMoreConfig = onOpenMoreConfig,
onOpenBgTextConfig = onOpenBgTextConfig,
onOpenTextTitle = { showTextTitle = true },
onOpenFontSelect = onOpenFontSelect,
onToggleDayNight = onToggleDayNight,
readMenuCustomIcons = readMenuCustomIcons,
bottomBarButtons = bottomBarButtons,
onIntent = onIntent,
styleConfig = styleConfig,
)
TextTitlePage(
show = showTextTitle,
onDismissRequest = { showTextTitle = false },
onOpenShadowSet = onOpenShadowSet,
onOpenUnderlineConfig = onOpenUnderlineConfig,
onOpenHighlightRule = onOpenHighlightRule,
onOpenFontSelect = onOpenFontSelect,
onIntent = onIntent,
)
}
}
@Composable @Composable
fun ReadStyleContent( fun ReadStyleContent(
onOpenPaddingConfig: () -> Unit, onOpenPaddingConfig: () -> Unit,
@@ -87,6 +33,7 @@ fun ReadStyleContent(
onOpenTextTitle: () -> Unit, onOpenTextTitle: () -> Unit,
onOpenFontSelect: () -> Unit, onOpenFontSelect: () -> Unit,
onToggleDayNight: () -> Unit, onToggleDayNight: () -> Unit,
onPageChanged: (Int) -> Unit = {},
readMenuCustomIcons: Map<String, String> = emptyMap(), readMenuCustomIcons: Map<String, String> = emptyMap(),
bottomBarButtons: List<ReadBookButtonConfigItem> = emptyList(), bottomBarButtons: List<ReadBookButtonConfigItem> = emptyList(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -100,6 +47,7 @@ fun ReadStyleContent(
LaunchedEffect(pagerState) { LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page -> snapshotFlow { pagerState.currentPage }.collect { page ->
currentPage = page currentPage = page
onPageChanged(page)
} }
} }
@@ -340,6 +340,31 @@ private fun GlobalMenuTab(
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
SectionTitle(stringResource(R.string.show_brightness_view))
TinyDropdownSettingItem(
title = stringResource(R.string.show_brightness_view),
selectedValue = preferences.showBrightnessView,
displayEntries = stringArrayResource(R.array.brightness_bar_mode_title),
entryValues = stringArrayResource(R.array.brightness_bar_mode_value),
onValueChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowBrightnessView(it)))
},
)
if (preferences.showBrightnessView == "2") {
TinyDropdownSettingItem(
title = stringResource(R.string.brightness_bar_position),
selectedValue = preferences.brightnessVwPos,
displayEntries = stringArrayResource(R.array.brightness_bar_position_title),
entryValues = stringArrayResource(R.array.brightness_bar_position_value),
onValueChange = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.BrightnessVwPos(it)))
},
)
}
Spacer(Modifier.height(8.dp))
var borderEnabled by remember { var borderEnabled by remember {
mutableStateOf(preferences.readMenuBorderWidth > 0) mutableStateOf(preferences.readMenuBorderWidth > 0)
} }
@@ -41,7 +41,6 @@ import io.legado.app.ui.book.read.ConfigUpdate
import io.legado.app.ui.book.read.ReadBookIntent import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.config.readConfig.ReadConfig import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem
@@ -55,51 +54,6 @@ private const val COLOR_TEXT = 1
private const val COLOR_ACCENT = 2 private const val COLOR_ACCENT = 2
private const val COLOR_TITLE = 4 private const val COLOR_TITLE = 4
// ========== Text & Title Sheet ==========
@Composable
internal fun TextTitlePage(
show: Boolean,
onDismissRequest: () -> Unit,
onOpenShadowSet: () -> Unit,
onOpenUnderlineConfig: () -> Unit,
onOpenHighlightRule: () -> Unit,
onOpenFontSelect: () -> Unit,
onIntent: (ReadBookIntent) -> Unit,
) {
val scope = rememberCoroutineScope()
val tabTitles = listOf(
stringResource(R.string.read_config_text_effects),
stringResource(R.string.read_config_layout_spacing),
stringResource(R.string.read_config_title_settings),
)
val pagerState = rememberPagerState(pageCount = { 3 })
var selectedTab by remember { mutableIntStateOf(0) }
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.settledPage }.collect { selectedTab = it }
}
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest,
title = stringResource(R.string.read_config_text_effects),
) {
ReadStyleTextTitleContent(
tabTitles = tabTitles,
selectedTab = selectedTab,
onSelectedTabChange = { selectedTab = it },
pagerState = pagerState,
onOpenShadowSet = onOpenShadowSet,
onOpenUnderlineConfig = onOpenUnderlineConfig,
onOpenHighlightRule = onOpenHighlightRule,
onOpenFontSelect = onOpenFontSelect,
animateToPage = { page -> scope.launch { pagerState.animateScrollToPage(page) } },
onIntent = onIntent,
)
}
}
@Composable @Composable
fun ReadStyleTextTitleContent( fun ReadStyleTextTitleContent(
onOpenShadowSet: () -> Unit, onOpenShadowSet: () -> Unit,
@@ -1,17 +0,0 @@
package io.legado.app.ui.book.searchContent
import androidx.compose.runtime.Composable
import io.legado.app.base.BaseComposeActivity
import io.legado.app.ui.theme.AppTheme
class SearchContentActivity : BaseComposeActivity() {
@Composable
override fun Content() {
AppTheme {
SearchContentScreen(
onBack = { finish() }
)
}
}
}
@@ -0,0 +1,31 @@
package io.legado.app.ui.book.searchContent
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
/**
* Singleton bus for passing search results from [SearchContentScreen] back to the reader.
*
* Uses [MutableSharedFlow] with `replay = 1` so the reader entry receives the result
* even if it subscribes after the emission (the reader entry is not composed while
* the search route is on top of the NavDisplay back stack).
*/
object SearchContentResult {
data class Result(
val searchResults: List<SearchResult>,
val index: Int,
val query: String,
)
private val _results = MutableSharedFlow<Result>(replay = 1)
val results = _results.asSharedFlow()
fun emitResult(result: Result) {
_results.tryEmit(result)
}
fun resetReplayCache() {
_results.resetReplayCache()
}
}
@@ -1,8 +1,5 @@
package io.legado.app.ui.book.searchContent package io.legado.app.ui.book.searchContent
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.LocalActivity
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -81,8 +78,6 @@ fun SearchContentScreen(
onBack: () -> Unit, onBack: () -> Unit,
viewModel: SearchContentViewModel = koinViewModel() viewModel: SearchContentViewModel = koinViewModel()
) { ) {
val activity = LocalActivity.current
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
val searchQuery by viewModel.searchQuery.collectAsState() val searchQuery by viewModel.searchQuery.collectAsState()
val replaceEnabled by viewModel.replaceEnabled.collectAsState() val replaceEnabled by viewModel.replaceEnabled.collectAsState()
@@ -259,13 +254,8 @@ fun SearchContentScreen(
result = result, result = result,
isCurrentChapter = result.chapterIndex == durChapterIndex, isCurrentChapter = result.chapterIndex == durChapterIndex,
onClick = { onClick = {
viewModel.onSearchResultClick(result) { key -> if (viewModel.onSearchResultClick(result)) {
val intent = Intent().apply { onBack()
putExtra("key", key)
putExtra("index", index)
}
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()
} }
} }
) )
@@ -1,16 +1,12 @@
package io.legado.app.ui.book.searchContent package io.legado.app.ui.book.searchContent
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchContentHistory import io.legado.app.data.entities.SearchContentHistory
import io.legado.app.data.repository.BookRepository import io.legado.app.data.repository.BookRepository
import io.legado.app.data.repository.SearchContentRepository import io.legado.app.data.repository.SearchContentRepository
import io.legado.app.help.IntentData
import io.legado.app.utils.postEvent
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,15 +38,13 @@ sealed interface SearchContentState {
class SearchContentViewModel( class SearchContentViewModel(
savedStateHandle: SavedStateHandle, val bookUrl: String,
initialSearchWord: String?,
private val searchResultIndex: Int,
private val bookRepository: BookRepository, private val bookRepository: BookRepository,
private val searchContentRepository: SearchContentRepository private val searchContentRepository: SearchContentRepository
) : ViewModel() { ) : ViewModel() {
val bookUrl: String = savedStateHandle.get<String>("bookUrl") ?: ""
private val initialSearchWord: String? = savedStateHandle.get<String>("searchWord")
private val searchResultIndex: Int = savedStateHandle.get<Int>("searchResultIndex") ?: 0
private val _searchQuery = MutableStateFlow(initialSearchWord ?: "") private val _searchQuery = MutableStateFlow(initialSearchWord ?: "")
val searchQuery = _searchQuery.asStateFlow() val searchQuery = _searchQuery.asStateFlow()
@@ -189,12 +183,18 @@ class SearchContentViewModel(
hasAutoScrolled = true hasAutoScrolled = true
} }
fun onSearchResultClick(searchResult: SearchResult, onSuccess: (key: Long) -> Unit) { fun onSearchResultClick(searchResult: SearchResult): Boolean {
stopSearch() stopSearch()
postEvent(EventBus.SEARCH_RESULT, uiState.value.searchResults) val results = uiState.value.searchResults
val key = System.currentTimeMillis() val index = results.indexOf(searchResult)
IntentData.put("searchResult$key", searchResult) if (index < 0) return false
IntentData.put("searchResultList$key", uiState.value.searchResults) SearchContentResult.emitResult(
onSuccess(key) SearchContentResult.Result(
searchResults = results,
index = index,
query = searchResult.query
)
)
return true
} }
} }
@@ -1,10 +1,139 @@
package io.legado.app.ui.config.readConfig package io.legado.app.ui.config.readConfig
import io.legado.app.BuildConfig
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.repository.ReadPreferences
import io.legado.app.ui.config.prefDelegate import io.legado.app.ui.config.prefDelegate
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.utils.isNightMode
import io.legado.app.utils.sysConfiguration
object ReadConfig { object ReadConfig {
val isEInkMode: Boolean
get() = ThemeConfig.appTheme == "4"
var isNightTheme: Boolean
get() = when (ThemeConfig.themeMode) {
"1" -> false
"2" -> true
else -> sysConfiguration.isNightMode
}
set(value) {
val newMode = if (value) "2" else "1"
if (ThemeConfig.themeMode != newMode) {
ThemeConfig.themeMode = newMode
}
}
var useAntiAlias by prefDelegate(
PreferKey.antiAlias,
false
)
val enableReview: Boolean
get() = BuildConfig.DEBUG && enableReviewPref
private var enableReviewPref by prefDelegate(
PreferKey.enableReview,
false
)
var systemTypefaces by prefDelegate(
PreferKey.systemTypefaces,
0
)
var readUrlInBrowser by prefDelegate(
PreferKey.readUrlOpenInBrowser,
false
)
var contentSelectSpeakMod by prefDelegate(
PreferKey.contentSelectSpeakMod,
0
)
var syncBookProgress by prefDelegate(
PreferKey.syncBookProgress,
true
)
var syncBookProgressPlus by prefDelegate(
PreferKey.syncBookProgressPlus,
false
)
var preDownloadNum by prefDelegate(
PreferKey.preDownloadNum,
10
)
var imageRetainNum by prefDelegate(
PreferKey.imageRetainNum,
0
)
var audioCacheCleanTimeOrgin by prefDelegate(
PreferKey.audioCacheCleanTime,
10
)
val audioCacheCleanTime: Long
get() = audioCacheCleanTimeOrgin * 60 * 1000L
var audioPreDownloadNum by prefDelegate(
PreferKey.audioPreDownloadNum,
10
)
private const val defaultSpeechRate = 5
val speechRatePlay: Int
get() = if (ttsFollowSys) defaultSpeechRate else ttsSpeechRate
fun syncReadPreferences(preferences: ReadPreferences) {
optimizeRender = preferences.optimizeRender
adaptSpecialStyle = preferences.adaptSpecialStyle
useUnderline = preferences.useUnderline
brightnessVwPos = preferences.brightnessVwPos
readBrightness = preferences.readBrightness
brightnessAuto = preferences.brightnessAuto
chineseConverterType = preferences.chineseConverterType
clickActionTL = preferences.clickActionTL
clickActionTC = preferences.clickActionTC
clickActionTR = preferences.clickActionTR
clickActionML = preferences.clickActionML
clickActionMC = preferences.clickActionMC
clickActionMR = preferences.clickActionMR
clickActionBL = preferences.clickActionBL
clickActionBC = preferences.clickActionBC
clickActionBR = preferences.clickActionBR
screenOrientation = preferences.screenOrientation
noAnimScrollPage = preferences.noAnimScrollPage
tocUiUseReplace = preferences.tocUiUseReplace
tocCountWords = preferences.tocCountWords
autoChangeSource = preferences.autoChangeSource
clickImgWay = preferences.clickImgWay
doubleHorizontalPage = preferences.doubleHorizontalPage
progressBarBehavior = preferences.progressBarBehavior
keyPageOnLongPress = preferences.keyPageOnLongPress
volumeKeyPage = preferences.volumeKeyPage
volumeKeyPageOnPlay = preferences.volumeKeyPageOnPlay
mouseWheelPage = preferences.mouseWheelPage
paddingDisplayCutouts = preferences.paddingDisplayCutouts
pageTouchSlop = preferences.pageTouchSlop
showReadTitleAddition = preferences.showReadTitleAddition
titleBarMode = preferences.titleBarMode
menuAlpha = preferences.menuAlpha
readSliderMode = preferences.readSliderMode
readBarStyleFollowPage = preferences.readBarStyleFollowPage
readBarStyle = preferences.readBarStyle
defaultSourceChangeAll = preferences.defaultSourceChangeAll
sliderVibrator = preferences.sliderVibrator
selectVibrator = preferences.selectVibrator
}
var tocUiUseReplace by prefDelegate( var tocUiUseReplace by prefDelegate(
PreferKey.tocUiUseReplace, PreferKey.tocUiUseReplace,
false false
@@ -82,7 +211,22 @@ object ReadConfig {
var showBrightnessView by prefDelegate( var showBrightnessView by prefDelegate(
PreferKey.showBrightnessView, PreferKey.showBrightnessView,
true "1"
)
var brightnessVwPos by prefDelegate(
PreferKey.brightnessVwPos,
"1"
)
var readBrightness by prefDelegate(
PreferKey.brightness,
100
)
var brightnessAuto by prefDelegate(
PreferKey.brightnessAuto,
false
) )
var useUnderline by prefDelegate( var useUnderline by prefDelegate(
@@ -180,6 +324,73 @@ object ReadConfig {
true true
) )
var clickActionTL by prefDelegate(
PreferKey.clickActionTL,
2
)
var clickActionTC by prefDelegate(
PreferKey.clickActionTC,
2
)
var clickActionTR by prefDelegate(
PreferKey.clickActionTR,
1
)
var clickActionML by prefDelegate(
PreferKey.clickActionML,
2
)
var clickActionMC by prefDelegate(
PreferKey.clickActionMC,
0
)
var clickActionMR by prefDelegate(
PreferKey.clickActionMR,
1
)
var clickActionBL by prefDelegate(
PreferKey.clickActionBL,
2
)
var clickActionBC by prefDelegate(
PreferKey.clickActionBC,
1
)
var clickActionBR by prefDelegate(
PreferKey.clickActionBR,
1
)
fun hasMenuClickArea(): Boolean {
return clickActionTL * clickActionTC * clickActionTR *
clickActionML * clickActionMC * clickActionMR *
clickActionBL * clickActionBC * clickActionBR == 0
}
fun detectClickArea() {
if (!hasMenuClickArea()) {
clickActionMC = 0
}
}
var readBarStyleFollowPage by prefDelegate(
PreferKey.readBarStyleFollowPage,
false
)
var readBarStyle by prefDelegate(
PreferKey.readBarStyle,
0
)
var prevKeys by prefDelegate( var prevKeys by prefDelegate(
PreferKey.prevKeys, PreferKey.prevKeys,
"" ""
@@ -14,7 +14,9 @@ data class ReadConfigUiState(
val textBottomJustify: Boolean = true, val textBottomJustify: Boolean = true,
val adaptSpecialStyle: Boolean = true, val adaptSpecialStyle: Boolean = true,
val useZhLayout: Boolean = false, val useZhLayout: Boolean = false,
val showBrightnessView: Boolean = true, val showBrightnessView: String = "1",
val brightnessVwPos: String = "1",
val brightnessAuto: Boolean = false,
val useUnderline: Boolean = false, val useUnderline: Boolean = false,
val readSliderMode: String = "0", val readSliderMode: String = "0",
val doubleHorizontalPage: String = "0", val doubleHorizontalPage: String = "0",
@@ -53,7 +55,8 @@ sealed interface ReadConfigIntent {
data class TextBottomJustifyChanged(val value: Boolean) : ReadConfigIntent data class TextBottomJustifyChanged(val value: Boolean) : ReadConfigIntent
data class AdaptSpecialStyleChanged(val value: Boolean) : ReadConfigIntent data class AdaptSpecialStyleChanged(val value: Boolean) : ReadConfigIntent
data class UseZhLayoutChanged(val value: Boolean) : ReadConfigIntent data class UseZhLayoutChanged(val value: Boolean) : ReadConfigIntent
data class ShowBrightnessViewChanged(val value: Boolean) : ReadConfigIntent data class ShowBrightnessViewChanged(val value: String) : ReadConfigIntent
data class BrightnessVwPosChanged(val value: String) : ReadConfigIntent
data class UseUnderlineChanged(val value: Boolean) : ReadConfigIntent data class UseUnderlineChanged(val value: Boolean) : ReadConfigIntent
data class ReadSliderModeChanged(val value: String) : ReadConfigIntent data class ReadSliderModeChanged(val value: String) : ReadConfigIntent
data class DoubleHorizontalPageChanged(val value: String) : ReadConfigIntent data class DoubleHorizontalPageChanged(val value: String) : ReadConfigIntent
@@ -18,13 +18,13 @@ import io.legado.app.R
import io.legado.app.ui.theme.adaptiveContentPadding import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SplicedColumnGroup import io.legado.app.ui.widget.components.SplicedColumnGroup
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
import io.legado.app.ui.widget.components.settingItem.SliderSettingItem import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -173,14 +173,28 @@ fun ReadConfigScreen(
} }
) )
SwitchSettingItem( DropdownListSettingItem(
title = stringResource(R.string.show_brightness_view), title = stringResource(R.string.show_brightness_view),
checked = state.showBrightnessView, selectedValue = state.showBrightnessView,
onCheckedChange = { displayEntries = stringArrayResource(R.array.brightness_bar_mode_title),
entryValues = stringArrayResource(R.array.brightness_bar_mode_value),
onValueChange = {
viewModel.onIntent(ReadConfigIntent.ShowBrightnessViewChanged(it)) viewModel.onIntent(ReadConfigIntent.ShowBrightnessViewChanged(it))
} }
) )
if (state.showBrightnessView == "2") {
DropdownListSettingItem(
title = stringResource(R.string.brightness_bar_position),
selectedValue = state.brightnessVwPos,
displayEntries = stringArrayResource(R.array.brightness_bar_position_title),
entryValues = stringArrayResource(R.array.brightness_bar_position_value),
onValueChange = {
viewModel.onIntent(ReadConfigIntent.BrightnessVwPosChanged(it))
}
)
}
SwitchSettingItem( SwitchSettingItem(
title = stringResource(R.string.use_underline), title = stringResource(R.string.use_underline),
checked = state.useUnderline, checked = state.useUnderline,
@@ -92,6 +92,10 @@ class ReadConfigViewModel(
readSettingsRepository.setShowBrightnessView(intent.value) readSettingsRepository.setShowBrightnessView(intent.value)
} }
is ReadConfigIntent.BrightnessVwPosChanged -> {
readSettingsRepository.setBrightnessVwPos(intent.value)
}
is ReadConfigIntent.UseUnderlineChanged -> { is ReadConfigIntent.UseUnderlineChanged -> {
readSettingsRepository.setUseUnderline(intent.value) readSettingsRepository.setUseUnderline(intent.value)
} }
@@ -209,6 +213,8 @@ class ReadConfigViewModel(
adaptSpecialStyle = adaptSpecialStyle, adaptSpecialStyle = adaptSpecialStyle,
useZhLayout = useZhLayout, useZhLayout = useZhLayout,
showBrightnessView = showBrightnessView, showBrightnessView = showBrightnessView,
brightnessVwPos = brightnessVwPos,
brightnessAuto = brightnessAuto,
useUnderline = useUnderline, useUnderline = useUnderline,
readSliderMode = readSliderMode, readSliderMode = readSliderMode,
doubleHorizontalPage = doubleHorizontalPage, doubleHorizontalPage = doubleHorizontalPage,
@@ -41,17 +41,19 @@ import io.legado.app.ui.book.readRecord.ReadRecordScreen
import io.legado.app.ui.book.search.SearchIntent import io.legado.app.ui.book.search.SearchIntent
import io.legado.app.ui.book.search.SearchScreen import io.legado.app.ui.book.search.SearchScreen
import io.legado.app.ui.book.search.SearchViewModel import io.legado.app.ui.book.search.SearchViewModel
import io.legado.app.ui.book.searchContent.SearchContentScreen
import io.legado.app.ui.book.searchContent.SearchContentViewModel
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.config.ConfigNavScreen import io.legado.app.ui.config.ConfigNavScreen
import io.legado.app.ui.config.backupConfig.BackupConfigScreen import io.legado.app.ui.config.backupConfig.BackupConfigScreen
import io.legado.app.ui.config.coverConfig.CoverConfigScreen import io.legado.app.ui.config.coverConfig.CoverConfigScreen
import io.legado.app.ui.config.customTheme.CustomThemeScreen import io.legado.app.ui.config.customTheme.CustomThemeScreen
import io.legado.app.ui.config.downloadCacheConfig.DownloadCacheConfigScreen import io.legado.app.ui.config.downloadCacheConfig.DownloadCacheConfigScreen
import io.legado.app.ui.config.labConfig.LabConfigScreen
import io.legado.app.ui.config.otherConfig.OtherConfigScreen import io.legado.app.ui.config.otherConfig.OtherConfigScreen
import io.legado.app.ui.config.readConfig.ReadConfigScreen import io.legado.app.ui.config.readConfig.ReadConfigScreen
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
import io.legado.app.ui.config.themeManage.ThemeManageScreen import io.legado.app.ui.config.themeManage.ThemeManageScreen
import io.legado.app.ui.config.labConfig.LabConfigScreen
import io.legado.app.ui.config.translation.TranslationConfigScreen import io.legado.app.ui.config.translation.TranslationConfigScreen
import io.legado.app.ui.rss.article.MainRouteRssSort import io.legado.app.ui.rss.article.MainRouteRssSort
import io.legado.app.ui.rss.article.RssSortRouteScreen import io.legado.app.ui.rss.article.RssSortRouteScreen
@@ -69,6 +71,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@OptIn(ExperimentalSharedTransitionApi::class) @OptIn(ExperimentalSharedTransitionApi::class)
fun MainActivity.mainEntryProvider( fun MainActivity.mainEntryProvider(
@@ -294,6 +297,15 @@ fun MainActivity.mainEntryProvider(
host = controller, host = controller,
controller = controller, controller = controller,
onEffectsReady = { effectsReady.complete(Unit) }, onEffectsReady = { effectsReady.complete(Unit) },
onOpenSearch = { word, bookUrl ->
onNavigateToRoute(
MainRouteSearchContent(
bookUrl = bookUrl,
searchWord = word,
searchResultIndex = readBookViewModel.uiState.value.searchResultIndex
)
)
},
) )
DisposableEffect(controller, lifecycleOwner, route.readAloud) { DisposableEffect(controller, lifecycleOwner, route.readAloud) {
@@ -340,6 +352,16 @@ fun MainActivity.mainEntryProvider(
} }
} }
entry<MainRouteSearchContent> { route ->
val viewModel = koinViewModel<SearchContentViewModel>(
parameters = { parametersOf(route) }
)
SearchContentScreen(
viewModel = viewModel,
onBack = { onNavigateBack() },
)
}
entry<MainRouteSearch> { route -> entry<MainRouteSearch> { route ->
val searchViewModel = koinViewModel<SearchViewModel>() val searchViewModel = koinViewModel<SearchViewModel>()
@@ -97,6 +97,13 @@ data class MainRouteExploreShow(
val exploreUrl: String?, val exploreUrl: String?,
) : MainRoute ) : MainRoute
@Serializable
data class MainRouteSearchContent(
val bookUrl: String,
val searchWord: String? = null,
val searchResultIndex: Int = 0,
) : MainRoute
@Serializable @Serializable
data object MainRouteAbout : MainRoute data object MainRouteAbout : MainRoute
@@ -118,6 +125,7 @@ object MainRouteConst {
const val ROUTE_BOOK_CACHE_MANAGE = "book/cache/manage" const val ROUTE_BOOK_CACHE_MANAGE = "book/cache/manage"
const val ROUTE_READ_BOOK = "book/read" const val ROUTE_READ_BOOK = "book/read"
const val ROUTE_SEARCH = "search" const val ROUTE_SEARCH = "search"
const val ROUTE_SEARCH_CONTENT = "book/searchContent"
const val ROUTE_BOOK_INFO = "book/info" const val ROUTE_BOOK_INFO = "book/info"
const val ROUTE_EXPLORE_SHOW = "explore/show" const val ROUTE_EXPLORE_SHOW = "explore/show"
const val ROUTE_RSS_SORT = "rss/sort" const val ROUTE_RSS_SORT = "rss/sort"
@@ -60,6 +60,10 @@ object MainNavigator {
} }
} }
is MainRouteSearchContent -> {
backStack.add(route)
}
is MainRouteSearch -> { is MainRouteSearch -> {
if ( if (
currentRoute == MainRouteHome || currentRoute == MainRouteHome ||
@@ -11,7 +11,7 @@ import android.text.StaticLayout
import android.util.AttributeSet import android.util.AttributeSet
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.help.config.AppConfig import io.legado.app.ui.config.readConfig.ReadConfig
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
@@ -75,7 +75,7 @@ class BatteryViewOrgin @JvmOverloads constructor(
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
if (AppConfig.optimizeRender) { if (ReadConfig.optimizeRender) {
canvasRecorder.recordIfNeededThenDraw(canvas, width, height) { canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
super.onDraw(this) super.onDraw(this)
drawBattery(this) drawBattery(this)
@@ -0,0 +1,68 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import androidx.compose.material3.Slider as M3Slider
import top.yukonga.miuix.kmp.basic.Slider as MiuixSlider
/**
* Vertical slider component that supports both Miuix and M3 engines.
*
* @param height Visual height of the vertical slider.
*/
@Composable
fun AppVerticalSlider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
height: Dp = 160.dp,
) {
val sliderModifier = Modifier
.requiredWidth(height)
.requiredHeight(48.dp)
.graphicsLayer { rotationZ = -90f }
Box(
modifier = modifier
.width(48.dp)
.height(height),
contentAlignment = Alignment.Center,
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
MiuixSlider(
value = value,
onValueChange = onValueChange,
modifier = sliderModifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = onValueChangeFinished,
)
} else {
M3Slider(
value = value,
onValueChange = onValueChange,
modifier = sliderModifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = onValueChangeFinished,
)
}
}
}
@@ -143,14 +143,14 @@ private fun FontItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(LegadoTheme.colorScheme.surfaceVariant) .background(LegadoTheme.colorScheme.surface)
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(12.dp), .padding(12.dp),
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.height(48.dp)
) { ) {
val context = LocalContext.current
AndroidView( AndroidView(
factory = { ctx -> factory = { ctx ->
android.widget.TextView(ctx).apply { android.widget.TextView(ctx).apply {
@@ -178,7 +178,7 @@ private fun FontItem(
Icon( Icon(
Icons.Default.Check, Icons.Default.Check,
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.primary, tint = LegadoTheme.colorScheme.primary,
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
) )
} }
@@ -1,7 +1,7 @@
package io.legado.app.utils.canvasrecorder package io.legado.app.utils.canvasrecorder
import android.os.Build import android.os.Build
import io.legado.app.help.config.AppConfig import io.legado.app.ui.config.readConfig.ReadConfig
object CanvasRecorderFactory { object CanvasRecorderFactory {
@@ -12,7 +12,7 @@ object CanvasRecorderFactory {
// issue 3868 // issue 3868
fun create(locked: Boolean = false): CanvasRecorder { fun create(locked: Boolean = false): CanvasRecorder {
val impl = when { val impl = when {
!AppConfig.optimizeRender -> CanvasRecorderImpl() !ReadConfig.optimizeRender -> CanvasRecorderImpl()
atLeastApi29 -> CanvasRecorderApi29Impl() atLeastApi29 -> CanvasRecorderApi29Impl()
atLeastApi24 -> CanvasRecorderApi23Impl() atLeastApi24 -> CanvasRecorderApi23Impl()
else -> CanvasRecorderImpl() else -> CanvasRecorderImpl()
@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentLayout="@layout/view_search"
app:contentInsetRight="24dp"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/refresh_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminate="true"
app:layout_constraintTop_toBottomOf="@id/title_bar" />
<io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info"
app:layout_constraintTop_toBottomOf="@id/refresh_progress_bar"
tools:ignore="SpeakableTextPresentCheck" />
<LinearLayout
android:id="@+id/ll_search_base_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurfaceContainer"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tv_current_search_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:singleLine="true"
android:textSize="14sp"
android:textStyle="bold" />
<Space
android:layout_width="20dp"
android:layout_height="1dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/iv_search_content_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
android:contentDescription="@string/go_to_top"
app:icon="@drawable/ic_arrow_drop_up"
app:iconGravity="textStart"
android:tooltipText="@string/go_to_top"
tools:ignore="UnusedAttribute" />
<com.google.android.material.button.MaterialButton
android:id="@+id/iv_search_content_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
android:contentDescription="@string/go_to_bottom"
app:icon="@drawable/ic_arrow_drop_down"
app:iconGravity="textStart"
android:tooltipText="@string/go_to_bottom"
tools:ignore="UnusedAttribute" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fb_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_stop_black_24dp"
android:visibility="invisible"
android:contentDescription="@string/stop"
app:fabSize="mini"
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -907,6 +907,13 @@
<string name="wrong_format">格式不对</string> <string name="wrong_format">格式不对</string>
<string name="error">错误</string> <string name="error">错误</string>
<string name="show_brightness_view">显示亮度调节控件</string> <string name="show_brightness_view">显示亮度调节控件</string>
<string name="brightness_bar_off">关闭</string>
<string name="brightness_bar_horizontal">横排</string>
<string name="brightness_bar_vertical">竖排</string>
<string name="brightness_bar_position">亮度条位置</string>
<string name="brightness_bar_left">左侧</string>
<string name="brightness_bar_right">右侧</string>
<string name="brightness_follow_system">跟随系统</string>
<string name="language">语言</string> <string name="language">语言</string>
<string name="import_rss_source">导入订阅源</string> <string name="import_rss_source">导入订阅源</string>
<string name="donate_summary">您的支持是我更新的动力</string> <string name="donate_summary">您的支持是我更新的动力</string>
+11
View File
@@ -73,6 +73,17 @@
<item>chapter</item> <item>chapter</item>
</string-array> </string-array>
<string-array name="brightness_bar_mode_value">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="brightness_bar_position_value">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="language_value"> <string-array name="language_value">
<item>auto</item> <item>auto</item>
<item>zh</item> <item>zh</item>
+11
View File
@@ -147,6 +147,17 @@
<item>@string/adjust_chapter_index</item> <item>@string/adjust_chapter_index</item>
</string-array> </string-array>
<string-array name="brightness_bar_mode_title">
<item>@string/brightness_bar_off</item>
<item>@string/brightness_bar_horizontal</item>
<item>@string/brightness_bar_vertical</item>
</string-array>
<string-array name="brightness_bar_position_title">
<item>@string/brightness_bar_left</item>
<item>@string/brightness_bar_right</item>
</string-array>
<string-array name="icon_names" tools:ignore="Typos"> <string-array name="icon_names" tools:ignore="Typos">
<item>iconMain</item> <item>iconMain</item>
<item>iconWhite</item> <item>iconWhite</item>
+7
View File
@@ -932,6 +932,13 @@
<string name="wrong_format">Wrong format</string> <string name="wrong_format">Wrong format</string>
<string name="error">Error</string> <string name="error">Error</string>
<string name="show_brightness_view">Show Brightness Widget</string> <string name="show_brightness_view">Show Brightness Widget</string>
<string name="brightness_bar_off">Off</string>
<string name="brightness_bar_horizontal">Horizontal</string>
<string name="brightness_bar_vertical">Vertical</string>
<string name="brightness_bar_position">Brightness Bar Position</string>
<string name="brightness_bar_left">Left</string>
<string name="brightness_bar_right">Right</string>
<string name="brightness_follow_system">Follow System</string>
<string name="language">Language</string> <string name="language">Language</string>
<string name="import_rss_source">Import rss source</string> <string name="import_rss_source">Import rss source</string>
<string name="donate_summary">Your donation makes this app better</string> <string name="donate_summary">Your donation makes this app better</string>
+4 -2
View File
@@ -93,10 +93,12 @@
android:title="@string/use_zh_layout" android:title="@string/use_zh_layout"
app:isBottomBackground="true" /> app:isBottomBackground="true" />
<io.legado.app.lib.prefs.SwitchPreference <io.legado.app.lib.prefs.ListPreference
android:defaultValue="true" android:defaultValue="1"
android:key="showBrightnessView" android:key="showBrightnessView"
android:title="@string/show_brightness_view" android:title="@string/show_brightness_view"
android:entries="@array/brightness_bar_mode_title"
android:entryValues="@array/brightness_bar_mode_value"
app:isBottomBackground="true" /> app:isBottomBackground="true" />
<io.legado.app.lib.prefs.SwitchPreference <io.legado.app.lib.prefs.SwitchPreference