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. 15
      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. 108
      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
/schemas

@ -14,6 +14,10 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec
android {
compileSdkVersion 30
buildToolsVersion '30.0.3'
kotlinOptions {
jvmTarget = "11"
}
signingConfigs {
if (project.hasProperty("RELEASE_STORE_FILE")) {
myConfig {
@ -102,10 +106,10 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
sourceSets {
// Adds exported schema location as test app assets.
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
buildToolsVersion '30.0.3'
tasks.withType(JavaCompile) {
//options.compilerArgs << "-Xlint:unchecked"
}
@ -166,7 +170,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
//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-ktx:$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" />
<!-- 书源登录 -->
<activity
android:name=".ui.login.SourceLoginActivity"
android:name="io.legado.app.ui.book.login.SourceLoginActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" />
<!-- 阅读记录 -->

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

@ -10,11 +10,11 @@ import io.legado.app.data.entities.Book
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
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.LocalBook
import io.legado.app.model.localBook.UmdFile
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.utils.*
import kotlinx.coroutines.runBlocking

@ -4,13 +4,10 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.androidId
import io.legado.app.data.dao.*
import io.legado.app.data.entities.*
import io.legado.app.help.AppConfig
import splitties.init.appCtx
import java.util.*
@ -19,13 +16,13 @@ val appDb by lazy {
}
@Database(
version = 35,
exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class],
version = 34,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
@ -54,14 +51,7 @@ abstract class AppDatabase : RoomDatabase() {
fun createDatabase(context: Context) =
Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addMigrations(
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
)
.addMigrations(*DatabaseMigrations.migrations)
.allowMainThreadQueries()
.addCallback(dbCallback)
.build()
@ -74,269 +64,28 @@ abstract class AppDatabase : RoomDatabase() {
override fun onOpen(db: SupportSQLiteDatabase) {
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})"""
)
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})"""
)
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})"""
)
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})"""
)
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.data.appDb
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.MD5Utils
import io.legado.app.utils.fromJsonObject

@ -29,6 +29,7 @@ data class BookSource(
var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频
var bookUrlPattern: String? = null, // 详情页url正则
var concurrentRate: String? = null, //并发率
var customOrder: Int = 0, // 手动排序编号
var enabled: 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())
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
fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule)

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

@ -225,7 +225,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val autoChangeSource: Boolean
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)

@ -1,4 +1,4 @@
package io.legado.app.service.help
package io.legado.app.model
import androidx.lifecycle.MutableLiveData
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.model.webBook.WebBook
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.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.utils.msg
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay
import splitties.init.appCtx
import kotlin.math.max
@ -44,7 +46,7 @@ object ReadBook {
var readStartTime: Long = System.currentTimeMillis()
fun resetData(book: Book) {
this.book = book
ReadBook.book = book
readRecord.bookName = book.name
readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0
durChapterIndex = book.durChapterIndex
@ -113,8 +115,8 @@ object ReadBook {
}
fun upMsg(msg: String?) {
if (this.msg != msg) {
this.msg = msg
if (ReadBook.msg != msg) {
ReadBook.msg = msg
callBack?.upContent()
}
}
@ -132,25 +134,39 @@ object ReadBook {
prevTextChapter = curTextChapter
curTextChapter = nextTextChapter
nextTextChapter = null
book?.let {
if (curTextChapter == null) {
loadContent(durChapterIndex, upContent, false)
} else if (upContent) {
callBack?.upContent()
}
loadContent(durChapterIndex.plus(1), upContent, false)
saveRead()
callBack?.upView()
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)
}
}
}
}
}
saveRead()
callBack?.upView()
curPageChanged()
return true
} else {
return false
@ -167,24 +183,23 @@ object ReadBook {
nextTextChapter = curTextChapter
curTextChapter = prevTextChapter
prevTextChapter = null
book?.let {
if (curTextChapter == null) {
loadContent(durChapterIndex, upContent, false)
} else if (upContent) {
callBack?.upContent()
}
loadContent(durChapterIndex.minus(1), upContent, false)
saveRead()
callBack?.upView()
curPageChanged()
Coroutine.async {
//预下载
val minChapterIndex = max(0, durChapterIndex - 5)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000)
download(i)
}
}
}
saveRead()
callBack?.upView()
curPageChanged()
return true
} else {
return false
@ -206,6 +221,9 @@ object ReadBook {
curPageChanged()
}
/**
* 当前页面变化
*/
private fun curPageChanged() {
callBack?.pageChanged()
if (BaseReadAloudService.isRun) {
@ -261,7 +279,7 @@ object ReadBook {
fun loadContent(
index: Int,
upContent: Boolean = true,
resetPageOffset: Boolean,
resetPageOffset: Boolean = false,
success: (() -> Unit)? = null
) {
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.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.KeyEvent
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
import io.legado.app.help.AppConfig
import io.legado.app.help.LifecycleHelp
import io.legado.app.model.ReadBook
import io.legado.app.service.AudioPlayService
import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.AudioPlay
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.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent
import splitties.init.appCtx
/**
@ -37,21 +34,6 @@ class MediaButtonReceiver : BroadcastReceiver() {
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 {
val intentAction = intent.action
if (Intent.ACTION_MEDIA_BUTTON == intentAction) {
@ -62,22 +44,17 @@ class MediaButtonReceiver : BroadcastReceiver() {
if (action == KeyEvent.ACTION_DOWN) {
when (keycode) {
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
if (handler.hasMessages(KeyEvent.KEYCODE_MEDIA_PREVIOUS)) {
handler.removeMessages(KeyEvent.KEYCODE_MEDIA_PREVIOUS)
if (context.getPrefBoolean("mediaButtonPerNext", false)) {
ReadBook.moveToPrevChapter(true)
} else {
handler.sendEmptyMessageDelayed(
KeyEvent.KEYCODE_MEDIA_PREVIOUS,
500
)
ReadAloud.prevParagraph(context)
}
}
KeyEvent.KEYCODE_MEDIA_NEXT -> {
if (handler.hasMessages(KeyEvent.KEYCODE_MEDIA_NEXT)) {
handler.removeMessages(KeyEvent.KEYCODE_MEDIA_NEXT)
if (context.getPrefBoolean("mediaButtonPerNext", false)) {
ReadBook.moveToNextChapter(true)
} else {
handler.sendEmptyMessageDelayed(KeyEvent.KEYCODE_MEDIA_NEXT, 500)
ReadAloud.nextParagraph(context)
}
}
else -> readAloud(context)

@ -21,8 +21,8 @@ import io.legado.app.constant.*
import io.legado.app.help.IntentDataHelp
import io.legado.app.help.IntentHelp
import io.legado.app.help.MediaHelp
import io.legado.app.model.ReadBook
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.page.entities.TextChapter
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.IntentHelp
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.*
import kotlinx.coroutines.ensureActive
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.IntentHelp
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.postEvent
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.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.model.ReadBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CacheBookService
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.group.GroupSelectDialog
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.search.SearchActivity
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.image.CoverImageView
import io.legado.app.utils.*
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.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook
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.toastOnUi
import kotlinx.coroutines.Dispatchers.IO

@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb
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) {
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.graphics.Bitmap
@ -74,7 +74,7 @@ class SourceLoginActivity : BaseActivity<ActivitySourceLoginBinding>() {
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_success -> {
R.id.menu_ok -> {
if (!checking) {
checking = true
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.selector
import io.legado.app.lib.theme.accentColor
import io.legado.app.model.ReadBook
import io.legado.app.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService
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.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.BgTextConfigDialog.Companion.BG_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.TocActivityResult
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.edit.ReplaceEditActivity
import io.legado.app.ui.widget.dialog.TextDialog
@ -855,11 +855,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
private fun skipToSearch(index: Int, indexWithinChapter: Int) {
viewModel.openChapter(index) {
val pages = ReadBook.curTextChapter?.pages ?: return@openChapter
val positions = ReadBook.searchResultPositions(
pages,
indexWithinChapter,
viewModel.searchContentQuery
)
val positions = viewModel.searchResultPositions(pages, indexWithinChapter)
ReadBook.skipToPage(positions[0]) {
launch {
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.ThemeStore
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.ReadBook
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.PaddingConfigDialog

@ -14,12 +14,13 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
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.webBook.PreciseSearch
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService
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.postEvent
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.ThemeConfig
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.utils.*
import splitties.views.onLongClick

@ -11,9 +11,9 @@ import io.legado.app.databinding.DialogReadAloudBinding
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.model.ReadBook
import io.legado.app.service.BaseReadAloudService
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.widget.seekbar.SeekBarChangeListener
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.bottomBackground
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.widget.font.FontSelectDialog
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.help.ReadBookConfig
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.TextLine
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.help.ReadBookConfig
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.provider.ChapterProvider
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.ReadBookConfig
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.delegate.*
import io.legado.app.ui.book.read.page.entities.PageDirection

@ -1,6 +1,6 @@
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
interface DataSource {

@ -4,7 +4,7 @@ import android.text.Layout
import android.text.StaticLayout
import io.legado.app.R
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 splitties.init.appCtx
import java.text.DecimalFormat

@ -1,6 +1,6 @@
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.PageFactory
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.theme.ATH
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.document.FilePicker
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.widget.KeyboardToolPop
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("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern))
add(EditEntity("header", source?.header, R.string.source_http_header))
add(
EditEntity(
"concurrentRate",
source?.concurrentRate,
R.string.source_concurrent_rate
)
)
}
//搜索
val sr = source?.getSearchRule()
@ -294,6 +300,7 @@ class BookSourceEditActivity :
"bookUrlPattern" -> source.bookUrlPattern = it.value
"header" -> source.header = it.value
"bookSourceComment" -> source.bookSourceComment = it.value ?: ""
"concurrentRate" -> source.concurrentRate = it.value
}
}
searchEntities.forEach {

@ -8,7 +8,7 @@ import androidx.fragment.app.FragmentManager
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
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.utils.viewbindingdelegate.viewBinding

@ -92,8 +92,8 @@ object EncoderUtils {
fun decryptBase64AES(
data: ByteArray?,
key: ByteArray?,
transformation: String?,
iv: ByteArray?
transformation: String = "DES/CBC/PKCS5Padding",
iv: ByteArray? = null
): ByteArray? {
return decryptAES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv)
}
@ -111,10 +111,10 @@ object EncoderUtils {
fun decryptAES(
data: ByteArray?,
key: ByteArray?,
transformation: String?,
iv: ByteArray?
transformation: String = "DES/CBC/PKCS5Padding",
iv: ByteArray? = null
): 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.
* @return the bytes of symmetric encryption or decryption
*/
@Suppress("SameParameterValue")
private fun symmetricTemplate(
data: ByteArray?,
key: ByteArray?,
@ -137,7 +137,8 @@ object EncoderUtils {
iv: ByteArray?,
isEncrypt: Boolean
): 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 cipher = Cipher.getInstance(transformation)
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">
<item
android:id="@+id/menu_success"
android:id="@+id/menu_ok"
android:icon="@drawable/ic_check"
android:title="@string/success"
android:title="@string/ok"
app:showAsAction="always" />
</menu>

@ -393,6 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string>
<string name="diy_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="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string>
@ -849,6 +850,9 @@
<string name="unknown_error">未知错误</string>
<string name="end">end</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_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>

@ -393,6 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string>
<string name="diy_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="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string>
@ -850,6 +851,9 @@
<string name="export_no_chapter_name">No export chapter names</string>
<string name="end">end</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_summary">Show network status and timestamp during source checking, currently only supports single-thread</string>
</resources>

@ -393,6 +393,7 @@
<string name="source_group">源分组(fonteGrupo)</string>
<string name="diy_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="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(fonteComentário)</string>
@ -849,6 +850,9 @@
<string name="unknown_error">未知错误</string>
<string name="end">end</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_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>

@ -389,6 +389,7 @@
<string name="source_url">源URL (sourceUrl)</string>
<string name="source_group">源分組 (sourceGroup)</string>
<string name="sort_url">分類 Url</string>
<string name="source_concurrent_rate">并发率(concurrentRate)</string>
<string name="login_url">登錄 URL(loginUrl)</string>
<string name="comment">源注釋(sourceComment)</string>
<string name="r_search_url">搜索地址 (url)</string>
@ -851,6 +852,9 @@
<string name="export_no_chapter_name">TXT不導出章節名</string>
<string name="end">end</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_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string>

@ -392,6 +392,7 @@
<string name="source_group">源分組(sourceGroup)</string>
<string name="diy_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="login_url">登入URL(loginUrl)</string>
<string name="comment">源注釋(sourceComment)</string>
@ -852,6 +853,9 @@
<string name="export_no_chapter_name">TXT不匯出章節名</string>
<string name="end">end</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_summary">書源校驗時顯示網絡請求步驟和時間,當前只支持單線程校驗</string>

@ -392,6 +392,7 @@
<string name="source_group">源分组(sourceGroup)</string>
<string name="diy_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="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string>
@ -853,6 +854,9 @@
<string name="autobackup_fail">自动备份失败</string>
<string name="end">结束</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_summary">书源校验时显示网络请求步骤和时间,当前只支持单线程校验</string>

@ -393,7 +393,7 @@
<string name="source_group">源分组(sourceGroup)</string>
<string name="diy_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="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string>
@ -855,6 +855,9 @@
<string name="autobackup_fail">Autobackup failed</string>
<string name="end">end</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_summary">Show network status and timestamp during source checking, currently only supports single-thread</string>

@ -8,9 +8,17 @@
app:allowDividerBelow="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
android:defaultValue="false"
android:title="@string/read_aloud_by_page"
android:summary="@string/read_aloud_by_page_summary"
android:key="readAloudByPage"
app:iconSpaceReserved="false" />

Loading…
Cancel
Save