merge latest master

pull/1251/head
Jason Yao 3 years ago
commit 3d779f3fb0
  1. 1
      app/.gitignore
  2. 12
      app/build.gradle
  3. 1022
      app/schemas/io.legado.app.data.AppDatabase/1.json
  4. 1176
      app/schemas/io.legado.app.data.AppDatabase/10.json
  5. 1182
      app/schemas/io.legado.app.data.AppDatabase/11.json
  6. 1188
      app/schemas/io.legado.app.data.AppDatabase/12.json
  7. 1194
      app/schemas/io.legado.app.data.AppDatabase/13.json
  8. 1194
      app/schemas/io.legado.app.data.AppDatabase/14.json
  9. 1200
      app/schemas/io.legado.app.data.AppDatabase/15.json
  10. 1226
      app/schemas/io.legado.app.data.AppDatabase/16.json
  11. 1226
      app/schemas/io.legado.app.data.AppDatabase/17.json
  12. 1258
      app/schemas/io.legado.app.data.AppDatabase/18.json
  13. 1265
      app/schemas/io.legado.app.data.AppDatabase/19.json
  14. 1028
      app/schemas/io.legado.app.data.AppDatabase/2.json
  15. 1271
      app/schemas/io.legado.app.data.AppDatabase/20.json
  16. 1283
      app/schemas/io.legado.app.data.AppDatabase/21.json
  17. 1277
      app/schemas/io.legado.app.data.AppDatabase/22.json
  18. 1283
      app/schemas/io.legado.app.data.AppDatabase/23.json
  19. 1324
      app/schemas/io.legado.app.data.AppDatabase/24.json
  20. 1368
      app/schemas/io.legado.app.data.AppDatabase/25.json
  21. 1392
      app/schemas/io.legado.app.data.AppDatabase/26.json
  22. 1392
      app/schemas/io.legado.app.data.AppDatabase/27.json
  23. 1404
      app/schemas/io.legado.app.data.AppDatabase/28.json
  24. 1410
      app/schemas/io.legado.app.data.AppDatabase/29.json
  25. 1036
      app/schemas/io.legado.app.data.AppDatabase/3.json
  26. 1485
      app/schemas/io.legado.app.data.AppDatabase/30.json
  27. 1422
      app/schemas/io.legado.app.data.AppDatabase/31.json
  28. 1422
      app/schemas/io.legado.app.data.AppDatabase/32.json
  29. 1417
      app/schemas/io.legado.app.data.AppDatabase/33.json
  30. 1423
      app/schemas/io.legado.app.data.AppDatabase/34.json
  31. 1429
      app/schemas/io.legado.app.data.AppDatabase/35.json
  32. 1093
      app/schemas/io.legado.app.data.AppDatabase/4.json
  33. 1093
      app/schemas/io.legado.app.data.AppDatabase/5.json
  34. 1131
      app/schemas/io.legado.app.data.AppDatabase/6.json
  35. 1131
      app/schemas/io.legado.app.data.AppDatabase/7.json
  36. 1157
      app/schemas/io.legado.app.data.AppDatabase/8.json
  37. 1164
      app/schemas/io.legado.app.data.AppDatabase/9.json
  38. 50
      app/src/androidTest/java/io/legado/app/MigrationTest.kt
  39. 2
      app/src/main/AndroidManifest.xml
  40. 6
      app/src/main/assets/updateLog.md
  41. 2
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  42. 273
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  43. 283
      app/src/main/java/io/legado/app/data/DatabaseMigrations.kt
  44. 2
      app/src/main/java/io/legado/app/data/entities/Book.kt
  45. 14
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  46. 17
      app/src/main/java/io/legado/app/data/entities/rule/LoginRule.kt
  47. 2
      app/src/main/java/io/legado/app/help/AppConfig.kt
  48. 148
      app/src/main/java/io/legado/app/model/ReadBook.kt
  49. 35
      app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt
  50. 2
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  51. 2
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  52. 2
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  53. 1
      app/src/main/java/io/legado/app/service/help/CacheBook.kt
  54. 2
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  55. 2
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  56. 2
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt
  57. 4
      app/src/main/java/io/legado/app/ui/book/login/SourceLoginActivity.kt
  58. 83
      app/src/main/java/io/legado/app/ui/book/login/SourceLoginDialog.kt
  59. 10
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  60. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt
  61. 65
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  62. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  63. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  64. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  65. 2
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  66. 2
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  67. 2
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  68. 2
      app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt
  69. 2
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  70. 2
      app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt
  71. 11
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  72. 2
      app/src/main/java/io/legado/app/ui/widget/dialog/PhotoDialog.kt
  73. 15
      app/src/main/java/io/legado/app/utils/EncoderUtils.kt
  74. 23
      app/src/main/res/layout/dialog_login.xml
  75. 4
      app/src/main/res/menu/source_login.xml
  76. 4
      app/src/main/res/values-es-rES/strings.xml
  77. 4
      app/src/main/res/values-ja-rJP/strings.xml
  78. 4
      app/src/main/res/values-pt-rBR/strings.xml
  79. 4
      app/src/main/res/values-zh-rHK/strings.xml
  80. 4
      app/src/main/res/values-zh-rTW/strings.xml
  81. 4
      app/src/main/res/values-zh/strings.xml
  82. 5
      app/src/main/res/values/strings.xml
  83. 8
      app/src/main/res/xml/pref_config_aloud.xml

1
app/.gitignore vendored

@ -1,2 +1 @@
/build /build
/schemas

@ -14,6 +14,10 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3'
kotlinOptions {
jvmTarget = "11"
}
signingConfigs { signingConfigs {
if (project.hasProperty("RELEASE_STORE_FILE")) { if (project.hasProperty("RELEASE_STORE_FILE")) {
myConfig { myConfig {
@ -102,10 +106,10 @@ android {
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11
} }
kotlinOptions { sourceSets {
jvmTarget = "11" // Adds exported schema location as test app assets.
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
} }
buildToolsVersion '30.0.3'
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
//options.compilerArgs << "-Xlint:unchecked" //options.compilerArgs << "-Xlint:unchecked"
} }
@ -166,7 +170,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
//room //room
def room_version = '2.3.0' def room_version = '2.4.0-alpha04'
implementation("androidx.room:room-runtime:$room_version") implementation("androidx.room:room-runtime:$room_version")
implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-ktx:$room_version")
kapt("androidx.room:room-compiler:$room_version") kapt("androidx.room:room-compiler:$room_version")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,50 @@
package io.legado.app
import androidx.room.Room
import androidx.room.migration.Migration
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import io.legado.app.data.AppDatabase
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
@RunWith(AndroidJUnit4::class)
class MigrationTest {
private val TEST_DB = "migration-test"
private val ALL_MIGRATIONS = arrayOf<Migration>(
)
@Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
@Throws(IOException::class)
fun migrateAll() {
// Create earliest version of the database.
helper.createDatabase(TEST_DB, 30).apply {
close()
}
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
AppDatabase::class.java,
TEST_DB
).addMigrations(*ALL_MIGRATIONS)
.build().apply {
openHelper.writableDatabase
close()
}
}
}

@ -293,7 +293,7 @@
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<!-- 书源登录 --> <!-- 书源登录 -->
<activity <activity
android:name=".ui.login.SourceLoginActivity" android:name="io.legado.app.ui.book.login.SourceLoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" /> android:hardwareAccelerated="true" />
<!-- 阅读记录 --> <!-- 阅读记录 -->

@ -12,6 +12,12 @@
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
* 关于最近版本有时候界面没有数据的问题是因为把LiveData组件换成了谷歌推荐的Flow组件导致的问题,正在查找解决办法 * 关于最近版本有时候界面没有数据的问题是因为把LiveData组件换成了谷歌推荐的Flow组件导致的问题,正在查找解决办法
**2021/08/18**
1. 翻到最后一章时自动更新最新章节
2. 朗读添加媒体按键配置
3. 其它一些优化
**2021/08/13** **2021/08/13**
1. web传书可以使用 1. web传书可以使用

@ -10,11 +10,11 @@ import io.legado.app.data.entities.Book
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor import io.legado.app.help.ContentProcessor
import io.legado.app.help.ImageLoader import io.legado.app.help.ImageLoader
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.EpubFile import io.legado.app.model.localBook.EpubFile
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.localBook.UmdFile import io.legado.app.model.localBook.UmdFile
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.ui.widget.image.CoverImageView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking

@ -4,13 +4,10 @@ import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.androidId
import io.legado.app.data.dao.* import io.legado.app.data.dao.*
import io.legado.app.data.entities.* import io.legado.app.data.entities.*
import io.legado.app.help.AppConfig
import splitties.init.appCtx import splitties.init.appCtx
import java.util.* import java.util.*
@ -19,13 +16,13 @@ val appDb by lazy {
} }
@Database( @Database(
version = 35,
exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class, RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class], RuleSub::class],
version = 34,
exportSchema = true
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -54,14 +51,7 @@ abstract class AppDatabase : RoomDatabase() {
fun createDatabase(context: Context) = fun createDatabase(context: Context) =
Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.addMigrations( .addMigrations(*DatabaseMigrations.migrations)
migration_10_11, migration_11_12, migration_12_13, migration_13_14,
migration_14_15, migration_15_17, migration_17_18, migration_18_19,
migration_19_20, migration_20_21, migration_21_22, migration_22_23,
migration_23_24, migration_24_25, migration_25_26, migration_26_27,
migration_27_28, migration_28_29, migration_29_30, migration_30_31,
migration_31_32, migration_32_33, migration_33_34
)
.allowMainThreadQueries() .allowMainThreadQueries()
.addCallback(dbCallback) .addCallback(dbCallback)
.build() .build()
@ -74,269 +64,28 @@ abstract class AppDatabase : RoomDatabase() {
override fun onOpen(db: SupportSQLiteDatabase) { override fun onOpen(db: SupportSQLiteDatabase) {
db.execSQL( db.execSQL(
"""insert into book_groups(groupId, groupName, 'order', show) select ${AppConst.bookGroupAllId}, '全部', -10, 1 """insert into book_groups(groupId, groupName, 'order', show)
select ${AppConst.bookGroupAllId}, '全部', -10, 1
where not exists (select * from book_groups where groupId = ${AppConst.bookGroupAllId})""" where not exists (select * from book_groups where groupId = ${AppConst.bookGroupAllId})"""
) )
db.execSQL( db.execSQL(
"""insert into book_groups(groupId, groupName, 'order', show) select ${AppConst.bookGroupLocalId}, '本地', -9, 1 """insert into book_groups(groupId, groupName, 'order', show)
select ${AppConst.bookGroupLocalId}, '本地', -9, 1
where not exists (select * from book_groups where groupId = ${AppConst.bookGroupLocalId})""" where not exists (select * from book_groups where groupId = ${AppConst.bookGroupLocalId})"""
) )
db.execSQL( db.execSQL(
"""insert into book_groups(groupId, groupName, 'order', show) select ${AppConst.bookGroupAudioId}, '音频', -8, 1 """insert into book_groups(groupId, groupName, 'order', show)
select ${AppConst.bookGroupAudioId}, '音频', -8, 1
where not exists (select * from book_groups where groupId = ${AppConst.bookGroupAudioId})""" where not exists (select * from book_groups where groupId = ${AppConst.bookGroupAudioId})"""
) )
db.execSQL( db.execSQL(
"""insert into book_groups(groupId, groupName, 'order', show) select ${AppConst.bookGroupNoneId}, '未分组', -7, 1 """insert into book_groups(groupId, groupName, 'order', show)
select ${AppConst.bookGroupNoneId}, '未分组', -7, 1
where not exists (select * from book_groups where groupId = ${AppConst.bookGroupNoneId})""" where not exists (select * from book_groups where groupId = ${AppConst.bookGroupNoneId})"""
) )
if (AppConfig.isGooglePlay) {
db.execSQL(
"""
delete from rssSources where sourceUrl = 'https://github.com/gedoor/legado/releases'
"""
)
}
}
}
private val migration_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE txtTocRules")
database.execSQL(
"""CREATE TABLE txtTocRules(id INTEGER NOT NULL,
name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL,
enable INTEGER NOT NULL, PRIMARY KEY (id))"""
)
}
}
private val migration_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD style TEXT ")
}
}
private val migration_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0 ")
}
}
private val migration_13_14 = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT,
`customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL,
`latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL,
`totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL,
`durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL,
`originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))"""
)
database.execSQL("INSERT INTO books_new select * from books ")
database.execSQL("DROP TABLE books")
database.execSQL("ALTER TABLE books_new RENAME TO books")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
}
}
private val migration_14_15 = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE bookmarks ADD bookAuthor TEXT NOT NULL DEFAULT ''")
}
}
private val migration_15_17 = object : Migration(15, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))")
}
}
private val migration_17_18 = object : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))")
}
}
private val migration_18_19 = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL,
PRIMARY KEY(`androidId`, `bookName`))"""
)
database.execSQL("INSERT INTO readRecordNew(androidId, bookName, readTime) select '${androidId}' as androidId, bookName, readTime from readRecord")
database.execSQL("DROP TABLE readRecord")
database.execSQL("ALTER TABLE readRecordNew RENAME TO readRecord")
}
}
private val migration_19_20 = object : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE book_sources ADD bookSourceComment TEXT")
}
}
private val migration_20_21 = object : Migration(20, 21) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE book_groups ADD show INTEGER NOT NULL DEFAULT 1")
}
}
private val migration_21_22 = object : Migration(21, 22) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT,
`coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL,
`group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL,
`lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL,
`durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL,
`order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))"""
)
database.execSQL(
"""INSERT INTO books_new select `bookUrl`, `tocUrl`, `origin`, `originName`, `name`, `author`, `kind`, `customTag`, `coverUrl`,
`customCoverUrl`, `intro`, `customIntro`, `charset`, `type`, `group`, `latestChapterTitle`, `latestChapterTime`, `lastCheckTime`,
`lastCheckCount`, `totalChapterNum`, `durChapterTitle`, `durChapterIndex`, `durChapterPos`, `durChapterTime`, `wordCount`, `canUpdate`,
`order`, `originOrder`, `variable`, null
from books"""
)
database.execSQL("DROP TABLE books")
database.execSQL("ALTER TABLE books_new RENAME TO books")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
}
}
private val migration_22_23 = object : Migration(22, 23) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chapters ADD baseUrl TEXT NOT NULL DEFAULT ''")
}
}
private val migration_23_24 = object : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `caches` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `caches` (`key`)")
}
}
private val migration_24_25 = object : Migration(24, 25) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `sourceSubs`
(`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL,
PRIMARY KEY(`id`))"""
)
}
}
private val migration_25_26 = object : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `ruleSubs` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL,
`customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))"""
)
database.execSQL(" insert into `ruleSubs` select *, 0, 0 from `sourceSubs` ")
database.execSQL("DROP TABLE `sourceSubs`")
} }
} }
private val migration_26_27 = object : Migration(26, 27) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(" ALTER TABLE rssSources ADD singleUrl INTEGER NOT NULL DEFAULT 0 ")
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `bookmarks1` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL,
`bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL,
`bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))"""
)
database.execSQL(
"""insert into `bookmarks1`
select `time`, `bookUrl`, `bookName`, `bookAuthor`, `chapterIndex`, `pageIndex`, `chapterName`, '', `content`
from bookmarks"""
)
database.execSQL(" DROP TABLE `bookmarks` ")
database.execSQL(" ALTER TABLE bookmarks1 RENAME TO bookmarks ")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `bookmarks` (`time`)")
}
}
private val migration_27_28 = object : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssArticles ADD variable TEXT")
database.execSQL("ALTER TABLE rssStars ADD variable TEXT")
}
}
private val migration_28_29 = object : Migration(28, 29) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD sourceComment TEXT")
}
}
private val migration_29_30 = object : Migration(29, 30) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chapters ADD `startFragmentId` TEXT")
database.execSQL("ALTER TABLE chapters ADD `endFragmentId` TEXT")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `epubChapters`
(`bookUrl` TEXT NOT NULL, `href` TEXT NOT NULL, `parentHref` TEXT,
PRIMARY KEY(`bookUrl`, `href`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )
"""
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_epubChapters_bookUrl` ON `epubChapters` (`bookUrl`)")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `epubChapters` (`bookUrl`, `href`)")
}
}
private val migration_30_31 = object : Migration(30, 31) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE readRecord RENAME TO readRecord1")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `readRecord` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))
"""
)
database.execSQL("insert into readRecord (deviceId, bookName, readTime) select androidId, bookName, readTime from readRecord1")
}
}
private val migration_31_32 = object : Migration(31, 32) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE `epubChapters`")
}
}
private val migration_32_33 = object : Migration(32, 33) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE bookmarks RENAME TO bookmarks_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `bookmarks` (`time` INTEGER NOT NULL,
`bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL,
`chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL,
`content` TEXT NOT NULL, PRIMARY KEY(`time`))
"""
)
database.execSQL(
"""
CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `bookmarks` (`bookName`, `bookAuthor`)
"""
)
database.execSQL(
"""
insert into bookmarks (time, bookName, bookAuthor, chapterIndex, chapterPos, chapterName, bookText, content)
select time, ifNull(b.name, bookName) bookName, ifNull(b.author, bookAuthor) bookAuthor,
chapterIndex, chapterPos, chapterName, bookText, content from bookmarks_old o
left join books b on o.bookUrl = b.bookUrl
"""
)
}
}
private val migration_33_34 = object : Migration(33, 34) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `book_groups` ADD `cover` TEXT")
}
}
} }
} }

@ -0,0 +1,283 @@
package io.legado.app.data
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.constant.AppConst
object DatabaseMigrations {
val migrations: Array<Migration> by lazy {
arrayOf(
migration_10_11,
migration_11_12,
migration_12_13,
migration_13_14,
migration_14_15,
migration_15_17,
migration_17_18,
migration_18_19,
migration_19_20,
migration_20_21,
migration_21_22,
migration_22_23,
migration_23_24,
migration_24_25,
migration_25_26,
migration_26_27,
migration_27_28,
migration_28_29,
migration_29_30,
migration_30_31,
migration_31_32,
migration_32_33,
migration_33_34,
migration_34_35
)
}
private val migration_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE txtTocRules")
database.execSQL(
"""CREATE TABLE txtTocRules(id INTEGER NOT NULL,
name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL,
enable INTEGER NOT NULL, PRIMARY KEY (id))"""
)
}
}
private val migration_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD style TEXT ")
}
}
private val migration_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0 ")
}
}
private val migration_13_14 = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT,
`customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL,
`latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL,
`totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL,
`durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL,
`originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))"""
)
database.execSQL("INSERT INTO books_new select * from books ")
database.execSQL("DROP TABLE books")
database.execSQL("ALTER TABLE books_new RENAME TO books")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
}
}
private val migration_14_15 = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE bookmarks ADD bookAuthor TEXT NOT NULL DEFAULT ''")
}
}
private val migration_15_17 = object : Migration(15, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))")
}
}
private val migration_17_18 = object : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))")
}
}
private val migration_18_19 = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL,
PRIMARY KEY(`androidId`, `bookName`))"""
)
database.execSQL("INSERT INTO readRecordNew(androidId, bookName, readTime) select '${AppConst.androidId}' as androidId, bookName, readTime from readRecord")
database.execSQL("DROP TABLE readRecord")
database.execSQL("ALTER TABLE readRecordNew RENAME TO readRecord")
}
}
private val migration_19_20 = object : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE book_sources ADD bookSourceComment TEXT")
}
}
private val migration_20_21 = object : Migration(20, 21) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE book_groups ADD show INTEGER NOT NULL DEFAULT 1")
}
}
private val migration_21_22 = object : Migration(21, 22) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,
`originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT,
`coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL,
`group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL,
`lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL,
`durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL,
`order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))"""
)
database.execSQL(
"""INSERT INTO books_new select `bookUrl`, `tocUrl`, `origin`, `originName`, `name`, `author`, `kind`, `customTag`, `coverUrl`,
`customCoverUrl`, `intro`, `customIntro`, `charset`, `type`, `group`, `latestChapterTitle`, `latestChapterTime`, `lastCheckTime`,
`lastCheckCount`, `totalChapterNum`, `durChapterTitle`, `durChapterIndex`, `durChapterPos`, `durChapterTime`, `wordCount`, `canUpdate`,
`order`, `originOrder`, `variable`, null
from books"""
)
database.execSQL("DROP TABLE books")
database.execSQL("ALTER TABLE books_new RENAME TO books")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) ")
}
}
private val migration_22_23 = object : Migration(22, 23) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chapters ADD baseUrl TEXT NOT NULL DEFAULT ''")
}
}
private val migration_23_24 = object : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `caches` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `caches` (`key`)")
}
}
private val migration_24_25 = object : Migration(24, 25) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `sourceSubs`
(`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL,
PRIMARY KEY(`id`))"""
)
}
}
private val migration_25_26 = object : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `ruleSubs` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL,
`customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))"""
)
database.execSQL(" insert into `ruleSubs` select *, 0, 0 from `sourceSubs` ")
database.execSQL("DROP TABLE `sourceSubs`")
}
}
private val migration_26_27 = object : Migration(26, 27) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(" ALTER TABLE rssSources ADD singleUrl INTEGER NOT NULL DEFAULT 0 ")
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `bookmarks1` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL,
`bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL,
`bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))"""
)
database.execSQL(
"""insert into `bookmarks1`
select `time`, `bookUrl`, `bookName`, `bookAuthor`, `chapterIndex`, `pageIndex`, `chapterName`, '', `content`
from bookmarks"""
)
database.execSQL(" DROP TABLE `bookmarks` ")
database.execSQL(" ALTER TABLE bookmarks1 RENAME TO bookmarks ")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `bookmarks` (`time`)")
}
}
private val migration_27_28 = object : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssArticles ADD variable TEXT")
database.execSQL("ALTER TABLE rssStars ADD variable TEXT")
}
}
private val migration_28_29 = object : Migration(28, 29) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE rssSources ADD sourceComment TEXT")
}
}
private val migration_29_30 = object : Migration(29, 30) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chapters ADD `startFragmentId` TEXT")
database.execSQL("ALTER TABLE chapters ADD `endFragmentId` TEXT")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `epubChapters`
(`bookUrl` TEXT NOT NULL, `href` TEXT NOT NULL, `parentHref` TEXT,
PRIMARY KEY(`bookUrl`, `href`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )
"""
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_epubChapters_bookUrl` ON `epubChapters` (`bookUrl`)")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `epubChapters` (`bookUrl`, `href`)")
}
}
private val migration_30_31 = object : Migration(30, 31) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE readRecord RENAME TO readRecord1")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `readRecord` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))
"""
)
database.execSQL("insert into readRecord (deviceId, bookName, readTime) select androidId, bookName, readTime from readRecord1")
}
}
private val migration_31_32 = object : Migration(31, 32) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE `epubChapters`")
}
}
private val migration_32_33 = object : Migration(32, 33) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE bookmarks RENAME TO bookmarks_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `bookmarks` (`time` INTEGER NOT NULL,
`bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL,
`chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL,
`content` TEXT NOT NULL, PRIMARY KEY(`time`))
"""
)
database.execSQL(
"""
CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `bookmarks` (`bookName`, `bookAuthor`)
"""
)
database.execSQL(
"""
insert into bookmarks (time, bookName, bookAuthor, chapterIndex, chapterPos, chapterName, bookText, content)
select time, ifNull(b.name, bookName) bookName, ifNull(b.author, bookAuthor) bookAuthor,
chapterIndex, chapterPos, chapterName, bookText, content from bookmarks_old o
left join books b on o.bookUrl = b.bookUrl
"""
)
}
}
private val migration_33_34 = object : Migration(33, 34) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `book_groups` ADD `cover` TEXT")
}
}
private val migration_34_35 = object : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `book_sources` ADD `concurrentRate` TEXT")
}
}
}

@ -6,7 +6,7 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject

@ -29,6 +29,7 @@ data class BookSource(
var bookSourceUrl: String = "", // 地址,包括 http/https var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频 var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频
var bookUrlPattern: String? = null, // 详情页url正则 var bookUrlPattern: String? = null, // 详情页url正则
var concurrentRate: String? = null, //并发率
var customOrder: Int = 0, // 手动排序编号 var customOrder: Int = 0, // 手动排序编号
var enabled: Boolean = true, // 是否启用 var enabled: Boolean = true, // 是否启用
var enabledExplore: Boolean = true, // 启用发现 var enabledExplore: Boolean = true, // 启用发现
@ -189,6 +190,19 @@ data class BookSource(
private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
class Converters { class Converters {
@TypeConverter
fun loginRuleTString(loginRule: LoginRule?): String = GSON.toJson(loginRule)
@TypeConverter
fun stringToLoginRule(json: String?): LoginRule? {
json ?: return null
return if (json.isJsonObject()) {
GSON.fromJsonObject(json)
} else {
LoginRule(url = json)
}
}
@TypeConverter @TypeConverter
fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule) fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule)

@ -5,7 +5,16 @@ import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class LoginRule( data class LoginRule(
val ui: HashMap<String, String>? = null, var ui: List<RowUi>? = null,
val url: String? = null, var url: String? = null,
val checkJs: String? = null var checkJs: String? = null
) : Parcelable ) : Parcelable {
@Parcelize
data class RowUi(
var name: String,
var type: String,
) : Parcelable
}

@ -225,7 +225,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val autoChangeSource: Boolean val autoChangeSource: Boolean
get() = appCtx.getPrefBoolean(PreferKey.autoChangeSource, true) get() = appCtx.getPrefBoolean(PreferKey.autoChangeSource, true)
val changeSourceLoadInfo get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadToc) val changeSourceLoadInfo get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadInfo)
val changeSourceLoadToc get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadToc) val changeSourceLoadToc get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadToc)

@ -1,4 +1,4 @@
package io.legado.app.service.help package io.legado.app.model
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.github.liuyueyi.quick.transfer.ChineseUtils import com.github.liuyueyi.quick.transfer.ChineseUtils
@ -10,13 +10,15 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.BookWebDav import io.legado.app.help.storage.BookWebDav
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.CacheBook
import io.legado.app.service.help.ReadAloud
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.ImageProvider import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.utils.msg import io.legado.app.utils.msg
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.math.max import kotlin.math.max
@ -44,7 +46,7 @@ object ReadBook {
var readStartTime: Long = System.currentTimeMillis() var readStartTime: Long = System.currentTimeMillis()
fun resetData(book: Book) { fun resetData(book: Book) {
this.book = book ReadBook.book = book
readRecord.bookName = book.name readRecord.bookName = book.name
readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0 readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0
durChapterIndex = book.durChapterIndex durChapterIndex = book.durChapterIndex
@ -113,8 +115,8 @@ object ReadBook {
} }
fun upMsg(msg: String?) { fun upMsg(msg: String?) {
if (this.msg != msg) { if (ReadBook.msg != msg) {
this.msg = msg ReadBook.msg = msg
callBack?.upContent() callBack?.upContent()
} }
} }
@ -132,25 +134,39 @@ object ReadBook {
prevTextChapter = curTextChapter prevTextChapter = curTextChapter
curTextChapter = nextTextChapter curTextChapter = nextTextChapter
nextTextChapter = null nextTextChapter = null
book?.let { if (curTextChapter == null) {
if (curTextChapter == null) { loadContent(durChapterIndex, upContent, false)
loadContent(durChapterIndex, upContent, false) } else if (upContent) {
} else if (upContent) { callBack?.upContent()
callBack?.upContent()
}
loadContent(durChapterIndex.plus(1), upContent, false)
Coroutine.async {
val maxChapterIndex =
min(chapterSize - 1, durChapterIndex + AppConfig.preDownloadNum)
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000)
download(i)
}
}
} }
loadContent(durChapterIndex.plus(1), upContent, false)
saveRead() saveRead()
callBack?.upView() callBack?.upView()
curPageChanged() curPageChanged()
Coroutine.async {
//预下载
val maxChapterIndex =
min(chapterSize - 1, durChapterIndex + AppConfig.preDownloadNum)
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000)
download(i)
}
book?.let { book ->
//最后一章时检查更新
if (durChapterPos == 0 && durChapterIndex == chapterSize - 1) {
webBook?.getChapterList(this, book)
?.onSuccess(IO) { cList ->
if (book.bookUrl == ReadBook.book?.bookUrl
&& cList.size > chapterSize
) {
appDb.bookChapterDao.insert(*cList.toTypedArray())
chapterSize = cList.size
nextTextChapter ?: loadContent(1)
}
}
}
}
}
return true return true
} else { } else {
return false return false
@ -167,24 +183,23 @@ object ReadBook {
nextTextChapter = curTextChapter nextTextChapter = curTextChapter
curTextChapter = prevTextChapter curTextChapter = prevTextChapter
prevTextChapter = null prevTextChapter = null
book?.let { if (curTextChapter == null) {
if (curTextChapter == null) { loadContent(durChapterIndex, upContent, false)
loadContent(durChapterIndex, upContent, false) } else if (upContent) {
} else if (upContent) { callBack?.upContent()
callBack?.upContent()
}
loadContent(durChapterIndex.minus(1), upContent, false)
Coroutine.async {
val minChapterIndex = max(0, durChapterIndex - 5)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000)
download(i)
}
}
} }
loadContent(durChapterIndex.minus(1), upContent, false)
saveRead() saveRead()
callBack?.upView() callBack?.upView()
curPageChanged() curPageChanged()
Coroutine.async {
//预下载
val minChapterIndex = max(0, durChapterIndex - 5)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000)
download(i)
}
}
return true return true
} else { } else {
return false return false
@ -206,6 +221,9 @@ object ReadBook {
curPageChanged() curPageChanged()
} }
/**
* 当前页面变化
*/
private fun curPageChanged() { private fun curPageChanged() {
callBack?.pageChanged() callBack?.pageChanged()
if (BaseReadAloudService.isRun) { if (BaseReadAloudService.isRun) {
@ -261,7 +279,7 @@ object ReadBook {
fun loadContent( fun loadContent(
index: Int, index: Int,
upContent: Boolean = true, upContent: Boolean = true,
resetPageOffset: Boolean, resetPageOffset: Boolean = false,
success: (() -> Unit)? = null success: (() -> Unit)? = null
) { ) {
book?.let { book -> book?.let { book ->
@ -337,66 +355,6 @@ object ReadBook {
} }
} }
fun searchResultPositions(
pages: List<TextPage>,
indexWithinChapter: Int,
query: String
): Array<Int> {
// calculate search result's pageIndex
var content = ""
pages.map {
content += it.text
}
var count = 1
var index = content.indexOf(query)
while (count != indexWithinChapter) {
index = content.indexOf(query, index + 1)
count += 1
}
val contentPosition = index
var pageIndex = 0
var length = pages[pageIndex].text.length
while (length < contentPosition) {
pageIndex += 1
if (pageIndex > pages.size) {
pageIndex = pages.size
break
}
length += pages[pageIndex].text.length
}
// calculate search result's lineIndex
val currentPage = pages[pageIndex]
var lineIndex = 0
length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length
while (length < contentPosition) {
lineIndex += 1
if (lineIndex > currentPage.textLines.size) {
lineIndex = currentPage.textLines.size
break
}
length += currentPage.textLines[lineIndex].text.length
}
// charIndex
val currentLine = currentPage.textLines[lineIndex]
length -= currentLine.text.length
val charIndex = contentPosition - length
var addLine = 0
var charIndex2 = 0
// change line
if ((charIndex + query.length) > currentLine.text.length) {
addLine = 1
charIndex2 = charIndex + query.length - currentLine.text.length - 1
}
// changePage
if ((lineIndex + addLine + 1) > currentPage.textLines.size) {
addLine = -1
charIndex2 = charIndex + query.length - currentLine.text.length - 1
}
return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
}
/** /**
* 内容加载完成 * 内容加载完成
*/ */

@ -3,24 +3,21 @@ package io.legado.app.receiver
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.KeyEvent import android.view.KeyEvent
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.LifecycleHelp import io.legado.app.help.LifecycleHelp
import io.legado.app.model.ReadBook
import io.legado.app.service.AudioPlayService import io.legado.app.service.AudioPlayService
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.audio.AudioPlayActivity import io.legado.app.ui.book.audio.AudioPlayActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import splitties.init.appCtx
/** /**
@ -37,21 +34,6 @@ class MediaButtonReceiver : BroadcastReceiver() {
companion object { companion object {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
ReadAloud.prevParagraph(appCtx)
}
KeyEvent.KEYCODE_MEDIA_NEXT -> {
ReadAloud.nextParagraph(appCtx)
}
}
}
}
fun handleIntent(context: Context, intent: Intent): Boolean { fun handleIntent(context: Context, intent: Intent): Boolean {
val intentAction = intent.action val intentAction = intent.action
if (Intent.ACTION_MEDIA_BUTTON == intentAction) { if (Intent.ACTION_MEDIA_BUTTON == intentAction) {
@ -62,22 +44,17 @@ class MediaButtonReceiver : BroadcastReceiver() {
if (action == KeyEvent.ACTION_DOWN) { if (action == KeyEvent.ACTION_DOWN) {
when (keycode) { when (keycode) {
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
if (handler.hasMessages(KeyEvent.KEYCODE_MEDIA_PREVIOUS)) { if (context.getPrefBoolean("mediaButtonPerNext", false)) {
handler.removeMessages(KeyEvent.KEYCODE_MEDIA_PREVIOUS)
ReadBook.moveToPrevChapter(true) ReadBook.moveToPrevChapter(true)
} else { } else {
handler.sendEmptyMessageDelayed( ReadAloud.prevParagraph(context)
KeyEvent.KEYCODE_MEDIA_PREVIOUS,
500
)
} }
} }
KeyEvent.KEYCODE_MEDIA_NEXT -> { KeyEvent.KEYCODE_MEDIA_NEXT -> {
if (handler.hasMessages(KeyEvent.KEYCODE_MEDIA_NEXT)) { if (context.getPrefBoolean("mediaButtonPerNext", false)) {
handler.removeMessages(KeyEvent.KEYCODE_MEDIA_NEXT)
ReadBook.moveToNextChapter(true) ReadBook.moveToNextChapter(true)
} else { } else {
handler.sendEmptyMessageDelayed(KeyEvent.KEYCODE_MEDIA_NEXT, 500) ReadAloud.nextParagraph(context)
} }
} }
else -> readAloud(context) else -> readAloud(context)

@ -21,8 +21,8 @@ import io.legado.app.constant.*
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.MediaHelp import io.legado.app.help.MediaHelp
import io.legado.app.model.ReadBook
import io.legado.app.receiver.MediaButtonReceiver import io.legado.app.receiver.MediaButtonReceiver
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean

@ -6,9 +6,9 @@ import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive

@ -9,7 +9,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.MediaHelp import io.legado.app.help.MediaHelp
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi

@ -5,6 +5,7 @@ import io.legado.app.R
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.model.ReadBook
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CacheBookService import io.legado.app.service.CacheBookService
import io.legado.app.utils.msg import io.legado.app.utils.msg

@ -33,11 +33,11 @@ import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.book.changesource.ChangeSourceDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.info.edit.BookInfoEditActivity
import io.legado.app.ui.book.login.SourceLoginActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.ui.widget.image.CoverImageView
import io.legado.app.utils.* import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding

@ -11,9 +11,9 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO

@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
class BookInfoEditViewModel(application: Application) : BaseViewModel(application) { class BookInfoEditViewModel(application: Application) : BaseViewModel(application) {
var book: Book? = null var book: Book? = null

@ -1,4 +1,4 @@
package io.legado.app.ui.login package io.legado.app.ui.book.login
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
@ -74,7 +74,7 @@ class SourceLoginActivity : BaseActivity<ActivitySourceLoginBinding>() {
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_success -> { R.id.menu_ok -> {
if (!checking) { if (!checking) {
checking = true checking = true
binding.titleBar.snackbar(R.string.check_host_cookie) binding.titleBar.snackbar(R.string.check_host_cookie)

@ -0,0 +1,83 @@
package io.legado.app.ui.book.login
import android.os.Bundle
import android.text.InputType
import android.util.Base64
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.AppConst
import io.legado.app.data.entities.rule.LoginRule
import io.legado.app.databinding.DialogLoginBinding
import io.legado.app.help.CacheManager
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.text.EditText
import io.legado.app.ui.widget.text.TextInputLayout
import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.applyTint
import io.legado.app.utils.viewbindingdelegate.viewBinding
class SourceLoginDialog : BaseDialogFragment() {
private val binding by viewBinding(DialogLoginBinding::bind)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_login, container)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
val sourceUrl = arguments?.getString("sourceUrl")
val loginRule = arguments?.getParcelable<LoginRule>("loginRule")
loginRule?.ui?.forEachIndexed { index, rowUi ->
when (rowUi.type) {
"text" -> layoutInflater.inflate(R.layout.item_source_edit, binding.root)
.apply {
id = index
}
"password" -> layoutInflater.inflate(R.layout.item_source_edit, binding.root)
.apply {
id = index
findViewById<EditText>(R.id.editText)?.inputType =
InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
}
}
}
binding.toolBar.inflateMenu(R.menu.source_login)
binding.toolBar.menu.applyTint(requireContext())
binding.toolBar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_ok -> {
val loginData = hashMapOf<String, String?>()
loginRule?.ui?.forEachIndexed { index, rowUi ->
when (rowUi.type) {
"text", "password" -> {
val value = binding.root.findViewById<TextInputLayout>(index)
.findViewById<EditText>(R.id.editText).text?.toString()
loginData[rowUi.name] = value
}
}
}
val data = Base64.encodeToString(
EncoderUtils.decryptAES(
GSON.toJson(loginData).toByteArray(),
AppConst.androidId.toByteArray()
),
Base64.DEFAULT
)
CacheManager.put("login_$sourceUrl", data)
}
}
return@setOnMenuItemClickListener true
}
}
}

@ -29,12 +29,13 @@ import io.legado.app.help.storage.Backup
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.model.ReadBook
import io.legado.app.receiver.TimeBatteryReceiver import io.legado.app.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.changesource.ChangeSourceDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.login.SourceLoginActivity
import io.legado.app.ui.book.read.config.* import io.legado.app.ui.book.read.config.*
import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR
@ -48,7 +49,6 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.BookmarkDialog import io.legado.app.ui.book.toc.BookmarkDialog
import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.dict.DictDialog import io.legado.app.ui.dict.DictDialog
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.replace.ReplaceRuleActivity import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.ui.replace.edit.ReplaceEditActivity import io.legado.app.ui.replace.edit.ReplaceEditActivity
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
@ -855,11 +855,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
private fun skipToSearch(index: Int, indexWithinChapter: Int) { private fun skipToSearch(index: Int, indexWithinChapter: Int) {
viewModel.openChapter(index) { viewModel.openChapter(index) {
val pages = ReadBook.curTextChapter?.pages ?: return@openChapter val pages = ReadBook.curTextChapter?.pages ?: return@openChapter
val positions = ReadBook.searchResultPositions( val positions = viewModel.searchResultPositions(pages, indexWithinChapter)
pages,
indexWithinChapter,
viewModel.searchContentQuery
)
ReadBook.skipToPage(positions[0]) { ReadBook.skipToPage(positions[0]) {
launch { launch {
binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2]) binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2])

@ -23,8 +23,8 @@ import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.model.ReadBook
import io.legado.app.service.help.CacheBook import io.legado.app.service.help.CacheBook
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.config.BgTextConfigDialog import io.legado.app.ui.book.read.config.BgTextConfigDialog
import io.legado.app.ui.book.read.config.ClickActionConfigDialog import io.legado.app.ui.book.read.config.ClickActionConfigDialog
import io.legado.app.ui.book.read.config.PaddingConfigDialog import io.legado.app.ui.book.read.config.PaddingConfigDialog

@ -14,12 +14,13 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor import io.legado.app.help.ContentProcessor
import io.legado.app.help.storage.BookWebDav import io.legado.app.help.storage.BookWebDav
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.PreciseSearch import io.legado.app.model.webBook.PreciseSearch
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.msg import io.legado.app.utils.msg
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@ -305,6 +306,68 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
} }
/**
* 内容搜索跳转
*/
fun searchResultPositions(
pages: List<TextPage>,
indexWithinChapter: Int
): Array<Int> {
// calculate search result's pageIndex
var content = ""
pages.map {
content += it.text
}
var count = 1
var index = content.indexOf(searchContentQuery)
while (count != indexWithinChapter) {
index = content.indexOf(searchContentQuery, index + 1)
count += 1
}
val contentPosition = index
var pageIndex = 0
var length = pages[pageIndex].text.length
while (length < contentPosition) {
pageIndex += 1
if (pageIndex > pages.size) {
pageIndex = pages.size
break
}
length += pages[pageIndex].text.length
}
// calculate search result's lineIndex
val currentPage = pages[pageIndex]
var lineIndex = 0
length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length
while (length < contentPosition) {
lineIndex += 1
if (lineIndex > currentPage.textLines.size) {
lineIndex = currentPage.textLines.size
break
}
length += currentPage.textLines[lineIndex].text.length
}
// charIndex
val currentLine = currentPage.textLines[lineIndex]
length -= currentLine.text.length
val charIndex = contentPosition - length
var addLine = 0
var charIndex2 = 0
// change line
if ((charIndex + searchContentQuery.length) > currentLine.text.length) {
addLine = 1
charIndex2 = charIndex + searchContentQuery.length - currentLine.text.length - 1
}
// changePage
if ((lineIndex + addLine + 1) > currentPage.textLines.size) {
addLine = -1
charIndex2 = charIndex + searchContentQuery.length - currentLine.text.length - 1
}
return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
}
/** /**
* 替换规则变化 * 替换规则变化
*/ */

@ -18,7 +18,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.LocalConfig import io.legado.app.help.LocalConfig
import io.legado.app.help.ThemeConfig import io.legado.app.help.ThemeConfig
import io.legado.app.lib.theme.* import io.legado.app.lib.theme.*
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.* import io.legado.app.utils.*
import splitties.views.onLongClick import splitties.views.onLongClick

@ -11,9 +11,9 @@ import io.legado.app.databinding.DialogReadAloudBinding
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.model.ReadBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils

@ -16,7 +16,7 @@ import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.widget.font.FontSelectDialog import io.legado.app.ui.widget.font.FontSelectDialog
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils

@ -11,7 +11,7 @@ import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChar import io.legado.app.ui.book.read.page.entities.TextChar
import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage

@ -15,7 +15,7 @@ import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ViewBookPageBinding import io.legado.app.databinding.ViewBookPageBinding
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig import io.legado.app.help.ReadTipConfig
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.widget.BatteryView import io.legado.app.ui.widget.BatteryView

@ -15,7 +15,7 @@ import android.widget.FrameLayout
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.api.DataSource import io.legado.app.ui.book.read.page.api.DataSource
import io.legado.app.ui.book.read.page.delegate.* import io.legado.app.ui.book.read.page.delegate.*
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection

@ -1,6 +1,6 @@
package io.legado.app.ui.book.read.page.api package io.legado.app.ui.book.read.page.api
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
interface DataSource { interface DataSource {

@ -4,7 +4,7 @@ import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import splitties.init.appCtx import splitties.init.appCtx
import java.text.DecimalFormat import java.text.DecimalFormat

@ -1,6 +1,6 @@
package io.legado.app.ui.book.read.page.provider package io.legado.app.ui.book.read.page.provider
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.api.DataSource import io.legado.app.ui.book.read.page.api.DataSource
import io.legado.app.ui.book.read.page.api.PageFactory import io.legado.app.ui.book.read.page.api.PageFactory
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage

@ -24,10 +24,10 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.book.login.SourceLoginActivity
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.document.FilePicker import io.legado.app.ui.document.FilePicker
import io.legado.app.ui.document.FilePickerParam import io.legado.app.ui.document.FilePickerParam
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.qrcode.QrCodeResult import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.ui.widget.KeyboardToolPop
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
@ -202,7 +202,13 @@ class BookSourceEditActivity :
add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url))
add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern)) add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern))
add(EditEntity("header", source?.header, R.string.source_http_header)) add(EditEntity("header", source?.header, R.string.source_http_header))
add(
EditEntity(
"concurrentRate",
source?.concurrentRate,
R.string.source_concurrent_rate
)
)
} }
//搜索 //搜索
val sr = source?.getSearchRule() val sr = source?.getSearchRule()
@ -294,6 +300,7 @@ class BookSourceEditActivity :
"bookUrlPattern" -> source.bookUrlPattern = it.value "bookUrlPattern" -> source.bookUrlPattern = it.value
"header" -> source.header = it.value "header" -> source.header = it.value
"bookSourceComment" -> source.bookSourceComment = it.value ?: "" "bookSourceComment" -> source.bookSourceComment = it.value ?: ""
"concurrentRate" -> source.concurrentRate = it.value
} }
} }
searchEntities.forEach { searchEntities.forEach {

@ -8,7 +8,7 @@ import androidx.fragment.app.FragmentManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.databinding.DialogPhotoViewBinding import io.legado.app.databinding.DialogPhotoViewBinding
import io.legado.app.service.help.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.provider.ImageProvider import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding

@ -92,8 +92,8 @@ object EncoderUtils {
fun decryptBase64AES( fun decryptBase64AES(
data: ByteArray?, data: ByteArray?,
key: ByteArray?, key: ByteArray?,
transformation: String?, transformation: String = "DES/CBC/PKCS5Padding",
iv: ByteArray? iv: ByteArray? = null
): ByteArray? { ): ByteArray? {
return decryptAES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv) return decryptAES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv)
} }
@ -111,10 +111,10 @@ object EncoderUtils {
fun decryptAES( fun decryptAES(
data: ByteArray?, data: ByteArray?,
key: ByteArray?, key: ByteArray?,
transformation: String?, transformation: String = "DES/CBC/PKCS5Padding",
iv: ByteArray? iv: ByteArray? = null
): ByteArray? { ): ByteArray? {
return symmetricTemplate(data, key, "AES", transformation!!, iv, false) return symmetricTemplate(data, key, "AES", transformation, iv, false)
} }
@ -128,7 +128,7 @@ object EncoderUtils {
* @param isEncrypt True to encrypt, false otherwise. * @param isEncrypt True to encrypt, false otherwise.
* @return the bytes of symmetric encryption or decryption * @return the bytes of symmetric encryption or decryption
*/ */
@Suppress("SameParameterValue")
private fun symmetricTemplate( private fun symmetricTemplate(
data: ByteArray?, data: ByteArray?,
key: ByteArray?, key: ByteArray?,
@ -137,7 +137,8 @@ object EncoderUtils {
iv: ByteArray?, iv: ByteArray?,
isEncrypt: Boolean isEncrypt: Boolean
): ByteArray? { ): ByteArray? {
return if (data == null || data.isEmpty() || key == null || key.isEmpty()) null else try { return if (data == null || data.isEmpty() || key == null || key.isEmpty()) null
else try {
val keySpec = SecretKeySpec(key, algorithm) val keySpec = SecretKeySpec(key, algorithm)
val cipher = Cipher.getInstance(transformation) val cipher = Cipher.getInstance(transformation)
if (iv == null || iv.isEmpty()) { if (iv == null || iv.isEmpty()) {

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/background"
android:gravity="center"
android:orientation="horizontal">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_menu"
android:elevation="5dp"
android:theme="?attr/actionBarStyle"
app:displayHomeAsUp="false"
app:fitStatusBar="false"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:titleTextAppearance="@style/ToolbarTitle" />
</LinearLayout>

@ -3,9 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/menu_success" android:id="@+id/menu_ok"
android:icon="@drawable/ic_check" android:icon="@drawable/ic_check"
android:title="@string/success" android:title="@string/ok"
app:showAsAction="always" /> app:showAsAction="always" />
</menu> </menu>

@ -393,6 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string> <string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string> <string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -849,6 +850,9 @@
<string name="unknown_error">未知错误</string> <string name="unknown_error">未知错误</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">La fuente del libro de cheques muestra un mensaje de depuración</string> <string name="check_source_show_debug_message">La fuente del libro de cheques muestra un mensaje de depuración</string>
<string name="check_source_show_debug_message_summary">Muestra los pasos y el tiempo de la solicitud de red durante la verificación de la fuente del libro; actualmente solo admite la verificación de un solo hilo</string> <string name="check_source_show_debug_message_summary">Muestra los pasos y el tiempo de la solicitud de red durante la verificación de la fuente del libro; actualmente solo admite la verificación de un solo hilo</string>
</resources> </resources>

@ -393,6 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string> <string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string> <string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -850,6 +851,9 @@
<string name="export_no_chapter_name">No export chapter names</string> <string name="export_no_chapter_name">No export chapter names</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">Check book source shows debug message</string> <string name="check_source_show_debug_message">Check book source shows debug message</string>
<string name="check_source_show_debug_message_summary">Show network status and timestamp during source checking, currently only supports single-thread</string> <string name="check_source_show_debug_message_summary">Show network status and timestamp during source checking, currently only supports single-thread</string>
</resources> </resources>

@ -393,6 +393,7 @@
<string name="source_group">源分组(fonteGrupo)</string> <string name="source_group">源分组(fonteGrupo)</string>
<string name="diy_source_group">自定义源分组</string> <string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string> <string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(fonteComentário)</string> <string name="comment">源注释(fonteComentário)</string>
@ -849,6 +850,9 @@
<string name="unknown_error">未知错误</string> <string name="unknown_error">未知错误</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">Verificar a fonte do livro mostra uma mensagem de depuração</string> <string name="check_source_show_debug_message">Verificar a fonte do livro mostra uma mensagem de depuração</string>
<string name="check_source_show_debug_message_summary">Exibir etapas de solicitação de rede e tempo durante a verificação da fonte do livro; atualmente, oferece suporte apenas à verificação de thread único</string> <string name="check_source_show_debug_message_summary">Exibir etapas de solicitação de rede e tempo durante a verificação da fonte do livro; atualmente, oferece suporte apenas à verificação de thread único</string>
</resources> </resources>

@ -389,6 +389,7 @@
<string name="source_url">源URL (sourceUrl)</string> <string name="source_url">源URL (sourceUrl)</string>
<string name="source_group">源分組 (sourceGroup)</string> <string name="source_group">源分組 (sourceGroup)</string>
<string name="sort_url">分類 Url</string> <string name="sort_url">分類 Url</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="login_url">登錄 URL(loginUrl)</string> <string name="login_url">登錄 URL(loginUrl)</string>
<string name="comment">源注釋(sourceComment)</string> <string name="comment">源注釋(sourceComment)</string>
<string name="r_search_url">搜索地址 (url)</string> <string name="r_search_url">搜索地址 (url)</string>
@ -851,6 +852,9 @@
<string name="export_no_chapter_name">TXT不導出章節名</string> <string name="export_no_chapter_name">TXT不導出章節名</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">校驗書源顯示詳細信息</string> <string name="check_source_show_debug_message">校驗書源顯示詳細信息</string>
<string name="check_source_show_debug_message_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string> <string name="check_source_show_debug_message_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string>

@ -392,6 +392,7 @@
<string name="source_group">源分組(sourceGroup)</string> <string name="source_group">源分組(sourceGroup)</string>
<string name="diy_source_group">自訂源分組</string> <string name="diy_source_group">自訂源分組</string>
<string name="diy_edit_source_group">輸入自訂源分組名稱</string> <string name="diy_edit_source_group">輸入自訂源分組名稱</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分類Url</string> <string name="sort_url">分類Url</string>
<string name="login_url">登入URL(loginUrl)</string> <string name="login_url">登入URL(loginUrl)</string>
<string name="comment">源注釋(sourceComment)</string> <string name="comment">源注釋(sourceComment)</string>
@ -852,6 +853,9 @@
<string name="export_no_chapter_name">TXT不匯出章節名</string> <string name="export_no_chapter_name">TXT不匯出章節名</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">校驗書源顯示詳細信息</string> <string name="check_source_show_debug_message">校驗書源顯示詳細信息</string>
<string name="check_source_show_debug_message_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string> <string name="check_source_show_debug_message_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string>

@ -392,6 +392,7 @@
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string> <string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string> <string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -853,6 +854,9 @@
<string name="autobackup_fail">自动备份失败</string> <string name="autobackup_fail">自动备份失败</string>
<string name="end">结束</string> <string name="end">结束</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">校验显示详细信息</string> <string name="check_source_show_debug_message">校验显示详细信息</string>
<string name="check_source_show_debug_message_summary">书源校验时显示网络请求步骤和时间,当前只支持单线程校验</string> <string name="check_source_show_debug_message_summary">书源校验时显示网络请求步骤和时间,当前只支持单线程校验</string>

@ -393,7 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string> <string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string> <string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -855,6 +855,9 @@
<string name="autobackup_fail">Autobackup failed</string> <string name="autobackup_fail">Autobackup failed</string>
<string name="end">end</string> <string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string> <string name="custom_group_summary">关闭替换分组/开启添加分组</string>
<string name="pref_media_button_per_next">媒体按钮•上一首|下一首</string>
<string name="pref_media_button_per_next_summary">上一段|下一段/上一章|下一章</string>
<string name="read_aloud_by_page_summary">及时翻页,翻页时会停顿一下</string>
<string name="check_source_show_debug_message">Check book source shows debug message</string> <string name="check_source_show_debug_message">Check book source shows debug message</string>
<string name="check_source_show_debug_message_summary">Show network status and timestamp during source checking, currently only supports single-thread</string> <string name="check_source_show_debug_message_summary">Show network status and timestamp during source checking, currently only supports single-thread</string>

@ -8,9 +8,17 @@
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:iconSpaceReserved="false"> app:iconSpaceReserved="false">
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false"
android:title="@string/pref_media_button_per_next"
android:summary="@string/pref_media_button_per_next_summary"
android:key="mediaButtonPerNext"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference <io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:title="@string/read_aloud_by_page" android:title="@string/read_aloud_by_page"
android:summary="@string/read_aloud_by_page_summary"
android:key="readAloudByPage" android:key="readAloudByPage"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />

Loading…
Cancel
Save