Merge pull request #4021 from xuanyue202/simulated_reading

feat(app): 增加模拟追读功能
This commit is contained in:
Horis
2024-07-10 15:10:06 +08:00
committed by GitHub
24 changed files with 388 additions and 23 deletions

View File

@@ -16,18 +16,18 @@ import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isEpub
import io.legado.app.help.book.isImage
import io.legado.app.help.book.isLocal
import io.legado.app.help.book.isPdf
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.fromJsonObject
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import java.nio.charset.Charset
import java.time.LocalDate
import java.time.Period.between
import kotlin.math.max
import kotlin.math.min
@@ -160,9 +160,21 @@ data class Book(
@IgnoredOnParcel
val lastChapterIndex get() = totalChapterNum - 1
// 根据当前日期计算章节总数
fun simulatedTotalChapterNum(): Int {
if (config.readSimulating) {
val currentDate = LocalDate.now()
val daysPassed = between(this.config.startDate, currentDate).days + 1
// 计算当前应该解锁到哪一章
val chaptersToUnlock =
max(0, (config.startChapter ?: 0) + (daysPassed * config.dailyChapters))
return min(this.totalChapterNum, chaptersToUnlock)
} else return this.totalChapterNum
}
fun getRealAuthor() = author.replace(AppPattern.authorRegex, "")
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
fun getUnreadChapterNum() = max(simulatedTotalChapterNum() - durChapterIndex - 1, 0)
fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
@@ -257,6 +269,46 @@ data class Book(
return config.splitLongChapter
}
// readSimulating 的 setter 和 getter
fun setReadSimulating(readSimulating: Boolean) {
config.readSimulating = readSimulating
}
fun getReadSimulating(): Boolean {
return config.readSimulating
}
// startDate 的 setter 和 getter
fun setStartDate(startDate: LocalDate?) {
config.startDate = startDate
}
fun getStartDate(): LocalDate? {
if (!config.readSimulating || config.startDate == null) {
return LocalDate.now()
}
return config.startDate
}
// startChapter 的 setter 和 getter
fun setStartChapter(startChapter: Int) {
config.startChapter = startChapter
}
fun getStartChapter(): Int {
if (config.readSimulating) return config.startChapter ?: 0
return this.durChapterIndex
}
// dailyChapters 的 setter 和 getter
fun setDailyChapters(dailyChapters: Int) {
config.dailyChapters = dailyChapters
}
fun getDailyChapters(): Int {
return config.dailyChapters
}
fun getDelTag(tag: Long): Boolean {
return config.delTag and tag == tag
}
@@ -385,7 +437,11 @@ data class Book(
var useReplaceRule: Boolean? = null,// 正文使用净化替换规则
var delTag: Long = 0L,//去除标签
var ttsEngine: String? = null,
var splitLongChapter: Boolean = true
var splitLongChapter: Boolean = true,
var readSimulating: Boolean = false,
var startDate: LocalDate? = null,
var startChapter: Int? = null, // 用户设置的起始章节
var dailyChapters: Int = 3 // 用户设置的每日更新章节数
) : Parcelable
class Converters {

View File

@@ -165,7 +165,7 @@ object AudioPlay {
fun next(context: Context) {
book?.let { book ->
if (book.durChapterIndex + 1 < book.totalChapterNum) {
if (book.durChapterIndex + 1 < book.simulatedTotalChapterNum()) {
book.durChapterIndex += 1
book.durChapterPos = 0
durChapterIndex = book.durChapterIndex

View File

@@ -151,7 +151,7 @@ object ReadBook : CoroutineScope by MainScope() {
fun setProgress(progress: BookProgress) {
if (progress.durChapterIndex < chapterSize &&
(durChapterIndex != progress.durChapterIndex
|| durChapterPos != progress.durChapterPos)
|| durChapterPos != progress.durChapterPos)
) {
durChapterIndex = progress.durChapterIndex
durChapterPos = progress.durChapterPos
@@ -229,7 +229,7 @@ object ReadBook : CoroutineScope by MainScope() {
}
fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean {
if (durChapterIndex < chapterSize - 1) {
if (durChapterIndex < (book?.simulatedTotalChapterNum()?: chapterSize) - 1) {
durChapterPos = 0
durChapterIndex++
prevTextChapter?.cancelLayout()
@@ -590,6 +590,8 @@ object ReadBook : CoroutineScope by MainScope() {
}
}
}
return@async
}.onError {
AppLog.put("ChapterProvider ERROR", it)
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")

View File

@@ -113,11 +113,11 @@ object BookChapterList {
if (chapterList.isEmpty()) {
throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))
}
//去重
if (!reverse) {
chapterList.reverse()
}
coroutineContext.ensureActive()
//去重
val lh = LinkedHashSet(chapterList)
val list = ArrayList(lh)
if (!book.getReverseToc()) {
@@ -145,7 +145,8 @@ object BookChapterList {
}
val replaceRules = ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
book.latestChapterTitle =
list.last().getDisplayTitle(replaceRules, book.getUseReplaceRule())
list.getOrElse(book.simulatedTotalChapterNum() - 1) {
list.last() }.getDisplayTitle(replaceRules, book.getUseReplaceRule())
book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }
.getDisplayTitle(replaceRules, book.getUseReplaceRule())
if (book.totalChapterNum < list.size) {

View File

@@ -36,13 +36,21 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.*
import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.dpToPx
import io.legado.app.utils.invisible
import io.legado.app.utils.observeEvent
import io.legado.app.utils.observeEventSticky
import io.legado.app.utils.sendToClip
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import splitties.views.onLongClick
import java.util.*
import java.util.Locale
/**
* 音频播放
@@ -108,12 +116,14 @@ class AudioPlayActivity :
R.id.menu_change_source -> AudioPlay.book?.let {
showDialogFragment(ChangeBookSourceDialog(it.name, it.author))
}
R.id.menu_login -> AudioPlay.bookSource?.let {
startActivity<SourceLoginActivity> {
putExtra("type", "bookSource")
putExtra("key", it.bookSourceUrl)
}
}
R.id.menu_wake_lock -> AppConfig.audioPlayUseWakeLock = !AppConfig.audioPlayUseWakeLock
R.id.menu_copy_audio_url -> sendToClip(AudioPlayService.url)
R.id.menu_edit_source -> AudioPlay.bookSource?.let {
@@ -121,6 +131,7 @@ class AudioPlayActivity :
putExtra("sourceUrl", it.bookSourceUrl)
}
}
R.id.menu_log -> showDialogFragment<AppLogDialog>()
}
return super.onCompatOptionsItemSelected(item)
@@ -258,7 +269,8 @@ class AudioPlayActivity :
binding.tvSubTitle.text = it
AudioPlay.book?.let { book ->
binding.ivSkipPrevious.isEnabled = book.durChapterIndex > 0
binding.ivSkipNext.isEnabled = book.durChapterIndex < book.totalChapterNum - 1
binding.ivSkipNext.isEnabled =
book.durChapterIndex < book.simulatedTotalChapterNum() - 1
}
}
observeEventSticky<Int>(EventBus.AUDIO_SIZE) {

View File

@@ -251,7 +251,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
appDb.bookChapterDao.insert(*it.toTypedArray())
if (book.isSameNameAuthor(ReadBook.book)) {
ReadBook.book = book
ReadBook.chapterSize = book.totalChapterNum
ReadBook.chapterSize = book.simulatedTotalChapterNum()
}
}
bookData.postValue(book)

View File

@@ -1,6 +1,7 @@
package io.legado.app.ui.book.read
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
@@ -20,6 +21,7 @@ import io.legado.app.constant.PreferKey
import io.legado.app.databinding.ActivityBookReadBinding
import io.legado.app.databinding.DialogDownloadChoiceBinding
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogSimulatedReadingBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.LocalConfig
import io.legado.app.help.config.ReadBookConfig
@@ -48,6 +50,8 @@ import io.legado.app.utils.setNavigationBarColorAuto
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import java.time.LocalDate
import java.time.format.DateTimeFormatter
/**
* 阅读界面
@@ -57,6 +61,7 @@ abstract class BaseReadBookActivity :
override val binding by viewBinding(ActivityBookReadBinding::inflate)
override val viewModel by viewModels<ReadBookViewModel>()
var bottomDialog = 0
set(value) {
if (field != value) {
@@ -304,6 +309,59 @@ abstract class BaseReadBookActivity :
}
}
fun showSimulatedReading() {
val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
ReadBook.book?.let { book ->
alert(titleResource = R.string.simulated_reading) {
val alertBinding = DialogSimulatedReadingBinding.inflate(layoutInflater).apply {
root.setBackgroundColor(root.context.backgroundColor)
srEnabled.isChecked = book.getReadSimulating()
editStart.setText(book.getStartChapter().toString())
editNum.setText(book.getDailyChapters().toString())
startDate.setText(book.getStartDate()?.format(dateFormatter))
startDate.isFocusable = false // 设置为false不允许获得焦点
startDate.isCursorVisible = false // 不显示光标
startDate.setOnClickListener {
// 获取当前日期
val localStartDate = LocalDate.parse(startDate.text)
// 创建 DatePickerDialog
val datePickerDialog = DatePickerDialog(
root.context,
{ _, yy, mm, dayOfMonth ->
// 使用Java 8的日期和时间API来格式化日期
val date = LocalDate.of(yy, mm + 1, dayOfMonth) // Java 8的LocalDate月份从1开始
val formattedDate = date.format(dateFormatter)
startDate.setText(formattedDate)
}, localStartDate.year, localStartDate.monthValue - 1, localStartDate.dayOfMonth
)
datePickerDialog.show()
}
}
customView { alertBinding.root }
yesButton {
alertBinding.run {
val start = editStart.text!!.toString().let {
if (it.isEmpty()) 0 else it.toInt()
}
val num = editNum.text!!.toString().let {
if (it.isEmpty()) book.totalChapterNum else it.toInt()
}
val enabled = srEnabled.isChecked
val date = startDate.text!!.toString().let {
if (it.isEmpty()) LocalDate.now() else LocalDate.parse(it, dateFormatter)
}
book.setStartDate(date)
book.setDailyChapters(num)
book.setStartChapter(start)
book.setReadSimulating(enabled)
book.save()
}
}
noButton()
}
}
}
fun showCharsetConfig() {
alert(R.string.set_charset) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {

View File

@@ -473,6 +473,7 @@ class ReadBookActivity : BaseReadBookActivity(),
R.id.menu_download -> showDownloadDialog()
R.id.menu_add_bookmark -> addBookmark()
R.id.menu_simulated_reading -> showSimulatedReading()
R.id.menu_edit_content -> showDialogFragment(ContentEditDialog())
R.id.menu_update_toc -> ReadBook.book?.let {
if (it.isEpub) {

View File

@@ -233,7 +233,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
&& progress.durChapterPos < book.durChapterPos)
) {
alertSync?.invoke(progress)
} else {
} else if (progress.durChapterIndex < book.simulatedTotalChapterNum()) {
ReadBook.setProgress(progress)
AppLog.put("自动同步阅读进度成功《${book.name}${progress.durChapterTitle}")
}

View File

@@ -708,7 +708,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
}
override fun hasNextChapter(): Boolean {
return ReadBook.durChapterIndex < ReadBook.chapterSize - 1
return ReadBook.durChapterIndex < ((ReadBook.book?.simulatedTotalChapterNum()
?: ReadBook.chapterSize) - 1)
}
override fun hasPrevChapter(): Boolean {

View File

@@ -118,7 +118,10 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
lifecycleScope.launch {
withContext(IO) {
when {
searchKey.isNullOrBlank() -> appDb.bookChapterDao.getChapterList(viewModel.bookUrl)
searchKey.isNullOrBlank() -> appDb.bookChapterDao.getChapterList(
viewModel.bookUrl
).take(book?.simulatedTotalChapterNum() ?: Int.MAX_VALUE)
else -> appDb.bookChapterDao.search(viewModel.bookUrl, searchKey)
}
}.let {
@@ -163,9 +166,10 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
override fun openChapter(bookChapter: BookChapter) {
activity?.run {
setResult(RESULT_OK, Intent()
.putExtra("index", bookChapter.index)
.putExtra("chapterChanged", bookChapter.index != durChapterIndex)
setResult(
RESULT_OK, Intent()
.putExtra("index", bookChapter.index)
.putExtra("chapterChanged", bookChapter.index != durChapterIndex)
)
finish()
}

View File

@@ -192,7 +192,7 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
ReadBook.book?.let { readBook ->
if (readBook == book) {
ReadBook.book = book
ReadBook.chapterSize = book.totalChapterNum
ReadBook.chapterSize = book.simulatedTotalChapterNum()
ReadBook.upMsg(null)
ReadBook.loadContent(resetPageOffset = true)
}

View File

@@ -163,7 +163,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
appDb.bookChapterDao.insert(*toc.toTypedArray())
if (book.isSameNameAuthor(ReadBook.book)) {
ReadBook.book = book
ReadBook.chapterSize = book.totalChapterNum
ReadBook.chapterSize = book.simulatedTotalChapterNum()
}
addDownload(source, book)
}.onFailure {

View File

@@ -83,4 +83,4 @@ abstract class BaseBooksAdapter<VB : ViewBinding>(context: Context) :
fun openBookInfo(book: Book)
fun isUpdate(bookUrl: String): Boolean
}
}
}

View File

@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="2dp"
android:text="@string/switch_on"
android:textColor="@color/primaryText"
android:textSize="16sp"
tools:ignore="RtlHardcoded"
android:minWidth="88dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="53dp"
android:layout_height="match_parent"
android:gravity="center_vertical">
<androidx.appcompat.widget.SwitchCompat
android:layout_height="wrap_content"
android:id="@+id/sr_enabled"
android:layout_weight="1"
android:gravity="start|center_vertical"
style="@style/Widget.AppCompat.CompoundButton.Switch"
android:layout_width="match_parent"
android:width="20dp" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="65dp"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="start|center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/start_from"
android:textColor="@color/primaryText"
android:textSize="16sp"
android:gravity="center_vertical"
android:minWidth="100dp" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="match_parent"
android:layout_height="49dp"
android:inputType="none"
android:ems="10"
android:id="@+id/start_date"
android:hint="Select date"
android:layout_weight="1"
android:clickable="true"
android:textSize="16sp"
android:layout_marginRight="68dp" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="63dp"
android:gravity="center_horizontal|left"
android:padding="16dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="100dp"
android:layout_height="match_parent"
android:minWidth="100dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="2dp"
android:text="@string/start_chapter"
android:textColor="@color/primaryText"
android:textSize="16sp"
tools:ignore="RtlHardcoded" />
</LinearLayout>
<EditText
android:id="@+id/edit_start"
android:layout_width="47dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_edit"
android:hint="@string/start"
android:importantForAutofill="no"
android:inputType="number"
android:lines="1"
android:maxLength="5"
android:minWidth="60dp"
android:paddingLeft="5dp"
android:paddingTop="4dp"
android:paddingRight="5dp"
android:paddingBottom="4dp"
android:textColor="@color/primaryText"
android:textCursorDrawable="@drawable/shape_text_cursor"
android:textSize="14sp"
tools:ignore="TouchTargetSizeCheck,TextContrastCheck" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="25dp"
android:layout_marginRight="5dp"
android:text="@string/daily_chapters"
android:textColor="@color/primaryText"
android:textSize="16sp" />
<EditText
android:id="@+id/edit_num"
android:layout_width="42dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_edit"
android:hint="3"
android:importantForAutofill="no"
android:inputType="number"
android:lines="1"
android:maxLength="5"
android:minWidth="60dp"
android:paddingLeft="5dp"
android:paddingTop="4dp"
android:paddingRight="5dp"
android:paddingBottom="4dp"
android:textColor="@color/primaryText"
android:textCursorDrawable="@drawable/shape_text_cursor"
android:textSize="14sp"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck,TextContrastCheck"
android:textAlignment="center" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -70,6 +70,11 @@
android:title="@string/reverse_content"
app:showAsAction="never" />
<item
android:id="@+id/menu_simulated_reading"
android:title="@string/simulated_reading"
app:showAsAction="never" />
<item
android:id="@+id/menu_enable_replace"
android:checkable="true"

View File

@@ -683,7 +683,7 @@
<string name="alouding_disable">The selected text cannot be spoken in full text speech</string>
<string name="read_body_to_lh">Extend to cutout</string>
<string name="toc_updateing">Updating Chapters</string>
<string name="media_button_on_exit_title">Headset buttons are always available</string>
<string name="media_button_on_exit_summary">Headset buttons are available even exit the app.</string>
<string name="contributors">Contributors</string>
@@ -1148,4 +1148,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">Lectura Ficticia</string>
<string name="switch_on">Enc/Apag</string>
<string name="start_from">Comenzar</string>
<string name="daily_chapters">Cap. al día</string>
<string name="start_chapter">Cap. inicio</string>
</resources>

View File

@@ -1151,4 +1151,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">模擬追読</string>
<string name="switch_on">スイッチ</string>
<string name="start_from">開始日</string>
<string name="daily_chapters">日更の章数</string>
<string name="start_chapter">開始の章</string>
</resources>

View File

@@ -1151,4 +1151,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">Leitura Fingida</string>
<string name="switch_on">Lig/Desl</string>
<string name="start_from">Começar</string>
<string name="daily_chapters">Cap. por dia</string>
<string name="start_chapter">Início</string>
</resources>

View File

@@ -1147,4 +1147,9 @@ Còn </string>
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">Đọc giả lập</string>
<string name="switch_on">Bật</string>
<string name="start_from">Bắt đầu</string>
<string name="daily_chapters">Chương/ngày</string>
<string name="start_chapter">Chương đầu</string>
</resources>

View File

@@ -1148,4 +1148,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">模擬追讀</string>
<string name="switch_on">開關</string>
<string name="start_from">起始日期</string>
<string name="daily_chapters">日更章數</string>
<string name="start_chapter">開始篇章</string>
</resources>

View File

@@ -1150,4 +1150,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">模擬追讀</string>
<string name="switch_on">開關</string>
<string name="start_from">起始日期</string>
<string name="daily_chapters">日更章數</string>
<string name="start_chapter">開始篇章</string>
</resources>

View File

@@ -1150,4 +1150,9 @@
<string name="keep_swipe_tip">继续滑动以加载下一章…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">模拟追读</string>
<string name="switch_on">开关</string>
<string name="start_from">开始日期</string>
<string name="daily_chapters">日更章数</string>
<string name="start_chapter">起始章节</string>
</resources>

View File

@@ -1151,4 +1151,9 @@
<string name="keep_swipe_tip">Keep swiping to load the next chapter…</string>
<string name="enable_optimize_render">启用绘制优化</string>
<string name="ignore_battery_permission_rationale">阅读需要请求后台权限以保持服务正常运行</string>
<string name="simulated_reading">Simulated Reading</string>
<string name="switch_on">Switch</string>
<string name="start_from">Start from</string>
<string name="daily_chapters">Daily Chapters</string>
<string name="start_chapter">Start Chapter</string>
</resources>