fix: 补全修复阅读界面缺失的一些功能和同步问题
This commit is contained in:
@@ -163,6 +163,7 @@ object PreferKey {
|
||||
const val clearWebViewData = "clearWebViewData"
|
||||
const val onlyLatestBackup = "onlyLatestBackup"
|
||||
const val brightnessVwPos = "brightnessVwPos"
|
||||
const val brightnessAuto = "brightnessAuto"
|
||||
const val shrinkDatabase = "shrinkDatabase"
|
||||
const val batchChangeSourceDelay = "batchChangeSourceDelay"
|
||||
const val openBookInfoByClickTitle = "openBookInfoByClickTitle"
|
||||
|
||||
@@ -80,7 +80,9 @@ class ReadAloudSettingsRepository(
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -29,7 +29,10 @@ data class ReadPreferences(
|
||||
val textBottomJustify: Boolean = true,
|
||||
val adaptSpecialStyle: Boolean = true,
|
||||
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 readSliderMode: String = "0",
|
||||
val doubleHorizontalPage: String = "0",
|
||||
@@ -157,8 +160,17 @@ class ReadSettingsRepository(
|
||||
suspend fun setUseZhLayout(value: Boolean) =
|
||||
settingsRepository.putBoolean(PreferKey.useZhLayout, value)
|
||||
|
||||
suspend fun setShowBrightnessView(value: Boolean) =
|
||||
settingsRepository.putBoolean(PreferKey.showBrightnessView, value)
|
||||
suspend fun setShowBrightnessView(value: String) =
|
||||
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) =
|
||||
settingsRepository.putBoolean(PreferKey.useUnderline, value)
|
||||
@@ -370,7 +382,10 @@ class ReadSettingsRepository(
|
||||
textBottomJustify = this[Keys.TextBottomJustify] ?: true,
|
||||
adaptSpecialStyle = this[Keys.AdaptSpecialStyle] ?: true,
|
||||
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,
|
||||
readSliderMode = this[Keys.ReadSliderMode] ?: "0",
|
||||
doubleHorizontalPage = this[Keys.DoubleHorizontalPage] ?: "0",
|
||||
@@ -461,7 +476,10 @@ class ReadSettingsRepository(
|
||||
val TextBottomJustify = booleanPreferencesKey(PreferKey.textBottomJustify)
|
||||
val AdaptSpecialStyle = booleanPreferencesKey(PreferKey.adaptSpecialStyle)
|
||||
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 ReadSliderMode = stringPreferencesKey(PreferKey.readSliderMode)
|
||||
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.dict.DictViewModel
|
||||
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.bookshelf.BookshelfViewModel
|
||||
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.WebDavFile
|
||||
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.FileUtils
|
||||
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.compress.ZipUtils
|
||||
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.normalizeFileName
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@@ -33,7 +33,6 @@ import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
/**
|
||||
* webDav初始化会访问网络,不要放到主线程
|
||||
@@ -359,10 +358,10 @@ object AppWebDav {
|
||||
appDb.bookDao.all.forEach { book ->
|
||||
val progressFileName = getProgressFileName(book.name, book.author)
|
||||
val webDavFile = map[progressFileName]
|
||||
webDavFile ?: return
|
||||
webDavFile ?: return@forEach
|
||||
if (webDavFile.lastModify <= book.syncTime) {
|
||||
//本地同步时间大于上传时间不用同步
|
||||
return
|
||||
return@forEach
|
||||
}
|
||||
getBookProgress(book)?.let { bookProgress ->
|
||||
if (bookProgress.durChapterIndex > book.durChapterIndex
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.widget.TextView
|
||||
import com.bumptech.glide.Glide
|
||||
@@ -95,10 +94,11 @@ class GlideImageGetter(private val context: Context, textView: TextView, private
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
targets.forEach {
|
||||
val snapshot = targets.toList()
|
||||
targets.clear()
|
||||
snapshot.forEach {
|
||||
Glide.with(context).clear(it)
|
||||
}
|
||||
targets.clear()
|
||||
textViewRef.clear()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.legado.app.help
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
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.entities.Book
|
||||
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.WebDavFile
|
||||
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.FileUtils
|
||||
import io.legado.app.utils.GSON
|
||||
@@ -253,9 +253,9 @@ class WebDavManager(
|
||||
}
|
||||
appDb.bookDao.all.forEach { book ->
|
||||
val progressFileName = getProgressFileName(book.name, book.author)
|
||||
val webDavFile = map[progressFileName] ?: return
|
||||
val webDavFile = map[progressFileName] ?: return@forEach
|
||||
if (webDavFile.lastModify <= book.syncTime) {
|
||||
return
|
||||
return@forEach
|
||||
}
|
||||
getBookProgress(book)?.let { bookProgress ->
|
||||
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.BookChapter
|
||||
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.localBook.LocalBook
|
||||
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.FileUtils
|
||||
import io.legado.app.utils.ImageUtils
|
||||
@@ -133,12 +133,12 @@ object BookHelp {
|
||||
private fun clearComicCache(book: Book) {
|
||||
//只处理漫画
|
||||
//为0的时候,不清除已缓存数据
|
||||
if (!book.isImage || AppConfig.imageRetainNum == 0) {
|
||||
if (!book.isImage || ReadConfig.imageRetainNum == 0) {
|
||||
return
|
||||
}
|
||||
//向前保留设定数量,向后保留预下载数量
|
||||
val startIndex = book.durChapterIndex - AppConfig.imageRetainNum
|
||||
val endIndex = book.durChapterIndex + AppConfig.preDownloadNum
|
||||
val startIndex = book.durChapterIndex - ReadConfig.imageRetainNum
|
||||
val endIndex = book.durChapterIndex + ReadConfig.preDownloadNum
|
||||
val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl, startIndex, endIndex)
|
||||
val imgNames = hashSetOf<String>()
|
||||
//获取需要保留章节的图片信息
|
||||
@@ -202,7 +202,7 @@ object BookHelp {
|
||||
book.getFolderName(),
|
||||
bookChapter.getFileName(),
|
||||
).writeText(content)
|
||||
if (book.isOnLineTxt && AppConfig.tocCountWords) {
|
||||
if (book.isOnLineTxt && ReadConfig.tocCountWords) {
|
||||
val wordCount = StringUtils.wordCountFormat(content.length)
|
||||
bookChapter.wordCount = wordCount
|
||||
appDb.bookChapterDao.update(bookChapter)
|
||||
|
||||
@@ -132,6 +132,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
ReadConfig.defaultSourceChangeAll = preferences.defaultSourceChangeAll
|
||||
ReadConfig.sliderVibrator = preferences.sliderVibrator
|
||||
ReadConfig.selectVibrator = preferences.selectVibrator
|
||||
ReadConfig.brightnessVwPos = preferences.brightnessVwPos
|
||||
ReadConfig.readBrightness = preferences.readBrightness
|
||||
}
|
||||
|
||||
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.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.fromJsonObject
|
||||
import io.legado.app.utils.getMeanColor
|
||||
@@ -208,6 +209,10 @@ object ReadBookConfig {
|
||||
var readMenuBorderColorNight by prefDelegate(PreferKey.readMenuBorderColorNight, 0)
|
||||
var showTitleBarIcons by prefDelegate(PreferKey.showTitleBarIcons, true)
|
||||
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
|
||||
get() = if (isComic) comicStyleSelect else readStyleSelect
|
||||
@@ -301,7 +306,7 @@ object ReadBookConfig {
|
||||
val resolvedMenuBgColor: Int
|
||||
get() {
|
||||
val isNight = ReadStyleResolver.isNightTheme()
|
||||
return when (AppConfig.readBarStyle) {
|
||||
return when (ReadConfig.readBarStyle) {
|
||||
1 -> { // 跟随阅读背景
|
||||
val background = ReadStyleResolver.currentBackground(durConfig)
|
||||
if (background.type == 0) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.drawable.Drawable
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.graphics.toColorInt
|
||||
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.FileUtils
|
||||
import io.legado.app.utils.externalFiles
|
||||
@@ -28,14 +29,14 @@ object ReadStyleResolver {
|
||||
|
||||
fun currentMode(): ReadStyleMode {
|
||||
return when {
|
||||
AppConfig.isEInkMode -> ReadStyleMode.EInk
|
||||
AppConfig.isNightTheme -> ReadStyleMode.Night
|
||||
ReadConfig.isEInkMode -> ReadStyleMode.EInk
|
||||
ReadConfig.isNightTheme -> ReadStyleMode.Night
|
||||
else -> ReadStyleMode.Day
|
||||
}
|
||||
}
|
||||
|
||||
fun isNightTheme(): Boolean {
|
||||
return AppConfig.isNightTheme
|
||||
return ReadConfig.isNightTheme
|
||||
}
|
||||
|
||||
fun setCurrentBackground(
|
||||
|
||||
@@ -8,10 +8,10 @@ import io.legado.app.constant.EventBus
|
||||
import io.legado.app.constant.IntentAction
|
||||
import io.legado.app.data.appDb
|
||||
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.HttpReadAloudService
|
||||
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.StringUtils
|
||||
import io.legado.app.utils.postEvent
|
||||
@@ -21,7 +21,7 @@ import splitties.init.appCtx
|
||||
|
||||
object ReadAloud {
|
||||
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
|
||||
|
||||
private fun getReadAloudClass(): Class<*> {
|
||||
@@ -134,4 +134,4 @@ object ReadAloud {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import io.legado.app.help.book.isSameNameAuthor
|
||||
import io.legado.app.help.book.readSimulating
|
||||
import io.legado.app.help.book.simulatedTotalChapterNum
|
||||
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.coroutine.Coroutine
|
||||
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.TranslationChapterStatus
|
||||
import io.legado.app.model.translation.TranslationManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.service.BaseReadAloudService
|
||||
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.provider.ChapterProvider
|
||||
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.stackTraceStr
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@@ -51,6 +50,7 @@ import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -211,7 +211,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
ReadBookConfig.isComic = book.isImage
|
||||
if (oldIndex != ReadBookConfig.styleSelect) {
|
||||
postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))
|
||||
if (AppConfig.readBarStyleFollowPage) {
|
||||
if (ReadConfig.readBarStyleFollowPage) {
|
||||
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
|
||||
}
|
||||
}
|
||||
@@ -288,7 +288,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
uploadSuccessAction: (() -> Unit)? = null,
|
||||
syncSuccessAction: (() -> Unit)? = null
|
||||
) {
|
||||
if (!AppConfig.syncBookProgress) return
|
||||
if (!ReadConfig.syncBookProgress) return
|
||||
val book = book ?: return
|
||||
Coroutine.async {
|
||||
AppWebDav.getBookProgress(book)
|
||||
@@ -301,7 +301,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
) {
|
||||
// 服务器没有进度或者进度比服务器快,上传现有进度
|
||||
Coroutine.async {
|
||||
AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction)
|
||||
AppWebDav.uploadBookProgress(book, onSuccess = uploadSuccessAction)
|
||||
book.update()
|
||||
}
|
||||
} else if (progress.durChapterIndex > book.durChapterIndex ||
|
||||
@@ -560,7 +560,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
}
|
||||
|
||||
fun recycleRecorders(beforeIndex: Int, afterIndex: Int) {
|
||||
if (!AppConfig.optimizeRender) {
|
||||
if (!ReadConfig.optimizeRender) {
|
||||
return
|
||||
}
|
||||
executor.execute {
|
||||
@@ -1131,7 +1131,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
private fun preDownload() {
|
||||
if (book?.isLocal == true) return
|
||||
executor.execute {
|
||||
if (AppConfig.preDownloadNum < 2) {
|
||||
if (ReadConfig.preDownloadNum < 2) {
|
||||
return@execute
|
||||
}
|
||||
preDownloadTask?.cancel()
|
||||
@@ -1139,7 +1139,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
//预下载
|
||||
launch {
|
||||
val maxChapterIndex =
|
||||
min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)
|
||||
min(durChapterIndex + ReadConfig.preDownloadNum, chapterSize)
|
||||
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
|
||||
if (downloadedChapters.contains(i)) continue
|
||||
if ((downloadFailChapters[i] ?: 0) >= 3) continue
|
||||
@@ -1147,7 +1147,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
}
|
||||
}
|
||||
launch {
|
||||
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)
|
||||
val minChapterIndex = durChapterIndex - min(5, ReadConfig.preDownloadNum)
|
||||
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
|
||||
if (downloadedChapters.contains(i)) 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.simulatedTotalChapterNum
|
||||
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.globalExecutor
|
||||
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.MangaPage
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
@@ -45,7 +45,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.getValue
|
||||
import kotlin.math.min
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@@ -472,7 +471,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
private fun preDownload() {
|
||||
if (book?.isLocal == true) return
|
||||
executor.execute {
|
||||
if (AppConfig.preDownloadNum < 2) {
|
||||
if (ReadConfig.preDownloadNum < 2) {
|
||||
return@execute
|
||||
}
|
||||
preDownloadTask?.cancel()
|
||||
@@ -480,7 +479,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
//预下载
|
||||
launch {
|
||||
val maxChapterIndex =
|
||||
min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)
|
||||
min(durChapterIndex + ReadConfig.preDownloadNum, chapterSize)
|
||||
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
|
||||
if (downloadedChapters.contains(i)) continue
|
||||
if ((downloadFailChapters[i] ?: 0) >= 3) continue
|
||||
@@ -488,7 +487,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
}
|
||||
}
|
||||
launch {
|
||||
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)
|
||||
val minChapterIndex = durChapterIndex - min(5, ReadConfig.preDownloadNum)
|
||||
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
|
||||
if (downloadedChapters.contains(i)) continue
|
||||
if ((downloadFailChapters[i] ?: 0) >= 3) continue
|
||||
@@ -594,7 +593,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
uploadSuccessAction: (() -> Unit)? = null,
|
||||
syncSuccessAction: (() -> Unit)? = null,
|
||||
) {
|
||||
if (!AppConfig.syncBookProgress) return
|
||||
if (!ReadConfig.syncBookProgress) return
|
||||
val book = book ?: return
|
||||
Coroutine.async {
|
||||
AppWebDav.getBookProgress(book)
|
||||
@@ -607,7 +606,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
) {
|
||||
// 服务器没有进度或者进度比服务器快,上传现有进度
|
||||
Coroutine.async {
|
||||
AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction)
|
||||
AppWebDav.uploadBookProgress(book, onSuccess = uploadSuccessAction)
|
||||
book.update()
|
||||
}
|
||||
} else if (progress.durChapterIndex > book.durChapterIndex ||
|
||||
@@ -721,4 +720,4 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
fun showLoading()
|
||||
fun startLoad()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ import io.legado.app.data.entities.BookChapter
|
||||
import io.legado.app.data.entities.HttpTTS
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
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.exoplayer.InputStreamDataSource
|
||||
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.analyzeRule.AnalyzeUrl
|
||||
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.MD5Utils
|
||||
import io.legado.app.utils.printOnDebug
|
||||
@@ -98,7 +97,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
private val loadErrorHandlingPolicy by lazy {
|
||||
CustomLoadErrorHandlingPolicy()
|
||||
}
|
||||
private var speechRate: Int = AppConfig.speechRatePlay + 5
|
||||
private var speechRate: Int = ReadConfig.speechRatePlay + 5
|
||||
private var downloadTask: Coroutine<*>? = null
|
||||
private var playIndexJob: Job? = null
|
||||
private var downloadErrorNo: Int = 0
|
||||
@@ -210,7 +209,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
private suspend fun preDownloadAudios(httpTts: HttpTTS) {
|
||||
val book = ReadBook.book ?: return
|
||||
val currentIdx = ReadBook.durChapterIndex
|
||||
val limit = AppConfig.audioPreDownloadNum
|
||||
val limit = ReadConfig.audioPreDownloadNum
|
||||
|
||||
try {
|
||||
for (i in 1..limit) {
|
||||
@@ -300,7 +299,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
) {
|
||||
val book = ReadBook.book ?: return
|
||||
val currentIdx = ReadBook.durChapterIndex
|
||||
val limit = AppConfig.audioPreDownloadNum
|
||||
val limit = ReadConfig.audioPreDownloadNum
|
||||
|
||||
try {
|
||||
for (i in 1..limit) {
|
||||
@@ -491,7 +490,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
* 如果时间设置为0,则不再保护当前章节,退出即全删。
|
||||
*/
|
||||
private fun removeCacheFile() {
|
||||
val keepTime = AppConfig.audioCacheCleanTime
|
||||
val keepTime = ReadConfig.audioCacheCleanTime
|
||||
// 只有当时间大于0时,才需要保护当前章节。如果为0,说明用户想彻底不留缓存。
|
||||
val protectCurrentChapter = keepTime > 0
|
||||
val titleMd5 = if (protectCurrentChapter) MD5Utils.md5Encode16(this.textChapter?.chapter?.title ?: "") else ""
|
||||
@@ -572,7 +571,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
override fun upSpeechRate(reset: Boolean) {
|
||||
downloadTask?.cancel()
|
||||
exoPlayer.stop()
|
||||
speechRate = AppConfig.speechRatePlay + 5
|
||||
speechRate = ReadConfig.speechRatePlay + 5
|
||||
if (ReadConfig.streamReadAloudAudio) {
|
||||
downloadAndPlayAudiosStream()
|
||||
} else {
|
||||
|
||||
@@ -38,12 +38,9 @@ import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.databinding.ActivityMangaBinding
|
||||
import io.legado.app.databinding.ViewLoadMoreBinding
|
||||
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.isImage
|
||||
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.source.getSourceType
|
||||
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.toc.TocActivityResult
|
||||
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.widget.number.NumberPickerDialog
|
||||
import io.legado.app.ui.widget.recycler.LoadMoreView
|
||||
@@ -220,7 +219,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
binding.llRetry.isGone = true
|
||||
ReadManga.loadOrUpContent()
|
||||
}
|
||||
binding.flLoading.isVisible = !AppConfig.isEInkMode
|
||||
binding.flLoading.isVisible = !ReadConfig.isEInkMode
|
||||
binding.eyeProtectionOverlay.refresh()
|
||||
mAdapter.addFooterView {
|
||||
ViewLoadMoreBinding.bind(loadMoreView)
|
||||
@@ -458,7 +457,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
networkChangedListener.register()
|
||||
networkChangedListener.onNetworkChanged = {
|
||||
// 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑)
|
||||
if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
|
||||
if (ReadConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
|
||||
ReadManga.syncProgress({ progress -> sureNewProgress(progress) })
|
||||
}
|
||||
}
|
||||
@@ -480,7 +479,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
if (ReadManga.inBookshelf) {
|
||||
ReadManga.saveRead()
|
||||
if (!BuildConfig.DEBUG) {
|
||||
if (AppConfig.syncBookProgressPlus) {
|
||||
if (ReadConfig.syncBookProgressPlus) {
|
||||
ReadManga.syncProgress()
|
||||
} else {
|
||||
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.removeType
|
||||
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.model.ReadBook
|
||||
import io.legado.app.model.ReadManga
|
||||
import io.legado.app.model.localBook.LocalBook
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
@@ -48,6 +46,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import splitties.init.appCtx
|
||||
import java.io.ByteArrayOutputStream
|
||||
import io.legado.app.ui.config.readConfig.ReadConfig as GlobalReadConfig
|
||||
|
||||
class ReadMangaViewModel(
|
||||
application: Application,
|
||||
@@ -112,7 +111,7 @@ class ReadMangaViewModel(
|
||||
// 有章节跳转不同步阅读进度
|
||||
ReadManga.chapterChanged = false
|
||||
} else if (!isSameBook) {
|
||||
if (AppConfig.syncBookProgressPlus) {
|
||||
if (GlobalReadConfig.syncBookProgressPlus) {
|
||||
ReadManga.syncProgress(
|
||||
{ progress -> ReadManga.mCallback?.sureNewProgress(progress) })
|
||||
} else {
|
||||
@@ -195,7 +194,7 @@ class ReadMangaViewModel(
|
||||
* 自动换源
|
||||
*/
|
||||
private fun autoChangeSource(name: String, author: String) {
|
||||
if (!AppConfig.autoChangeSource) return
|
||||
if (!GlobalReadConfig.autoChangeSource) return
|
||||
execute {
|
||||
val sources = appDb.bookSourceDao.allTextEnabledPart
|
||||
flow {
|
||||
@@ -246,7 +245,7 @@ class ReadMangaViewModel(
|
||||
book: Book,
|
||||
alertSync: ((progress: BookProgress) -> Unit)? = null
|
||||
) {
|
||||
if (!AppConfig.syncBookProgress) return
|
||||
if (!GlobalReadConfig.syncBookProgress) return
|
||||
execute {
|
||||
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
|
||||
}.onError {
|
||||
|
||||
@@ -14,12 +14,12 @@ import androidx.core.view.isVisible
|
||||
import com.google.android.material.slider.Slider
|
||||
import io.legado.app.R
|
||||
import io.legado.app.databinding.ViewMangaMenuBinding
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.source.getSourceType
|
||||
import io.legado.app.lib.dialogs.alert
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.model.ReadManga
|
||||
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.applyNavigationBarPadding
|
||||
import io.legado.app.utils.gone
|
||||
@@ -112,13 +112,13 @@ class MangaMenu @JvmOverloads constructor(
|
||||
// val brightnessBackground = GradientDrawable()
|
||||
// brightnessBackground.cornerRadius = 5F.dpToPx()
|
||||
// //brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f))
|
||||
// if (AppConfig.isEInkMode) {
|
||||
// if (ReadConfig.isEInkMode) {
|
||||
// titleBar.setBackgroundResource(R.drawable.bg_eink_border_bottom)
|
||||
// bottomMenu.setBackgroundResource(R.drawable.bg_eink_border_top)
|
||||
// } else {
|
||||
// //bottomMenu.setBackgroundColor(bgColor)
|
||||
// }
|
||||
if (AppConfig.showReadTitleBarAddition) {
|
||||
if (ReadConfig.showReadTitleAddition) {
|
||||
titleBarAddition.visible()
|
||||
} else {
|
||||
titleBarAddition.gone()
|
||||
@@ -134,7 +134,7 @@ class MangaMenu @JvmOverloads constructor(
|
||||
menuTopOut.setAnimationListener(menuOutListener)
|
||||
}
|
||||
|
||||
fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode) {
|
||||
fun runMenuOut(anim: Boolean = !ReadConfig.isEInkMode) {
|
||||
if (isMenuOutAnimating) {
|
||||
return
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class MangaMenu @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) {
|
||||
fun runMenuIn(anim: Boolean = !ReadConfig.isEInkMode) {
|
||||
this.visible()
|
||||
binding.titleBar.visible()
|
||||
binding.bottomMenu.visible()
|
||||
@@ -181,7 +181,7 @@ class MangaMenu @JvmOverloads constructor(
|
||||
callBack.openBookInfoActivity()
|
||||
}
|
||||
val chapterViewClickListener = OnClickListener {
|
||||
if (AppConfig.readUrlInBrowser) {
|
||||
if (ReadConfig.readUrlInBrowser) {
|
||||
context.openUrl(tvChapterUrl.text.toString().substringBefore(",{"))
|
||||
} else {
|
||||
context.startActivity<WebViewActivity> {
|
||||
@@ -199,10 +199,10 @@ class MangaMenu @JvmOverloads constructor(
|
||||
context.alert(R.string.open_fun) {
|
||||
setMessage(R.string.use_browser_open)
|
||||
okButton {
|
||||
AppConfig.readUrlInBrowser = true
|
||||
ReadConfig.readUrlInBrowser = true
|
||||
}
|
||||
noButton {
|
||||
AppConfig.readUrlInBrowser = false
|
||||
ReadConfig.readUrlInBrowser = false
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
@@ -196,6 +196,10 @@ data class ReadMenuConfig(
|
||||
val readMenuCustomIcons: ImmutableMap<String, String> = persistentMapOf(),
|
||||
val titleBarButtons: 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
|
||||
@@ -300,7 +304,7 @@ sealed interface ReadBookIntent {
|
||||
|
||||
// Brightness
|
||||
data class SetBrightness(val value: Int) : ReadBookIntent
|
||||
data object ToggleBrightnessAuto : ReadBookIntent
|
||||
data class ToggleBrightnessAuto(val auto: Boolean) : ReadBookIntent
|
||||
|
||||
// Seek bar jump
|
||||
data class SeekToChapter(val index: Int) : ReadBookIntent
|
||||
@@ -526,7 +530,7 @@ sealed interface ReadBookEffect {
|
||||
data object CancelSelect : ReadBookEffect
|
||||
data object UpSystemUiVisibility : 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
|
||||
data object ToggleReadAloud : ReadBookEffect
|
||||
@@ -535,7 +539,15 @@ sealed interface ReadBookEffect {
|
||||
|
||||
// Search
|
||||
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
|
||||
|
||||
// Source actions
|
||||
@@ -1040,7 +1052,15 @@ sealed interface ConfigUpdate {
|
||||
data class UseZhLayout(val value: Boolean) : ConfigUpdate {
|
||||
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>()
|
||||
}
|
||||
data class UseUnderlineGlobal(val value: Boolean) : ConfigUpdate {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
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.entities.BookProgress
|
||||
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.storage.Backup
|
||||
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.ReadView
|
||||
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.TextPageFactory
|
||||
import io.legado.app.ui.config.readConfig.ReadConfig
|
||||
import io.legado.app.ui.login.SourceLoginJsExtensions
|
||||
import io.legado.app.ui.widget.PopupAction
|
||||
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.visible
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -341,7 +344,7 @@ class ReadBookController(
|
||||
r.textMenuPosition.x = x
|
||||
r.textMenuPosition.y = top
|
||||
|
||||
if (AppConfig.selectVibrator) {
|
||||
if (ReadConfig.selectVibrator) {
|
||||
r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE)
|
||||
}
|
||||
}
|
||||
@@ -351,7 +354,7 @@ class ReadBookController(
|
||||
r.cursorRight.x = x
|
||||
r.cursorRight.y = y
|
||||
r.cursorRight.visible(true)
|
||||
if (AppConfig.selectVibrator) {
|
||||
if (ReadConfig.selectVibrator) {
|
||||
r.root.performHapticFeedback(HapticFeedbackConstantsCompat.TEXT_HANDLE_MOVE)
|
||||
}
|
||||
}
|
||||
@@ -616,7 +619,7 @@ class ReadBookController(
|
||||
is ReadBookEffect.LongToast -> activity.longToastOnUi(effect.message)
|
||||
is ReadBookEffect.SetBrightness -> {
|
||||
val lp = activity.window.attributes
|
||||
lp.screenBrightness = effect.value / 255f
|
||||
lp.screenBrightness = effect.value / 100f
|
||||
activity.window.attributes = lp
|
||||
}
|
||||
|
||||
@@ -682,7 +685,14 @@ class ReadBookController(
|
||||
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 ──
|
||||
@@ -695,15 +705,48 @@ class ReadBookController(
|
||||
|
||||
is ReadBookEffect.TextActionSpeak -> speak(effect.text)
|
||||
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 -> {
|
||||
if (viewModel.uiState.value.isShowingSearchResult) {
|
||||
viewModel.onIntent(ReadBookIntent.SetShowingSearchResult(false))
|
||||
ReadBook.clearSearchResult()
|
||||
refs?.readView?.cancelSelect(true)
|
||||
}
|
||||
ReadBook.clearSearchResult()
|
||||
refs?.readView?.cancelSelect(clearSearchResult = true)
|
||||
}
|
||||
|
||||
is ReadBookEffect.SyncBookProgress -> {
|
||||
@@ -935,17 +978,17 @@ class ReadBookController(
|
||||
}
|
||||
|
||||
override fun mouseWheelPage(direction: PageDirection) {
|
||||
if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) {
|
||||
if (menuLayoutIsVisible || !ReadConfig.mouseWheelPage) {
|
||||
return
|
||||
}
|
||||
keyPageDebounce(direction, mouseWheel = true, longPress = false)
|
||||
}
|
||||
|
||||
private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {
|
||||
if (!AppConfig.volumeKeyPage) {
|
||||
if (!ReadConfig.volumeKeyPage) {
|
||||
return false
|
||||
}
|
||||
if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
|
||||
if (!ReadConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {
|
||||
return false
|
||||
}
|
||||
handleKeyPage(direction, longPress)
|
||||
@@ -953,7 +996,7 @@ class ReadBookController(
|
||||
}
|
||||
|
||||
override fun handleKeyPage(direction: PageDirection, longPress: Boolean) {
|
||||
if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
|
||||
if (ReadConfig.keyPageOnLongPress || direction == PageDirection.NONE) {
|
||||
keyPage(direction)
|
||||
} else {
|
||||
keyPageDebounce(direction, longPress = longPress)
|
||||
@@ -1057,7 +1100,7 @@ class ReadBookController(
|
||||
}
|
||||
|
||||
fun setOrientation() {
|
||||
when (AppConfig.screenOrientation) {
|
||||
when (ReadConfig.screenOrientation) {
|
||||
"0" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
"1" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
"2" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
@@ -1066,4 +1109,38 @@ class ReadBookController(
|
||||
"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.SizeTransform
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
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.Login
|
||||
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.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Payment
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SwapHoriz
|
||||
import androidx.compose.material3.Icon
|
||||
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.ReadMenuBlurStyle
|
||||
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.ui.animation.DampedDragAnimation
|
||||
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.ReadStyleTextTitleContent
|
||||
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.hazeStyle.HazeLegado
|
||||
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.button.series.SmallTonalButton
|
||||
import io.legado.app.ui.widget.components.divider.PillDivider
|
||||
@@ -168,12 +178,29 @@ fun ReadBookMenuBar(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
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
|
||||
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()
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AnimatedVisibility(
|
||||
visible = state.menuVisible,
|
||||
visible = state.menuVisible || searchMenuVisible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
@@ -183,13 +210,19 @@ fun ReadBookMenuBar(
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
) { onIntent(ReadBookIntent.HideMenu) }
|
||||
) {
|
||||
if (searchMenuVisible) {
|
||||
onIntent(ReadBookIntent.HideSearchMenu)
|
||||
} else {
|
||||
onIntent(ReadBookIntent.HideMenu)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Top title bar + floating icon row (top positions)
|
||||
AnimatedVisibility(
|
||||
visible = state.menuVisible && !dialogLikeRoute,
|
||||
visible = state.menuVisible && !hideTopBar,
|
||||
enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(),
|
||||
exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(),
|
||||
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)
|
||||
AnimatedVisibility(
|
||||
visible = state.menuVisible,
|
||||
visible = state.menuVisible || searchMenuVisible,
|
||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
if (state.menuConfig.showTitleBarIcons && state.menuConfig.titleBarIconPosition >= 2) {
|
||||
if (state.menuVisible &&
|
||||
state.menuConfig.showTitleBarIcons &&
|
||||
state.menuConfig.titleBarIconPosition >= 2
|
||||
) {
|
||||
FloatingIconRow(
|
||||
state = state,
|
||||
colors = menuColors,
|
||||
@@ -240,29 +355,42 @@ fun ReadBookMenuBar(
|
||||
)
|
||||
}
|
||||
ReadBookMenuSurface(
|
||||
route = currentRoute,
|
||||
contentTarget = contentTarget,
|
||||
state = state,
|
||||
colors = menuColors,
|
||||
onIntent = onIntent,
|
||||
context = context,
|
||||
backdrop = backdrop,
|
||||
hazeState = hazeState,
|
||||
readStylePage = readStylePage,
|
||||
onReadStylePageChanged = { readStylePage = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface ReadBookMenuContent {
|
||||
data object Search : ReadBookMenuContent
|
||||
data class Route(val route: ReadBookMenuRoute) : ReadBookMenuContent
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReadBookMenuSurface(
|
||||
route: ReadBookMenuRoute,
|
||||
contentTarget: ReadBookMenuContent,
|
||||
state: ReadBookUiState,
|
||||
colors: ReadMenuColors,
|
||||
onIntent: (ReadBookIntent) -> Unit,
|
||||
context: Context,
|
||||
backdrop: Backdrop?,
|
||||
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 dialogLikeRoute = route == ReadBookMenuRoute.PaddingConfig
|
||||
val density = LocalDensity.current
|
||||
@@ -272,6 +400,18 @@ private fun ReadBookMenuSurface(
|
||||
targetValue = if (dialogLikeRoute) 1f else 0f,
|
||||
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) {
|
||||
windowSize.height.toDp() * 0.64f
|
||||
}
|
||||
@@ -377,7 +517,8 @@ private fun ReadBookMenuSurface(
|
||||
.heightIn(max = maxHeight)
|
||||
.onSizeChanged { surfaceHeightPx = it.height }
|
||||
.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())
|
||||
}
|
||||
.then(
|
||||
@@ -433,16 +574,27 @@ private fun ReadBookMenuSurface(
|
||||
contentColor = colors.content
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = route,
|
||||
targetState = contentTarget,
|
||||
transitionSpec = {
|
||||
(slideInVertically { it / 4 } + fadeIn())
|
||||
.togetherWith(slideOutVertically { -it / 4 } + fadeOut())
|
||||
.using(SizeTransform(clip = true))
|
||||
},
|
||||
label = "ReadBookMenuRoute",
|
||||
) { targetRoute ->
|
||||
when (targetRoute) {
|
||||
ReadBookMenuRoute.Main -> {
|
||||
) { target ->
|
||||
when (target) {
|
||||
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(
|
||||
state = state,
|
||||
colors = colors,
|
||||
@@ -482,6 +634,7 @@ private fun ReadBookMenuSurface(
|
||||
onToggleDayNight = {
|
||||
onIntent(ReadBookIntent.ToggleDayNight)
|
||||
},
|
||||
onPageChanged = onReadStylePageChanged,
|
||||
readMenuCustomIcons = state.menuConfig.readMenuCustomIcons,
|
||||
bottomBarButtons = state.menuConfig.bottomBarButtons,
|
||||
onIntent = onIntent,
|
||||
@@ -597,6 +750,7 @@ private fun ReadBookMenuSurface(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,7 +817,7 @@ private fun MenuTitleBar(
|
||||
backdrop: Backdrop?,
|
||||
hazeState: HazeState?,
|
||||
) {
|
||||
val titleBarMode = AppConfig.titleBarMode
|
||||
val titleBarMode = ReadConfig.titleBarMode
|
||||
|
||||
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
|
||||
private fun MenuBottomBar(
|
||||
state: ReadBookUiState,
|
||||
@@ -1431,7 +1738,7 @@ private fun MenuBottomBar(
|
||||
val target = value.roundToInt().coerceIn(0, seekMax)
|
||||
sliderDragging = false
|
||||
sliderValue = target.toFloat()
|
||||
val behavior = AppConfig.progressBarBehavior
|
||||
val behavior = ReadConfig.progressBarBehavior
|
||||
if (behavior == "page") {
|
||||
onIntent(ReadBookIntent.SkipToPage(target))
|
||||
} else {
|
||||
@@ -1459,6 +1766,27 @@ private fun MenuBottomBar(
|
||||
.padding(top = 8.dp, bottom = contentBottomPadding)
|
||||
.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
|
||||
AnimatedVisibility(visible = state.menuConfig.readSliderMode != "1") {
|
||||
Row(
|
||||
@@ -2181,8 +2509,8 @@ private fun Modifier.readMenuLiquidGlass(
|
||||
}
|
||||
},
|
||||
)
|
||||
.then(if (interactiveHighlight != null) interactiveHighlight.modifier else Modifier)
|
||||
.then(if (interactiveHighlight != null) interactiveHighlight.gestureModifier else Modifier)
|
||||
.then(interactiveHighlight?.modifier ?: Modifier)
|
||||
.then(interactiveHighlight?.gestureModifier ?: Modifier)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalHazeMaterialsApi::class)
|
||||
@@ -2288,7 +2616,7 @@ private fun readMenuBottomBarSurfaceBrush(
|
||||
private fun readMenuColors(): ReadMenuColors {
|
||||
val themeBackground = LegadoTheme.colorScheme.surfaceContainerHigh
|
||||
val themeContent = LegadoTheme.colorScheme.onSurface
|
||||
return when (AppConfig.readBarStyle) {
|
||||
return when (ReadConfig.readBarStyle) {
|
||||
1 -> ReadMenuColors(
|
||||
background = themeBackground,
|
||||
content = themeContent,
|
||||
@@ -2370,3 +2698,116 @@ private fun loadFloatingIcons(
|
||||
}
|
||||
.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.constant.AppLog
|
||||
import io.legado.app.constant.ReadMenuBlurMode
|
||||
import io.legado.app.help.IntentData
|
||||
import io.legado.app.help.IntentHelp
|
||||
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.ReadView
|
||||
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.SearchResult
|
||||
import io.legado.app.ui.book.searchContent.SearchContentResult
|
||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||
import io.legado.app.ui.book.toc.TocActivityResult
|
||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||
@@ -45,6 +43,7 @@ import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||
import io.legado.app.utils.StartActivityContract
|
||||
import io.legado.app.utils.takePersistablePermissionSafely
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -95,10 +94,12 @@ fun ReadBookRouteScreen(
|
||||
host: ReadBookRouteHost,
|
||||
controller: ReadBookController,
|
||||
onEffectsReady: () -> Unit = {},
|
||||
onOpenSearch: (word: String?, bookUrl: String) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val readPreferences by viewModel.readPreferences.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val effectsReady = remember(viewModel) { CompletableDeferred<Unit>() }
|
||||
val menuBackdrop = rememberLayerBackdrop()
|
||||
val menuHazeState = remember { HazeState() }
|
||||
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(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
@@ -245,7 +233,10 @@ fun ReadBookRouteScreen(
|
||||
LaunchedEffect(viewModel) {
|
||||
launch {
|
||||
viewModel.effects
|
||||
.onSubscription { onEffectsReady() }
|
||||
.onSubscription {
|
||||
effectsReady.complete(Unit)
|
||||
onEffectsReady()
|
||||
}
|
||||
.collect { effect ->
|
||||
try {
|
||||
when (effect) {
|
||||
@@ -292,18 +283,7 @@ fun ReadBookRouteScreen(
|
||||
)
|
||||
}
|
||||
is ReadBookEffect.OpenSearchActivity -> {
|
||||
val currentState = viewModel.uiState.value
|
||||
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)
|
||||
onOpenSearch(effect.word, effect.bookUrl)
|
||||
}
|
||||
is ReadBookEffect.MenuSettingReplace -> {
|
||||
replaceLauncher.launch(Intent(context, ReplaceRuleActivity::class.java))
|
||||
@@ -392,6 +372,24 @@ fun ReadBookRouteScreen(
|
||||
LaunchedEffect(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 ───────────────────────────────────────
|
||||
|
||||
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.DownloadSheet
|
||||
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.HttpTtsEditSheet
|
||||
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.ToolButtonConfigSheet
|
||||
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.changeSource.ChangeSourceSheet
|
||||
import io.legado.app.ui.widget.components.log.AppLogSheet
|
||||
@@ -135,6 +135,10 @@ fun ReadBookScreen(
|
||||
onIntent(ReadBookIntent.OpenReplaceEditor(id, pattern))
|
||||
},
|
||||
onReplaceRuleChanged = { onIntent(ReadBookIntent.ReplaceRuleChanged) },
|
||||
onNavigateToTextEffects = {
|
||||
onIntent(ReadBookIntent.DismissSheet)
|
||||
onIntent(ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.TextTitle))
|
||||
},
|
||||
)
|
||||
UnderlineConfigSheet(
|
||||
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.fadeIn
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
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.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.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
fun ReadBookSearchBar(
|
||||
@@ -52,9 +31,6 @@ fun ReadBookSearchBar(
|
||||
val hasResults = state.searchResultList.isNotEmpty()
|
||||
val currentIndex = state.searchResultIndex
|
||||
val totalResults = state.searchResultList.size
|
||||
val currentResult = if (hasResults && currentIndex in state.searchResultList.indices) {
|
||||
state.searchResultList[currentIndex]
|
||||
} else null
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
// Left FAB - previous result
|
||||
@@ -66,7 +42,7 @@ fun ReadBookSearchBar(
|
||||
.align(Alignment.CenterStart)
|
||||
.padding(start = 16.dp),
|
||||
) {
|
||||
SmallFloatingActionButton(
|
||||
AppFloatingActionButton(
|
||||
onClick = {
|
||||
val prevIndex = currentIndex - 1
|
||||
onIntent(
|
||||
@@ -75,11 +51,13 @@ fun ReadBookSearchBar(
|
||||
)
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
tooltipText = "上一个结果",
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
|
||||
contentColor = LegadoTheme.colorScheme.onSurfaceVariant,
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Previous",
|
||||
contentDescription = "上一个结果",
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
@@ -94,7 +72,7 @@ fun ReadBookSearchBar(
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(end = 16.dp),
|
||||
) {
|
||||
SmallFloatingActionButton(
|
||||
AppFloatingActionButton(
|
||||
onClick = {
|
||||
val nextIndex = currentIndex + 1
|
||||
onIntent(
|
||||
@@ -103,144 +81,16 @@ fun ReadBookSearchBar(
|
||||
)
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
tooltipText = "下一个结果",
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
|
||||
contentColor = LegadoTheme.colorScheme.onSurfaceVariant,
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowForward,
|
||||
contentDescription = "Next",
|
||||
contentDescription = "下一个结果",
|
||||
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.exception.NoStackTraceException
|
||||
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.ContentProcessor
|
||||
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.removeType
|
||||
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.coroutine.Coroutine
|
||||
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.searchContent.SearchResult
|
||||
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.utils.GSON
|
||||
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.openUrl
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.putPrefInt
|
||||
import io.legado.app.utils.toStringArray
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -155,7 +153,7 @@ class ReadBookViewModel(
|
||||
}
|
||||
|
||||
init {
|
||||
AppConfig.detectClickArea()
|
||||
ReadConfig.detectClickArea()
|
||||
ReadBook.register(this)
|
||||
refreshButtonConfigs()
|
||||
collectReadPreferences()
|
||||
@@ -253,7 +251,7 @@ class ReadBookViewModel(
|
||||
|
||||
is ReadBookIntent.NavigateToSearchResult -> {
|
||||
_uiState.update { it.copy(searchResultIndex = intent.index) }
|
||||
_effects.tryEmit(ReadBookEffect.NavigateToSearchResult(intent.result))
|
||||
navigateToSearchResult(intent.result)
|
||||
}
|
||||
|
||||
is ReadBookIntent.ToggleReadAloud -> {
|
||||
@@ -296,8 +294,28 @@ class ReadBookViewModel(
|
||||
is ReadBookIntent.CancelSelect -> _effects.tryEmit(ReadBookEffect.CancelSelect)
|
||||
is ReadBookIntent.UpSystemUiVisibility -> _effects.tryEmit(ReadBookEffect.UpSystemUiVisibility)
|
||||
is ReadBookIntent.UpContent -> ReadBook.loadOrUpContent()
|
||||
is ReadBookIntent.SetBrightness -> _effects.tryEmit(ReadBookEffect.SetBrightness(intent.value))
|
||||
is ReadBookIntent.ToggleBrightnessAuto -> _effects.tryEmit(ReadBookEffect.ToggleBrightnessAuto)
|
||||
is ReadBookIntent.SetBrightness -> {
|
||||
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 -> {
|
||||
ReadBook.saveCurrentBookProgress()
|
||||
openChapter(intent.index)
|
||||
@@ -627,7 +645,7 @@ class ReadBookViewModel(
|
||||
is ReadBookIntent.OpenPreDownloadNumPicker -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
preDownloadNum = AppConfig.preDownloadNum,
|
||||
preDownloadNum = ReadConfig.preDownloadNum,
|
||||
activeSheet = ReadBookSheet.PreDownloadConfig,
|
||||
)
|
||||
}
|
||||
@@ -636,7 +654,7 @@ class ReadBookViewModel(
|
||||
is ReadBookIntent.OpenCacheCleanTimePicker -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
audioCacheCleanTime = AppConfig.audioCacheCleanTimeOrgin,
|
||||
audioCacheCleanTime = ReadConfig.audioCacheCleanTimeOrgin,
|
||||
activeSheet = ReadBookSheet.AudioCacheCleanConfig,
|
||||
)
|
||||
}
|
||||
@@ -644,7 +662,8 @@ class ReadBookViewModel(
|
||||
|
||||
is ReadBookIntent.ApplySpeakEngine -> {
|
||||
ReadBook.book?.setTtsEngine(null)
|
||||
AppConfig.ttsEngine = intent.value
|
||||
ReadConfig.ttsEngine = intent.value
|
||||
ReadAloud.upReadAloudClass()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedTtsEngine = ReadAloud.ttsEngine,
|
||||
@@ -655,7 +674,7 @@ class ReadBookViewModel(
|
||||
}
|
||||
|
||||
is ReadBookIntent.ApplyPreDownloadNum -> {
|
||||
AppConfig.preDownloadNum = intent.value
|
||||
ReadConfig.preDownloadNum = intent.value
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
preDownloadNum = intent.value,
|
||||
@@ -665,7 +684,7 @@ class ReadBookViewModel(
|
||||
}
|
||||
|
||||
is ReadBookIntent.ApplyAudioCacheCleanTime -> {
|
||||
context.putPrefInt(PreferKey.audioCacheCleanTime, intent.value)
|
||||
ReadConfig.audioCacheCleanTimeOrgin = intent.value
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
audioCacheCleanTime = intent.value,
|
||||
@@ -704,7 +723,7 @@ class ReadBookViewModel(
|
||||
}.onSuccess {
|
||||
loadTtsEngineItems()
|
||||
if (ReadAloud.ttsEngine == intent.engineId.toString()) {
|
||||
AppConfig.ttsEngine = null
|
||||
ReadConfig.ttsEngine = null
|
||||
ReadAloud.upReadAloudClass()
|
||||
}
|
||||
}
|
||||
@@ -726,6 +745,7 @@ class ReadBookViewModel(
|
||||
|
||||
is ReadBookIntent.ApplySpeakEnginePerBook -> {
|
||||
ReadBook.book?.setTtsEngine(intent.value)
|
||||
ReadAloud.upReadAloudClass()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedTtsEngine = ReadAloud.ttsEngine,
|
||||
@@ -841,7 +861,7 @@ class ReadBookViewModel(
|
||||
|
||||
is ReadBookIntent.SelectFont -> selectFont(intent.path)
|
||||
is ReadBookIntent.SelectSystemTypeface -> {
|
||||
AppConfig.systemTypefaces = intent.index
|
||||
ReadConfig.systemTypefaces = intent.index
|
||||
ReadBookConfig.textFont = ""
|
||||
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
|
||||
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.ReloadContent)
|
||||
@@ -937,7 +957,7 @@ class ReadBookViewModel(
|
||||
is ReadBookIntent.ToggleDayNight -> toggleDayNight()
|
||||
// Text action menu
|
||||
is ReadBookIntent.TextActionAloud -> {
|
||||
when (AppConfig.contentSelectSpeakMod) {
|
||||
when (ReadConfig.contentSelectSpeakMod) {
|
||||
1 -> _effects.tryEmit(ReadBookEffect.TextActionAloudSelect)
|
||||
else -> _effects.tryEmit(ReadBookEffect.TextActionSpeak(intent.text))
|
||||
}
|
||||
@@ -1052,7 +1072,7 @@ class ReadBookViewModel(
|
||||
_effects.tryEmit(ReadBookEffect.UnregisterNetworkListener)
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
if (AppConfig.syncBookProgressPlus) {
|
||||
if (ReadConfig.syncBookProgressPlus) {
|
||||
ReadBook.syncProgress()
|
||||
} else {
|
||||
ReadBook.uploadProgress()
|
||||
@@ -1123,7 +1143,7 @@ class ReadBookViewModel(
|
||||
* Called from the network changed listener (registered by route).
|
||||
*/
|
||||
fun onNetworkChanged() {
|
||||
if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
|
||||
if (ReadConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
|
||||
ReadBook.syncProgress(newProgressAction = { progress ->
|
||||
sureNewProgress(progress)
|
||||
})
|
||||
@@ -1149,7 +1169,7 @@ class ReadBookViewModel(
|
||||
|
||||
private fun handleChangeSource() {
|
||||
viewModelScope.launch {
|
||||
if (AppConfig.defaultSourceChangeAll) {
|
||||
if (ReadConfig.defaultSourceChangeAll) {
|
||||
_uiState.update { it.copy(activeSheet = ReadBookSheet.ChangeBookSource) }
|
||||
} else {
|
||||
val book = ReadBook.book ?: return@launch
|
||||
@@ -1393,10 +1413,10 @@ class ReadBookViewModel(
|
||||
readSettingsRepository.preferences.collect { preferences ->
|
||||
val old = previous
|
||||
previous = preferences
|
||||
AppConfig.syncReadPreferences(preferences)
|
||||
ReadConfig.syncReadPreferences(preferences)
|
||||
_readPreferences.value = preferences
|
||||
if (!preferences.hasMenuClickArea()) {
|
||||
AppConfig.detectClickArea()
|
||||
ReadConfig.detectClickArea()
|
||||
readSettingsRepository.setClickAction(PreferKey.clickActionMC, 0)
|
||||
}
|
||||
if (old != null && old.keepLight != preferences.keepLight) {
|
||||
@@ -1423,11 +1443,7 @@ class ReadBookViewModel(
|
||||
readAloudStreamAudio = prefs.streamReadAloudAudio,
|
||||
readAloudTtsFollowSys = prefs.ttsFollowSys,
|
||||
readAloudTtsSpeechRate = prefs.ttsSpeechRate,
|
||||
readAloudTtsTimer = if (BaseReadAloudService.timeMinute > 0) {
|
||||
BaseReadAloudService.timeMinute
|
||||
} else {
|
||||
prefs.ttsTimer
|
||||
},
|
||||
readAloudTtsTimer = prefs.ttsTimer,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1445,11 +1461,12 @@ class ReadBookViewModel(
|
||||
}
|
||||
|
||||
private fun setReadAloudTtsTimer(value: Int) {
|
||||
val timer = value.coerceIn(0, 180)
|
||||
viewModelScope.launch {
|
||||
readAloudSettingsRepository.setTtsTimer(value)
|
||||
readAloudSettingsRepository.setTtsTimer(timer)
|
||||
}
|
||||
ReadAloud.setTimer(context, value)
|
||||
_uiState.update { it.copy(readAloudTtsTimer = value) }
|
||||
ReadAloud.setTimer(context, timer)
|
||||
_uiState.update { it.copy(readAloudTtsTimer = timer) }
|
||||
}
|
||||
|
||||
private fun setReadAloudTtsSpeechRate(value: Int) {
|
||||
@@ -1557,6 +1574,10 @@ class ReadBookViewModel(
|
||||
readMenuCustomIcons = ReadBookConfig.readMenuCustomIcons.toImmutableMap(),
|
||||
titleBarButtons = current.menuConfig.titleBarButtons,
|
||||
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 {
|
||||
return when (AppConfig.progressBarBehavior) {
|
||||
return when (ReadConfig.progressBarBehavior) {
|
||||
"page" -> ReadBook.durPageIndex
|
||||
else -> ReadBook.durChapterIndex
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateSeekMax(): Int {
|
||||
return when (AppConfig.progressBarBehavior) {
|
||||
return when (ReadConfig.progressBarBehavior) {
|
||||
"page" -> (ReadBook.curTextChapter?.pages?.size ?: 1) - 1
|
||||
else -> ReadBook.chapterSize - 1
|
||||
}
|
||||
@@ -1761,7 +1782,7 @@ class ReadBookViewModel(
|
||||
if (ReadBook.chapterChanged) {
|
||||
ReadBook.chapterChanged = false
|
||||
} else if (!(isSameBook && BaseReadAloudService.isRun) && ReadBook.inBookshelf) {
|
||||
if (AppConfig.syncBookProgressPlus) {
|
||||
if (ReadConfig.syncBookProgressPlus) {
|
||||
ReadBook.syncProgress({ progress -> sureNewProgress(progress) })
|
||||
} else {
|
||||
syncBookProgress(book)
|
||||
@@ -1857,7 +1878,7 @@ class ReadBookViewModel(
|
||||
book: Book,
|
||||
alertSync: ((progress: BookProgress) -> Unit)? = null
|
||||
) {
|
||||
if (!AppConfig.syncBookProgress) return
|
||||
if (!ReadConfig.syncBookProgress) return
|
||||
execute {
|
||||
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
|
||||
}.onError {
|
||||
@@ -1952,7 +1973,7 @@ class ReadBookViewModel(
|
||||
}
|
||||
|
||||
private fun autoChangeSource(name: String, author: String) {
|
||||
if (!AppConfig.autoChangeSource) return
|
||||
if (!ReadConfig.autoChangeSource) return
|
||||
execute {
|
||||
val sources = appDb.bookSourceDao.allTextEnabledPart
|
||||
flow {
|
||||
@@ -2164,11 +2185,11 @@ class ReadBookViewModel(
|
||||
|
||||
fun searchResultPositions(
|
||||
textChapter: TextChapter,
|
||||
searchResult: SearchResult
|
||||
searchResult: SearchResult,
|
||||
query: String = _uiState.value.searchContentQuery,
|
||||
): Array<Int> {
|
||||
val pages = textChapter.pages
|
||||
val content = textChapter.getContent()
|
||||
val query = _uiState.value.searchContentQuery
|
||||
val queryLength = query.length
|
||||
|
||||
var count = 0
|
||||
@@ -2178,6 +2199,7 @@ class ReadBookViewModel(
|
||||
count += 1
|
||||
}
|
||||
val contentPosition = index
|
||||
if (contentPosition < 0) return arrayOf(0, 0, 0, 0, 0)
|
||||
var pageIndex = 0
|
||||
var length = pages[pageIndex].text.length
|
||||
while (length < contentPosition && pageIndex + 1 < pages.size) {
|
||||
@@ -2191,7 +2213,7 @@ class ReadBookViewModel(
|
||||
var curLine = curTextLines[lineIndex]
|
||||
length = length - currentPage.text.length + curLine.text.length
|
||||
if (curLine.isParagraphEnd) length++
|
||||
while (length <= contentPosition && lineIndex + 1 < curTextLines.size) {
|
||||
while (length < contentPosition && lineIndex + 1 < curTextLines.size) {
|
||||
lineIndex += 1
|
||||
curLine = curTextLines[lineIndex]
|
||||
length += curLine.text.length
|
||||
@@ -2217,6 +2239,55 @@ class ReadBookViewModel(
|
||||
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() {
|
||||
execute {
|
||||
val book = ReadBook.book ?: return@execute
|
||||
@@ -2423,7 +2494,7 @@ class ReadBookViewModel(
|
||||
}
|
||||
is ConfigUpdate.ReadBarStyle -> {
|
||||
val value = update.value.coerceIn(0, 2)
|
||||
AppConfig.updateReadBarStyleCache(value)
|
||||
ReadConfig.readBarStyle = value
|
||||
viewModelScope.launch {
|
||||
readSettingsRepository.setReadBarStyle(value)
|
||||
}
|
||||
@@ -2760,11 +2831,41 @@ class ReadBookViewModel(
|
||||
}
|
||||
}
|
||||
is ConfigUpdate.ShowBrightnessView -> {
|
||||
ReadBookConfig.showBrightnessView = update.value
|
||||
viewModelScope.launch {
|
||||
readSettingsRepository.setShowBrightnessView(update.value)
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(menuConfig = it.menuConfig.copy(showBrightnessView = update.value))
|
||||
}
|
||||
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 -> {
|
||||
ReadConfig.useUnderline = update.value
|
||||
viewModelScope.launch {
|
||||
@@ -3051,7 +3152,7 @@ class ReadBookViewModel(
|
||||
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
|
||||
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask)
|
||||
))
|
||||
if (AppConfig.readBarStyleFollowPage) {
|
||||
if (ReadConfig.readBarStyleFollowPage) {
|
||||
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
|
||||
}
|
||||
}
|
||||
@@ -3061,7 +3162,7 @@ class ReadBookViewModel(
|
||||
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
|
||||
setOf(ConfigUpdateAction.UpdateStyle, ConfigUpdateAction.UpdateContent, ConfigUpdateAction.InvalidateTextPage, ConfigUpdateAction.SubmitRenderTask)
|
||||
))
|
||||
if (AppConfig.readBarStyleFollowPage) {
|
||||
if (ReadConfig.readBarStyleFollowPage) {
|
||||
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
|
||||
}
|
||||
}
|
||||
@@ -3071,7 +3172,7 @@ class ReadBookViewModel(
|
||||
_effects.tryEmit(ReadBookEffect.UpdateReadViewConfig(
|
||||
setOf(ConfigUpdateAction.UpdateBackground)
|
||||
))
|
||||
if (AppConfig.readBarStyleFollowPage) {
|
||||
if (ReadConfig.readBarStyleFollowPage) {
|
||||
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@ import android.widget.PopupWindow
|
||||
import androidx.appcompat.view.SupportMenuInflater
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.view.menu.MenuItemImpl
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.adapter.ItemViewHolder
|
||||
import io.legado.app.base.adapter.RecyclerAdapter
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.databinding.ItemTextBinding
|
||||
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.isAbsUrl
|
||||
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.toastOnUi
|
||||
import io.legado.app.utils.visible
|
||||
import androidx.core.net.toUri
|
||||
import io.legado.app.constant.AppLog
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
class TextActionMenu(
|
||||
@@ -185,11 +185,11 @@ class TextActionMenu(
|
||||
callBack.onMenuActionFinally()
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
if (AppConfig.contentSelectSpeakMod == 0) {
|
||||
AppConfig.contentSelectSpeakMod = 1
|
||||
if (ReadConfig.contentSelectSpeakMod == 0) {
|
||||
ReadConfig.contentSelectSpeakMod = 1
|
||||
context.toastOnUi("切换为从选择的地方开始一直朗读")
|
||||
} else {
|
||||
AppConfig.contentSelectSpeakMod = 0
|
||||
ReadConfig.contentSelectSpeakMod = 0
|
||||
context.toastOnUi("切换为朗读选择内容")
|
||||
}
|
||||
true
|
||||
|
||||
@@ -4,9 +4,9 @@ import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.os.SystemClock
|
||||
import androidx.core.graphics.withClip
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
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.recordIfNeeded
|
||||
import io.legado.app.utils.themeColor
|
||||
@@ -30,7 +30,7 @@ class AutoPager(private val readView: ReadView) : Runnable {
|
||||
|
||||
fun start() {
|
||||
isRunning = true
|
||||
isEInkMode = AppConfig.isEInkMode
|
||||
isEInkMode = ReadConfig.isEInkMode
|
||||
readView.curPage.upSelectAble(false)
|
||||
if (isEInkMode) {
|
||||
readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)
|
||||
@@ -49,7 +49,7 @@ class AutoPager(private val readView: ReadView) : Runnable {
|
||||
isPausing = false
|
||||
isEInkMode = false
|
||||
readView.removeCallbacks(this)
|
||||
readView.curPage.upSelectAble(AppConfig.textSelectAble)
|
||||
readView.curPage.upSelectAble(ReadConfig.selectText)
|
||||
readView.invalidate()
|
||||
reset()
|
||||
canvasRecorder.recycle()
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.view.View
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.help.book.isOnLineTxt
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.association.OpenUrlConfirmActivity
|
||||
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.provider.ChapterProvider
|
||||
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.utils.activity
|
||||
import io.legado.app.utils.dpToPx
|
||||
@@ -40,7 +40,7 @@ import kotlin.math.min
|
||||
* 阅读内容视图
|
||||
*/
|
||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||
var selectAble = AppConfig.textSelectAble
|
||||
var selectAble = ReadConfig.selectText
|
||||
val selectedPaint by lazy {
|
||||
Paint().apply {
|
||||
color = context.getCompatColor(R.color.btn_bg_press_2)
|
||||
@@ -73,7 +73,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
//绘制图片的paint
|
||||
val imagePaint by lazy {
|
||||
Paint().apply {
|
||||
isAntiAlias = AppConfig.useAntiAlias
|
||||
isAntiAlias = ReadConfig.useAntiAlias
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
handled = true
|
||||
}
|
||||
|
||||
is ImageColumn -> when (AppConfig.clickImgWay) {
|
||||
is ImageColumn -> when (ReadConfig.clickImgWay) {
|
||||
"1" -> { //预览图片
|
||||
activity?.showDialogFragment(PhotoDialog(column.src, isBook = true))
|
||||
handled = true
|
||||
|
||||
@@ -16,13 +16,13 @@ import io.legado.app.R
|
||||
import io.legado.app.constant.AppConst.timeFormat
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.databinding.ViewBookPageBinding
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
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.TextPage
|
||||
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.config.readConfig.ReadConfig
|
||||
import io.legado.app.ui.widget.BatteryView
|
||||
import io.legado.app.utils.activity
|
||||
import io.legado.app.utils.applyNavigationBarPadding
|
||||
@@ -169,7 +169,7 @@ class PageView(
|
||||
}
|
||||
|
||||
fun upPaddingDisplayCutouts() {
|
||||
if (AppConfig.paddingDisplayCutouts) {
|
||||
if (ReadConfig.paddingDisplayCutouts) {
|
||||
binding.vwRoot.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
binding.vwRoot.setPadding(
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.widget.FrameLayout
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.PageAnim
|
||||
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.model.ReadAloud
|
||||
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.LayoutProgressListener
|
||||
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.invisible
|
||||
import io.legado.app.utils.longToastOnUi
|
||||
@@ -272,7 +272,7 @@ class ReadView(
|
||||
}
|
||||
|
||||
fun cancelSelect(clearSearchResult: Boolean = false) {
|
||||
if (isTextSelected) {
|
||||
if (isTextSelected || clearSearchResult) {
|
||||
curPage.cancelSelect(clearSearchResult)
|
||||
isTextSelected = false
|
||||
}
|
||||
@@ -398,39 +398,39 @@ class ReadView(
|
||||
when {
|
||||
isTextSelected -> Unit
|
||||
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
|
||||
click(AppConfig.clickActionMC)
|
||||
click(ReadConfig.clickActionMC)
|
||||
}
|
||||
|
||||
bcRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionBC)
|
||||
click(ReadConfig.clickActionBC)
|
||||
}
|
||||
|
||||
blRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionBL)
|
||||
click(ReadConfig.clickActionBL)
|
||||
}
|
||||
|
||||
brRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionBR)
|
||||
click(ReadConfig.clickActionBR)
|
||||
}
|
||||
|
||||
mlRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionML)
|
||||
click(ReadConfig.clickActionML)
|
||||
}
|
||||
|
||||
mrRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionMR)
|
||||
click(ReadConfig.clickActionMR)
|
||||
}
|
||||
|
||||
tlRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionTL)
|
||||
click(ReadConfig.clickActionTL)
|
||||
}
|
||||
|
||||
tcRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionTC)
|
||||
click(ReadConfig.clickActionTC)
|
||||
}
|
||||
|
||||
trRect.contains(startX, startY) -> {
|
||||
click(AppConfig.clickActionTR)
|
||||
click(ReadConfig.clickActionTR)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,7 +552,7 @@ class ReadView(
|
||||
pageDelegate = NoAnimPageDelegate(this)
|
||||
}
|
||||
}
|
||||
(pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage
|
||||
(pageDelegate as? ScrollPageDelegate)?.noAnim = ReadConfig.noAnimScrollPage
|
||||
if (upRecorder) {
|
||||
(pageDelegate as? HorizontalPageDelegate)?.upRecorder()
|
||||
autoPager.upRecorder()
|
||||
@@ -603,7 +603,7 @@ class ReadView(
|
||||
* 更新滑动距离
|
||||
*/
|
||||
fun upPageSlopSquare() {
|
||||
val pageTouchSlop = AppConfig.pageTouchSlop
|
||||
val pageTouchSlop = ReadConfig.pageTouchSlop
|
||||
this.pageSlopSquare = if (pageTouchSlop == 0) slopSquare else pageTouchSlop
|
||||
pageSlopSquare2 = this.pageSlopSquare * this.pageSlopSquare
|
||||
}
|
||||
@@ -697,7 +697,7 @@ class ReadView(
|
||||
}
|
||||
|
||||
fun invalidateTextPage() {
|
||||
if (!AppConfig.optimizeRender) {
|
||||
if (!ReadConfig.optimizeRender) {
|
||||
return
|
||||
}
|
||||
pageFactory.run {
|
||||
@@ -722,7 +722,7 @@ class ReadView(
|
||||
}
|
||||
|
||||
fun submitRenderTask() {
|
||||
if (!AppConfig.optimizeRender) {
|
||||
if (!ReadConfig.optimizeRender) {
|
||||
return
|
||||
}
|
||||
curPage.submitRenderTask()
|
||||
|
||||
@@ -9,11 +9,9 @@ import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.Shader
|
||||
import android.os.Build
|
||||
import android.text.TextPaint
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.help.PaintPool
|
||||
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.model.ReadBook
|
||||
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.TextColumn
|
||||
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.recordIfNeededThenDraw
|
||||
import io.legado.app.utils.dpToPx
|
||||
@@ -77,7 +76,7 @@ data class TextLine(
|
||||
var textPage: TextPage = emptyTextPage
|
||||
var isLeftLine = true
|
||||
val useUnderline: Boolean
|
||||
get() = AppConfig.useUnderline
|
||||
get() = ReadConfig.useUnderline
|
||||
|
||||
fun addColumn(column: BaseColumn) {
|
||||
if (column !is TextColumn) {
|
||||
@@ -160,7 +159,7 @@ data class TextLine(
|
||||
}
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
if (AppConfig.optimizeRender) {
|
||||
if (ReadConfig.optimizeRender) {
|
||||
canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) {
|
||||
drawTextLine(view, this)
|
||||
}
|
||||
@@ -236,7 +235,7 @@ data class TextLine(
|
||||
val paint = ChapterProvider.contentPaint
|
||||
paint.color = ReadBookConfig.durConfig.curUnderlineColor()
|
||||
paint.strokeWidth = ReadBookConfig.underlineHeight.toFloat()
|
||||
paint.pathEffect = if (dottedLine && !AppConfig.isEInkMode)
|
||||
paint.pathEffect = if (dottedLine && !ReadConfig.isEInkMode)
|
||||
ChapterProvider.dashEffect
|
||||
else
|
||||
null
|
||||
@@ -557,7 +556,7 @@ data class TextLine(
|
||||
}
|
||||
|
||||
fun checkFastDraw(): Boolean {
|
||||
if (!AppConfig.optimizeRender || exceed || !onlyTextColumn || textPage.isMsgPage) {
|
||||
if (!ReadConfig.optimizeRender || exceed || !onlyTextColumn || textPage.isMsgPage) {
|
||||
return false
|
||||
}
|
||||
if (wordSpacing != 0f && (!atLeastApi26 || !wordSpacingWorking)) {
|
||||
|
||||
@@ -9,13 +9,13 @@ import androidx.annotation.Keep
|
||||
import androidx.core.graphics.withTranslation
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.PaintPool
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
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.column.TextBaseColumn
|
||||
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.config.readConfig.ReadConfig
|
||||
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
|
||||
import io.legado.app.utils.canvasrecorder.recordIfNeeded
|
||||
import io.legado.app.utils.dpToPx
|
||||
@@ -299,7 +299,7 @@ data class TextPage(
|
||||
}
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
|
||||
if (AppConfig.optimizeRender) {
|
||||
if (ReadConfig.optimizeRender) {
|
||||
render(view)
|
||||
canvas.withTranslation(0f, relativeOffset) {
|
||||
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.BookChapter
|
||||
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.dottedBase
|
||||
import io.legado.app.help.config.ReadBookConfig.dottedRatio
|
||||
import io.legado.app.model.ReadBook
|
||||
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.dpToPx
|
||||
import io.legado.app.utils.isContentScheme
|
||||
@@ -190,7 +190,7 @@ object ChapterProvider {
|
||||
displayTitle.splitNotBlank("\n").forEach { text ->
|
||||
setTypeText(
|
||||
book, absStartX, durY,
|
||||
if (AppConfig.enableReview) text + reviewChar else text,
|
||||
if (ReadConfig.enableReview) text + reviewChar else text,
|
||||
textPages,
|
||||
stringBuilder,
|
||||
titlePaint,
|
||||
@@ -279,7 +279,7 @@ object ChapterProvider {
|
||||
if (text.isNotBlank()) {
|
||||
setTypeText(
|
||||
book, absStartX, durY,
|
||||
if (AppConfig.enableReview) text + reviewChar else text,
|
||||
if (ReadConfig.enableReview) text + reviewChar else text,
|
||||
textPages,
|
||||
stringBuilder,
|
||||
contentPaint,
|
||||
@@ -918,7 +918,7 @@ object ChapterProvider {
|
||||
}
|
||||
|
||||
else -> {
|
||||
when (AppConfig.systemTypefaces) {
|
||||
when (ReadConfig.systemTypefaces) {
|
||||
1 -> Typeface.SERIF
|
||||
2 -> Typeface.MONOSPACE
|
||||
else -> Typeface.SANS_SERIF
|
||||
@@ -1013,7 +1013,7 @@ object ChapterProvider {
|
||||
tPaint.setFontVariationSettings("'wght' ${ReadBookConfig.titleBold}")
|
||||
tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx()
|
||||
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
|
||||
}
|
||||
if (ReadBookConfig.textItalic) {
|
||||
@@ -1036,7 +1036,7 @@ object ChapterProvider {
|
||||
cPaint.setFontVariationSettings("'wght' ${ReadBookConfig.textBold}")
|
||||
cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx()
|
||||
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
|
||||
}
|
||||
if (ReadBookConfig.textItalic) {
|
||||
@@ -1086,7 +1086,7 @@ object ChapterProvider {
|
||||
* 更新绘制尺寸
|
||||
*/
|
||||
fun upLayout() {
|
||||
when (AppConfig.doublePageHorizontal) {
|
||||
when (ReadConfig.doubleHorizontalPage) {
|
||||
"0" -> doublePage = false
|
||||
"1" -> doublePage = true
|
||||
"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.BookHelp
|
||||
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.coroutine.Coroutine
|
||||
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.srcReplaceCharC
|
||||
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.StringUtils
|
||||
import io.legado.app.utils.dpToPx
|
||||
@@ -141,7 +141,7 @@ class TextChapterLayout(
|
||||
private val useZhLayout = ReadBookConfig.useZhLayout
|
||||
private val isMiddleTitle = ReadBookConfig.isMiddleTitle
|
||||
private val textFullJustify = ReadBookConfig.textFullJustify
|
||||
private val adaptSpecialStyle = AppConfig.adaptSpecialStyle
|
||||
private val adaptSpecialStyle = ReadConfig.adaptSpecialStyle
|
||||
private val pageAnim = book.getPageAnim()
|
||||
private val titleSegType = ReadBookConfig.titleSegType
|
||||
private val titleSegDistance = ReadBookConfig.titleSegDistance
|
||||
@@ -490,7 +490,7 @@ class TextChapterLayout(
|
||||
text = sb.toString()
|
||||
if (text.isNotBlank()) {
|
||||
wordCount += text.replace(noWordCountRegex,"").length
|
||||
val textToType = if (AppConfig.enableReview) text + reviewChar else text
|
||||
val textToType = if (ReadConfig.enableReview) text + reviewChar else text
|
||||
setTypeText(
|
||||
book,
|
||||
textToType,
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.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.text.AppText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val CHINESE_CONVERT_ID = -1L
|
||||
|
||||
@Composable
|
||||
fun EffectiveReplacesSheet(
|
||||
@@ -26,12 +44,26 @@ fun EffectiveReplacesSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
onOpenReplaceEditor: (id: Long, pattern: String?) -> 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()
|
||||
}
|
||||
|
||||
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(
|
||||
show = show,
|
||||
@@ -44,18 +76,80 @@ fun EffectiveReplacesSheet(
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
items(effectiveRules, key = { it.id }) { rule ->
|
||||
Text(
|
||||
text = rule.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onOpenReplaceEditor(rule.id, rule.pattern)
|
||||
items(items, key = { it.id }) { rule ->
|
||||
if (rule.id !in disabledIds) {
|
||||
NormalCard(
|
||||
onClick = {
|
||||
if (rule.id == CHINESE_CONVERT_ID) {
|
||||
onNavigateToTextEffects()
|
||||
} else {
|
||||
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 = {
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseZhLayout(it)))
|
||||
},
|
||||
onShowBrightnessViewChange = {
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.ShowBrightnessView(it)))
|
||||
},
|
||||
onUseUnderlineChange = {
|
||||
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseUnderlineGlobal(it)))
|
||||
},
|
||||
@@ -170,7 +167,6 @@ private fun ScreenSettings(
|
||||
onTextBottomJustifyChange: (Boolean) -> Unit,
|
||||
onAdaptSpecialStyleChange: (Boolean) -> Unit,
|
||||
onUseZhLayoutChange: (Boolean) -> Unit,
|
||||
onShowBrightnessViewChange: (Boolean) -> Unit,
|
||||
onUseUnderlineChange: (Boolean) -> Unit,
|
||||
) {
|
||||
val screenDirectionEntries = stringArrayResource(R.array.screen_direction_title)
|
||||
@@ -232,11 +228,6 @@ private fun ScreenSettings(
|
||||
checked = preferences.useZhLayout,
|
||||
onCheckedChange = onUseZhLayoutChange,
|
||||
)
|
||||
TinySwitchSettingItem(
|
||||
title = stringResource(R.string.show_brightness_view),
|
||||
checked = preferences.showBrightnessView,
|
||||
onCheckedChange = onShowBrightnessViewChange,
|
||||
)
|
||||
TinySwitchSettingItem(
|
||||
title = stringResource(R.string.use_underline),
|
||||
checked = preferences.useUnderline,
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.ReadBookIntent
|
||||
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 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
|
||||
fun ReadStyleContent(
|
||||
onOpenPaddingConfig: () -> Unit,
|
||||
@@ -87,6 +33,7 @@ fun ReadStyleContent(
|
||||
onOpenTextTitle: () -> Unit,
|
||||
onOpenFontSelect: () -> Unit,
|
||||
onToggleDayNight: () -> Unit,
|
||||
onPageChanged: (Int) -> Unit = {},
|
||||
readMenuCustomIcons: Map<String, String> = emptyMap(),
|
||||
bottomBarButtons: List<ReadBookButtonConfigItem> = emptyList(),
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -100,6 +47,7 @@ fun ReadStyleContent(
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
currentPage = page
|
||||
onPageChanged(page)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,31 @@ private fun GlobalMenuTab(
|
||||
|
||||
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 {
|
||||
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.config.readConfig.ReadConfig
|
||||
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.TinyColorSettingItem
|
||||
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_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
|
||||
fun ReadStyleTextTitleContent(
|
||||
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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -81,8 +78,6 @@ fun SearchContentScreen(
|
||||
onBack: () -> Unit,
|
||||
viewModel: SearchContentViewModel = koinViewModel()
|
||||
) {
|
||||
val activity = LocalActivity.current
|
||||
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val searchQuery by viewModel.searchQuery.collectAsState()
|
||||
val replaceEnabled by viewModel.replaceEnabled.collectAsState()
|
||||
@@ -259,13 +254,8 @@ fun SearchContentScreen(
|
||||
result = result,
|
||||
isCurrentChapter = result.chapterIndex == durChapterIndex,
|
||||
onClick = {
|
||||
viewModel.onSearchResultClick(result) { key ->
|
||||
val intent = Intent().apply {
|
||||
putExtra("key", key)
|
||||
putExtra("index", index)
|
||||
}
|
||||
activity?.setResult(Activity.RESULT_OK, intent)
|
||||
activity?.finish()
|
||||
if (viewModel.onSearchResultClick(result)) {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package io.legado.app.ui.book.searchContent
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.SearchContentHistory
|
||||
import io.legado.app.data.repository.BookRepository
|
||||
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.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -42,15 +38,13 @@ sealed interface SearchContentState {
|
||||
|
||||
|
||||
class SearchContentViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
val bookUrl: String,
|
||||
initialSearchWord: String?,
|
||||
private val searchResultIndex: Int,
|
||||
private val bookRepository: BookRepository,
|
||||
private val searchContentRepository: SearchContentRepository
|
||||
) : 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 ?: "")
|
||||
val searchQuery = _searchQuery.asStateFlow()
|
||||
|
||||
@@ -189,12 +183,18 @@ class SearchContentViewModel(
|
||||
hasAutoScrolled = true
|
||||
}
|
||||
|
||||
fun onSearchResultClick(searchResult: SearchResult, onSuccess: (key: Long) -> Unit) {
|
||||
fun onSearchResultClick(searchResult: SearchResult): Boolean {
|
||||
stopSearch()
|
||||
postEvent(EventBus.SEARCH_RESULT, uiState.value.searchResults)
|
||||
val key = System.currentTimeMillis()
|
||||
IntentData.put("searchResult$key", searchResult)
|
||||
IntentData.put("searchResultList$key", uiState.value.searchResults)
|
||||
onSuccess(key)
|
||||
val results = uiState.value.searchResults
|
||||
val index = results.indexOf(searchResult)
|
||||
if (index < 0) return false
|
||||
SearchContentResult.emitResult(
|
||||
SearchContentResult.Result(
|
||||
searchResults = results,
|
||||
index = index,
|
||||
query = searchResult.query
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,139 @@
|
||||
package io.legado.app.ui.config.readConfig
|
||||
|
||||
import io.legado.app.BuildConfig
|
||||
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.themeConfig.ThemeConfig
|
||||
import io.legado.app.utils.isNightMode
|
||||
import io.legado.app.utils.sysConfiguration
|
||||
|
||||
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(
|
||||
PreferKey.tocUiUseReplace,
|
||||
false
|
||||
@@ -82,7 +211,22 @@ object ReadConfig {
|
||||
|
||||
var showBrightnessView by prefDelegate(
|
||||
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(
|
||||
@@ -180,6 +324,73 @@ object ReadConfig {
|
||||
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(
|
||||
PreferKey.prevKeys,
|
||||
""
|
||||
|
||||
@@ -14,7 +14,9 @@ data class ReadConfigUiState(
|
||||
val textBottomJustify: Boolean = true,
|
||||
val adaptSpecialStyle: Boolean = true,
|
||||
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 readSliderMode: String = "0",
|
||||
val doubleHorizontalPage: String = "0",
|
||||
@@ -53,7 +55,8 @@ sealed interface ReadConfigIntent {
|
||||
data class TextBottomJustifyChanged(val value: Boolean) : ReadConfigIntent
|
||||
data class AdaptSpecialStyleChanged(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 ReadSliderModeChanged(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.widget.components.AppScaffold
|
||||
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.DropdownListSettingItem
|
||||
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.topbar.GlassMediumFlexibleTopAppBar
|
||||
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 org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@@ -173,14 +173,28 @@ fun ReadConfigScreen(
|
||||
}
|
||||
)
|
||||
|
||||
SwitchSettingItem(
|
||||
DropdownListSettingItem(
|
||||
title = stringResource(R.string.show_brightness_view),
|
||||
checked = state.showBrightnessView,
|
||||
onCheckedChange = {
|
||||
selectedValue = state.showBrightnessView,
|
||||
displayEntries = stringArrayResource(R.array.brightness_bar_mode_title),
|
||||
entryValues = stringArrayResource(R.array.brightness_bar_mode_value),
|
||||
onValueChange = {
|
||||
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(
|
||||
title = stringResource(R.string.use_underline),
|
||||
checked = state.useUnderline,
|
||||
|
||||
@@ -92,6 +92,10 @@ class ReadConfigViewModel(
|
||||
readSettingsRepository.setShowBrightnessView(intent.value)
|
||||
}
|
||||
|
||||
is ReadConfigIntent.BrightnessVwPosChanged -> {
|
||||
readSettingsRepository.setBrightnessVwPos(intent.value)
|
||||
}
|
||||
|
||||
is ReadConfigIntent.UseUnderlineChanged -> {
|
||||
readSettingsRepository.setUseUnderline(intent.value)
|
||||
}
|
||||
@@ -209,6 +213,8 @@ class ReadConfigViewModel(
|
||||
adaptSpecialStyle = adaptSpecialStyle,
|
||||
useZhLayout = useZhLayout,
|
||||
showBrightnessView = showBrightnessView,
|
||||
brightnessVwPos = brightnessVwPos,
|
||||
brightnessAuto = brightnessAuto,
|
||||
useUnderline = useUnderline,
|
||||
readSliderMode = readSliderMode,
|
||||
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.SearchScreen
|
||||
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.config.ConfigNavScreen
|
||||
import io.legado.app.ui.config.backupConfig.BackupConfigScreen
|
||||
import io.legado.app.ui.config.coverConfig.CoverConfigScreen
|
||||
import io.legado.app.ui.config.customTheme.CustomThemeScreen
|
||||
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.readConfig.ReadConfigScreen
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
|
||||
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.rss.article.MainRouteRssSort
|
||||
import io.legado.app.ui.rss.article.RssSortRouteScreen
|
||||
@@ -69,6 +71,7 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
fun MainActivity.mainEntryProvider(
|
||||
@@ -294,6 +297,15 @@ fun MainActivity.mainEntryProvider(
|
||||
host = controller,
|
||||
controller = controller,
|
||||
onEffectsReady = { effectsReady.complete(Unit) },
|
||||
onOpenSearch = { word, bookUrl ->
|
||||
onNavigateToRoute(
|
||||
MainRouteSearchContent(
|
||||
bookUrl = bookUrl,
|
||||
searchWord = word,
|
||||
searchResultIndex = readBookViewModel.uiState.value.searchResultIndex
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
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 ->
|
||||
val searchViewModel = koinViewModel<SearchViewModel>()
|
||||
|
||||
|
||||
@@ -97,6 +97,13 @@ data class MainRouteExploreShow(
|
||||
val exploreUrl: String?,
|
||||
) : MainRoute
|
||||
|
||||
@Serializable
|
||||
data class MainRouteSearchContent(
|
||||
val bookUrl: String,
|
||||
val searchWord: String? = null,
|
||||
val searchResultIndex: Int = 0,
|
||||
) : MainRoute
|
||||
|
||||
@Serializable
|
||||
data object MainRouteAbout : MainRoute
|
||||
|
||||
@@ -118,6 +125,7 @@ object MainRouteConst {
|
||||
const val ROUTE_BOOK_CACHE_MANAGE = "book/cache/manage"
|
||||
const val ROUTE_READ_BOOK = "book/read"
|
||||
const val ROUTE_SEARCH = "search"
|
||||
const val ROUTE_SEARCH_CONTENT = "book/searchContent"
|
||||
const val ROUTE_BOOK_INFO = "book/info"
|
||||
const val ROUTE_EXPLORE_SHOW = "explore/show"
|
||||
const val ROUTE_RSS_SORT = "rss/sort"
|
||||
|
||||
@@ -60,6 +60,10 @@ object MainNavigator {
|
||||
}
|
||||
}
|
||||
|
||||
is MainRouteSearchContent -> {
|
||||
backStack.add(route)
|
||||
}
|
||||
|
||||
is MainRouteSearch -> {
|
||||
if (
|
||||
currentRoute == MainRouteHome ||
|
||||
|
||||
@@ -11,7 +11,7 @@ import android.text.StaticLayout
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.ColorInt
|
||||
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.recordIfNeededThenDraw
|
||||
import io.legado.app.utils.dpToPx
|
||||
@@ -75,7 +75,7 @@ class BatteryViewOrgin @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (AppConfig.optimizeRender) {
|
||||
if (ReadConfig.optimizeRender) {
|
||||
canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
|
||||
super.onDraw(this)
|
||||
drawBattery(this)
|
||||
@@ -119,4 +119,4 @@ class BatteryViewOrgin @JvmOverloads constructor(
|
||||
canvasRecorder?.invalidate()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(LegadoTheme.colorScheme.surfaceVariant)
|
||||
.background(LegadoTheme.colorScheme.surface)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.height(48.dp)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
android.widget.TextView(ctx).apply {
|
||||
@@ -178,7 +178,7 @@ private fun FontItem(
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
tint = LegadoTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.legado.app.utils.canvasrecorder
|
||||
|
||||
import android.os.Build
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.ui.config.readConfig.ReadConfig
|
||||
|
||||
object CanvasRecorderFactory {
|
||||
|
||||
@@ -12,7 +12,7 @@ object CanvasRecorderFactory {
|
||||
// issue 3868
|
||||
fun create(locked: Boolean = false): CanvasRecorder {
|
||||
val impl = when {
|
||||
!AppConfig.optimizeRender -> CanvasRecorderImpl()
|
||||
!ReadConfig.optimizeRender -> CanvasRecorderImpl()
|
||||
atLeastApi29 -> CanvasRecorderApi29Impl()
|
||||
atLeastApi24 -> CanvasRecorderApi23Impl()
|
||||
else -> CanvasRecorderImpl()
|
||||
|
||||
Reference in New Issue
Block a user