diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9c7545b12..905dd7d57 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -113,7 +113,8 @@ "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")", "Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")", "PowerShell(grep *)", - "Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")" + "Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")", + "Bash(git revert *)" ] } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5895e8ac0..11f98360c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -234,11 +234,6 @@ android:name=".ui.book.toc.TocActivity" android:enableOnBackInvokedCallback="true" android:launchMode="singleTop" /> - - + SearchContentViewModel( + bookUrl = route.bookUrl, + initialSearchWord = route.searchWord, + searchResultIndex = route.searchResultIndex, + bookRepository = get(), + searchContentRepository = get() + ) + } } diff --git a/app/src/main/java/io/legado/app/help/AppWebDav.kt b/app/src/main/java/io/legado/app/help/AppWebDav.kt index 84800342d..8f5f89ce8 100644 --- a/app/src/main/java/io/legado/app/help/AppWebDav.kt +++ b/app/src/main/java/io/legado/app/help/AppWebDav.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/help/GlideImageGetter.kt b/app/src/main/java/io/legado/app/help/GlideImageGetter.kt index 6cb1cea59..c0b69fa79 100644 --- a/app/src/main/java/io/legado/app/help/GlideImageGetter.kt +++ b/app/src/main/java/io/legado/app/help/GlideImageGetter.kt @@ -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() } diff --git a/app/src/main/java/io/legado/app/help/WebDavManager.kt b/app/src/main/java/io/legado/app/help/WebDavManager.kt index cd1dfb957..c2639e045 100644 --- a/app/src/main/java/io/legado/app/help/WebDavManager.kt +++ b/app/src/main/java/io/legado/app/help/WebDavManager.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/help/book/BookHelp.kt b/app/src/main/java/io/legado/app/help/book/BookHelp.kt index 9d79cfd18..fb994c5bf 100644 --- a/app/src/main/java/io/legado/app/help/book/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/book/BookHelp.kt @@ -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() //获取需要保留章节的图片信息 @@ -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) diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index c42b19713..02ba1bb2f 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -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) { diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index 83ba5a273..5527adeab 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -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) { diff --git a/app/src/main/java/io/legado/app/help/config/ReadStyleResolver.kt b/app/src/main/java/io/legado/app/help/config/ReadStyleResolver.kt index 353767cde..8a5664fc7 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadStyleResolver.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadStyleResolver.kt @@ -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( diff --git a/app/src/main/java/io/legado/app/model/ReadAloud.kt b/app/src/main/java/io/legado/app/model/ReadAloud.kt index 8c1a61398..dead400b1 100644 --- a/app/src/main/java/io/legado/app/model/ReadAloud.kt +++ b/app/src/main/java/io/legado/app/model/ReadAloud.kt @@ -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 { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index cc35dbd69..2bafc8bd9 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/model/ReadManga.kt b/app/src/main/java/io/legado/app/model/ReadManga.kt index 1520ff0cd..58f6a751f 100644 --- a/app/src/main/java/io/legado/app/model/ReadManga.kt +++ b/app/src/main/java/io/legado/app/model/ReadManga.kt @@ -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() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 780ab6074..3b95c8455 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -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 { diff --git a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt index b6795d1c1..cce9ca409 100644 --- a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt @@ -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 sureNewProgress(progress) }) } } @@ -480,7 +479,7 @@ class ReadMangaActivity : VMBaseActivity 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 { diff --git a/app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt index e4979ee98..d1d184626 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt @@ -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 { @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt index f177160ed..eb731c67c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt @@ -196,6 +196,10 @@ data class ReadMenuConfig( val readMenuCustomIcons: ImmutableMap = persistentMapOf(), val titleBarButtons: ImmutableList = persistentListOf(), val bottomBarButtons: ImmutableList = 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() + } + + data class BrightnessVwPos(val value: String) : ConfigUpdate { + override val actions = emptySet() + } + + data class BrightnessAuto(val value: Boolean) : ConfigUpdate { override val actions = emptySet() } data class UseUnderlineGlobal(val value: Boolean) : ConfigUpdate { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt index 20b1bdf82..6178045b1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt @@ -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 + } + } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt index 0d45c2732..a8f53d000 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt @@ -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)) + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt index ac0aad890..a843108fc 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt @@ -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() } 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$key") - val searchResultList = IntentData.get>("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()) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt index a1d4dc107..b0caf67b5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookSearchBar.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookSearchBar.kt index 0593368fa..3d5e53f20 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookSearchBar.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookSearchBar.kt @@ -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") - } - } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 65df6279a..5ff1e3945 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -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 { 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) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt index 2bb14deff..e9e01d010 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt index 164d6c164..d61caf727 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt @@ -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() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 915c1c5f4..9f686b6fa 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 1b22fa616..ff9ab11c6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -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( diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 2774a9e10..a31bca765 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -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() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index ca4e5a428..70d9df857 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -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)) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 561df6456..0c3a973e0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -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) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 27b747617..ef709ffaf 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -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" -> { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt index 01d87b664..b9975da26 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/EffectiveReplacesSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/EffectiveReplacesSheet.kt index 12b108659..7759fb08d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/EffectiveReplacesSheet.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/EffectiveReplacesSheet.kt @@ -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()) } 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() + } + } } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt index 243b8204f..c0162d031 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt index 2cb8d2a7e..20435348f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt @@ -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 = emptyMap(), - bottomBarButtons: List = 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 = emptyMap(), bottomBarButtons: List = emptyList(), modifier: Modifier = Modifier, @@ -100,6 +47,7 @@ fun ReadStyleContent( LaunchedEffect(pagerState) { snapshotFlow { pagerState.currentPage }.collect { page -> currentPage = page + onPageChanged(page) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt index 5a5ddafba..e5cae7682 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt @@ -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) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt index bafccfceb..24001eedc 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/TextTitleSheet.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt deleted file mode 100644 index a010ce75e..000000000 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt +++ /dev/null @@ -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() } - ) - } - } -} diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentResult.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentResult.kt new file mode 100644 index 000000000..c996e410b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentResult.kt @@ -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, + val index: Int, + val query: String, + ) + + private val _results = MutableSharedFlow(replay = 1) + val results = _results.asSharedFlow() + + fun emitResult(result: Result) { + _results.tryEmit(result) + } + + fun resetReplayCache() { + _results.resetReplayCache() + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt index f3672607b..57f924014 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentScreen.kt @@ -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() } } ) diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt index 7e28c808a..b7ebc2465 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt @@ -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("bookUrl") ?: "" - private val initialSearchWord: String? = savedStateHandle.get("searchWord") - private val searchResultIndex: Int = savedStateHandle.get("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 } } diff --git a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt index c41800a3c..202e76aa1 100644 --- a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt +++ b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt @@ -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, "" diff --git a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt index 90190ce80..df1d831d9 100644 --- a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt +++ b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt index 832741c7b..32970f322 100644 --- a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt +++ b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt index 3f1fe9a0d..7d939ed43 100644 --- a/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt index edc4f2594..545ce7448 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt @@ -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 { route -> + val viewModel = koinViewModel( + parameters = { parametersOf(route) } + ) + SearchContentScreen( + viewModel = viewModel, + onBack = { onNavigateBack() }, + ) + } + entry { route -> val searchViewModel = koinViewModel() diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt index 4de1d10d4..a35e80da3 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt @@ -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" diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt b/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt index b73997706..641febb00 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavigator.kt @@ -60,6 +60,10 @@ object MainNavigator { } } + is MainRouteSearchContent -> { + backStack.add(route) + } + is MainRouteSearch -> { if ( currentRoute == MainRouteHome || diff --git a/app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt b/app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt index 3aa74c3f9..3940f9f39 100644 --- a/app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt +++ b/app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt @@ -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() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppVerticalSlider.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppVerticalSlider.kt new file mode 100644 index 000000000..36847c76c --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppVerticalSlider.kt @@ -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 = 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, + ) + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/FontSelectGrid.kt b/app/src/main/java/io/legado/app/ui/widget/components/FontSelectGrid.kt index cea27be18..e15dda904 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/FontSelectGrid.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/FontSelectGrid.kt @@ -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), ) } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt index f6c550372..0f630bcf5 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt @@ -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() diff --git a/app/src/main/res/layout/activity_search_content.xml b/app/src/main/res/layout/activity_search_content.xml deleted file mode 100644 index 8293b420a..000000000 --- a/app/src/main/res/layout/activity_search_content.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 6593850a7..dfe919370 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -907,6 +907,13 @@ 格式不对 错误 显示亮度调节控件 + 关闭 + 横排 + 竖排 + 亮度条位置 + 左侧 + 右侧 + 跟随系统 语言 导入订阅源 您的支持是我更新的动力 diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml index ebb0acf40..32d2b7dd4 100644 --- a/app/src/main/res/values/array_values.xml +++ b/app/src/main/res/values/array_values.xml @@ -73,6 +73,17 @@ chapter + + 0 + 1 + 2 + + + + 0 + 1 + + auto zh diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 67ab13d29..f29c23c4e 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -147,6 +147,17 @@ @string/adjust_chapter_index + + @string/brightness_bar_off + @string/brightness_bar_horizontal + @string/brightness_bar_vertical + + + + @string/brightness_bar_left + @string/brightness_bar_right + + iconMain iconWhite diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6dd9e280..d470697f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -932,6 +932,13 @@ Wrong format Error Show Brightness Widget + Off + Horizontal + Vertical + Brightness Bar Position + Left + Right + Follow System Language Import rss source Your donation makes this app better diff --git a/app/src/main/res/xml/pref_config_read.xml b/app/src/main/res/xml/pref_config_read.xml index cf1b38d6a..6158b7f53 100644 --- a/app/src/main/res/xml/pref_config_read.xml +++ b/app/src/main/res/xml/pref_config_read.xml @@ -93,10 +93,12 @@ android:title="@string/use_zh_layout" app:isBottomBackground="true" /> -