Merge branch 'master' of github.com:gedoor/legado

pull/198/head
fisher 5 years ago
commit 0d7c5b5355
  1. 14
      app/build.gradle
  2. 6
      app/src/google/res/values-zh-rHK/strings.xml
  3. 6
      app/src/google/res/values/strings.xml
  4. 2
      app/src/main/AndroidManifest.xml
  5. 51
      app/src/main/assets/updateLog.md
  6. 2
      app/src/main/java/io/legado/app/App.kt
  7. 1
      app/src/main/java/io/legado/app/constant/AppConst.kt
  8. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  9. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  10. 8
      app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt
  11. 9
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  12. 11
      app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
  13. 8
      app/src/main/java/io/legado/app/data/entities/Book.kt
  14. 2
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  15. 2
      app/src/main/java/io/legado/app/data/entities/RssArticle.kt
  16. 13
      app/src/main/java/io/legado/app/data/entities/RssSource.kt
  17. 2
      app/src/main/java/io/legado/app/data/entities/RssStar.kt
  18. 8
      app/src/main/java/io/legado/app/help/AppConfig.kt
  19. 13
      app/src/main/java/io/legado/app/help/BookHelp.kt
  20. 179
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  21. 74
      app/src/main/java/io/legado/app/help/ReadTipConfig.kt
  22. 1
      app/src/main/java/io/legado/app/help/http/AjaxWebView.kt
  23. 8
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  24. 2
      app/src/main/java/io/legado/app/help/storage/OldRule.kt
  25. 3
      app/src/main/java/io/legado/app/model/Debug.kt
  26. 6
      app/src/main/java/io/legado/app/model/Rss.kt
  27. 76
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  28. 3
      app/src/main/java/io/legado/app/model/rss/RssParser.kt
  29. 11
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  30. 1
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  31. 1
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  32. 19
      app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt
  33. 46
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  34. 21
      app/src/main/java/io/legado/app/service/DownloadService.kt
  35. 2
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  36. 1
      app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
  37. 4
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  38. 17
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  39. 9
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  40. 6
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  41. 3
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  42. 12
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  43. 6
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  44. 5
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  45. 1
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  46. 10
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  47. 9
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  48. 117
      app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt
  49. 21
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  50. 2
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  51. 187
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  52. 6
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  53. 9
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  54. 3
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  55. 10
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  56. 54
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  57. 8
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  58. 4
      app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
  59. 94
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt
  60. 91
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  61. 100
      app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt
  62. 52
      app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt
  63. 3
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  64. 2
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  65. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  66. 6
      app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt
  67. 2
      app/src/main/java/io/legado/app/ui/widget/BatteryView.kt
  68. 8
      app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt
  69. 10
      app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt
  70. 266
      app/src/main/java/io/legado/app/utils/ConstraintUtil.kt
  71. 3
      app/src/main/java/io/legado/app/utils/GsonExtensions.kt
  72. 3
      app/src/main/res/layout-land/activity_book_info.xml
  73. 15
      app/src/main/res/layout/activity_book_info.xml
  74. 26
      app/src/main/res/layout/activity_rss_artivles.xml
  75. 4
      app/src/main/res/layout/activity_rss_read.xml
  76. 18
      app/src/main/res/layout/dialog_read_book_style.xml
  77. 125
      app/src/main/res/layout/dialog_tip_config.xml
  78. 15
      app/src/main/res/layout/fragment_rss_articles.xml
  79. 86
      app/src/main/res/layout/view_book_page.xml
  80. 15
      app/src/main/res/menu/book_group_manage.xml
  81. 52
      app/src/main/res/values-zh-rHK/arrays.xml
  82. 688
      app/src/main/res/values-zh-rHK/strings.xml
  83. 10
      app/src/main/res/values/arrays.xml
  84. 45
      app/src/main/res/values/strings.xml
  85. 14
      app/src/main/res/xml/about.xml
  86. 4
      app/src/main/res/xml/donate.xml
  87. 18
      app/src/main/res/xml/pref_config_backup.xml
  88. 7
      app/src/main/res/xml/pref_config_other.xml
  89. 36
      app/src/main/res/xml/pref_config_theme.xml
  90. 4
      app/src/main/res/xml/pref_main.xml
  91. 10
      build.gradle

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "de.timfreiheit.resourceplaceholders" apply plugin: 'de.timfreiheit.resourceplaceholders'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
androidExtensions { androidExtensions {
@ -19,6 +19,7 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec
android { android {
compileSdkVersion 29 compileSdkVersion 29
flavorDimensions ("version")
signingConfigs { signingConfigs {
if (project.hasProperty("RELEASE_STORE_FILE")) { if (project.hasProperty("RELEASE_STORE_FILE")) {
myConfig { myConfig {
@ -37,7 +38,6 @@ android {
targetSdkVersion 29 targetSdkVersion 29
versionCode gitCommits versionCode gitCommits
versionName version versionName version
flavorDimensions "versionCode"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
project.ext.set("archivesBaseName", name + "_" + version) project.ext.set("archivesBaseName", name + "_" + version)
multiDexEnabled true multiDexEnabled true
@ -76,9 +76,11 @@ android {
} }
productFlavors { productFlavors {
app { app {
dimension "version"
manifestPlaceholders = [APP_CHANNEL_VALUE: "app"] manifestPlaceholders = [APP_CHANNEL_VALUE: "app"]
} }
google { google {
dimension "version"
applicationId "io.legado.play" applicationId "io.legado.play"
manifestPlaceholders = [APP_CHANNEL_VALUE: "google"] manifestPlaceholders = [APP_CHANNEL_VALUE: "google"]
} }
@ -115,14 +117,14 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//fireBase //fireBase
implementation 'com.google.firebase:firebase-core:17.2.3' implementation 'com.google.firebase:firebase-core:17.3.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//androidX //androidX
implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.media:media:1.1.0' implementation 'androidx.media:media:1.1.0'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.viewpager2:viewpager2:1.0.0'
@ -182,14 +184,14 @@ dependencies {
implementation 'com.jaredrummler:colorpicker:1.1.0' implementation 'com.jaredrummler:colorpicker:1.1.0'
//apache //apache
implementation 'org.apache.commons:commons-lang3:3.9' implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'org.apache.commons:commons-text:1.8' implementation 'org.apache.commons:commons-text:1.8'
//MarkDown //MarkDown
implementation 'ru.noties.markwon:core:3.1.0' implementation 'ru.noties.markwon:core:3.1.0'
// //
implementation 'com.github.houbb:opencc4j:1.4.0' implementation 'com.hankcs:hanlp:portable-1.7.7'
} }

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">閱讀Pro</string>
</resources>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">阅读Pro</string>
</resources>

@ -257,7 +257,7 @@
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<!--订阅条目--> <!--订阅条目-->
<activity <activity
android:name=".ui.rss.article.RssArticlesActivity" android:name=".ui.rss.article.RssSortActivity"
android:launchMode="singleTop" /> android:launchMode="singleTop" />
<!--Rss收藏--> <!--Rss收藏-->
<activity <activity

@ -1,7 +1,54 @@
## 更新日志 ## 更新日志
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 请关注公众号[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 * 新公众号[开源阅读]()已启用,[开源阅读软件]()备用
**2020/04/20**
* 优化阅读界面信息显示
**2020/04/19**
* 添加阅读界面各种信息设置
**2020/04/18**
* feat: 中文简繁处理库换成 HanLP, 中文增加 zh-rHK 翻译, hingbong
* 修复更新时间不对的bug
**2020/04/13**
* 去除rss朗读时的引号
**2020/04/13**
* 修复调用webView返回结果多了引号的bug
**2020/04/12**
* 解决无法取消加粗的bug
* 修复换源自动加入书架的bug
**2020/04/09**
* 修复书架刷新闪烁
**2020/04/08**
* 可以隐藏书架未分组
**2020/04/07**
* 书架添加未分组,有未分组书籍时自动显示
* 其它一些优化
**2020/04/04**
* 优化备份逻辑
* 修复订阅分类太多显示不全的bug
* 修复一些分类要手动刷新的问题
**2020/04/02**
* 书架书名和作者作为唯一值
* 添加订阅分类,分类规则和发现一样,分类一::url1 && 分类2::url2
**2020/03/29**
* 添加退出软件后是否响应耳机按键的开关
* 优化书源校验
**2020/03/26**
* 修复txt目录bug
* 最近工作比较忙,只有晚上有时间写软件,bug之类的不要催,白天不回消息
**2020/03/25** **2020/03/25**
* 修复7.1.1的网络问题,是retrofit2库最新版本的bug,暂时退回上版本 * 修复7.1.1的网络问题,是retrofit2库最新版本的bug,暂时退回上版本

@ -73,7 +73,7 @@ class App : Application() {
.primaryColor( .primaryColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600)) getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600))
).accentColor( ).accentColor(
getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800)) getPrefInt("colorAccentNight", getCompatColor(R.color.md_deep_orange_800))
).backgroundColor( ).backgroundColor(
getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
).bottomBackground( ).bottomBackground(

@ -49,6 +49,7 @@ object AppConst {
val bookGroupAll = BookGroup(-1, App.INSTANCE.getString(R.string.all)) val bookGroupAll = BookGroup(-1, App.INSTANCE.getString(R.string.all))
val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local))
val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio)) val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio))
val bookGroupNone = BookGroup(-4, App.INSTANCE.getString(R.string.no_group))
const val notificationIdRead = 1144771 const val notificationIdRead = 1144771
const val notificationIdAudio = 1144772 const val notificationIdAudio = 1144772

@ -42,4 +42,5 @@ object PreferKey {
const val shareLayout = "shareLayout" const val shareLayout = "shareLayout"
const val readStyleSelect = "readStyleSelect" const val readStyleSelect = "readStyleSelect"
const val systemTypefaces = "system_typefaces" const val systemTypefaces = "system_typefaces"
const val readBodyToLh = "readBodyToLh"
} }

@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
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], RssStar::class, TxtTocRule::class],
version = 8, version = 10,
exportSchema = true exportSchema = true
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {

@ -10,16 +10,16 @@ import io.legado.app.data.entities.BookChapter
@Dao @Dao
interface BookChapterDao { interface BookChapterDao {
@Query("select * from chapters where bookUrl = :bookUrl") @Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun observeByBook(bookUrl: String): LiveData<List<BookChapter>> fun observeByBook(bookUrl: String): LiveData<List<BookChapter>>
@Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%'") @Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%' order by `index`")
fun liveDataSearch(bookUrl: String, key: String): LiveData<List<BookChapter>> fun liveDataSearch(bookUrl: String, key: String): LiveData<List<BookChapter>>
@Query("select * from chapters where bookUrl = :bookUrl") @Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun getChapterList(bookUrl: String): List<BookChapter> fun getChapterList(bookUrl: String): List<BookChapter>
@Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end") @Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end order by `index`")
fun getChapterList(bookUrl: String, start: Int, end: Int): List<BookChapter> fun getChapterList(bookUrl: String, start: Int, end: Int): List<BookChapter>
@Query("select * from chapters where bookUrl = :bookUrl and `index` = :index") @Query("select * from chapters where bookUrl = :bookUrl and `index` = :index")

@ -30,6 +30,9 @@ interface BookDao {
@Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") @Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
fun observeNoGroup(): LiveData<List<Book>> fun observeNoGroup(): LiveData<List<Book>>
@Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
fun observeNoGroupSize(): LiveData<Int>
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
fun liveDataSearch(key: String): LiveData<List<Book>> fun liveDataSearch(key: String): LiveData<List<Book>>
@ -42,6 +45,12 @@ interface BookDao {
@Query("SELECT * FROM books WHERE bookUrl = :bookUrl") @Query("SELECT * FROM books WHERE bookUrl = :bookUrl")
fun getBook(bookUrl: String): Book? fun getBook(bookUrl: String): Book?
@Query("SELECT * FROM books WHERE name = :name and author = :author")
fun getBook(name: String, author: String): Book?
@get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
val noGroupSize: Int
@get:Query("SELECT * FROM books where origin <> '${BookType.local}' and type = 0") @get:Query("SELECT * FROM books where origin <> '${BookType.local}' and type = 0")
val webBooks: List<Book> val webBooks: List<Book>

@ -12,17 +12,18 @@ interface RssArticleDao {
fun get(origin: String, link: String): RssArticle? fun get(origin: String, link: String): RssArticle?
@Query( @Query(
"""select t1.link, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read """select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read
from rssArticles as t1 left join rssReadRecords as t2 from rssArticles as t1 left join rssReadRecords as t2
on t1.link = t2.record where origin = :origin order by `order` desc""" on t1.link = t2.record where origin = :origin and sort = :sort
order by `order` desc"""
) )
fun liveByOrigin(origin: String): LiveData<List<RssArticle>> fun liveByOriginSort(origin: String, sort: String): LiveData<List<RssArticle>>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rssArticle: RssArticle) fun insert(vararg rssArticle: RssArticle)
@Query("delete from rssArticles where origin = :origin and `order` < :order") @Query("delete from rssArticles where origin = :origin and sort = :sort and `order` < :order")
fun clearOld(origin: String, order: Long) fun clearOld(origin: String, sort: String, order: Long)
@Update @Update
fun update(vararg rssArticle: RssArticle) fun update(vararg rssArticle: RssArticle)

@ -4,7 +4,6 @@ import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@ -15,9 +14,12 @@ import java.nio.charset.Charset
import kotlin.math.max import kotlin.math.max
@Parcelize @Parcelize
@Entity(tableName = "books", indices = [(Index(value = ["bookUrl"], unique = true))]) @Entity(
tableName = "books",
primaryKeys = ["name", "author"],
indices = [(Index(value = ["bookUrl"], unique = true))]
)
data class Book( data class Book(
@PrimaryKey
override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
var tocUrl: String = "", // 目录页Url (toc=table of Contents) var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var origin: String = BookType.local, // 书源URL(默认BookType.local) var origin: String = BookType.local, // 书源URL(默认BookType.local)

@ -169,7 +169,7 @@ data class BookSource(
} }
} }
val b = a.split("(&&|\n)+".toRegex()) val b = a.split("(&&|\n)+".toRegex())
b.map { c -> b.forEach { c ->
val d = c.split("::") val d = c.split("::")
if (d.size > 1) if (d.size > 1)
exploreKinds.add(ExploreKind(d[0], d[1])) exploreKinds.add(ExploreKind(d[0], d[1]))

@ -9,6 +9,7 @@ import androidx.room.Entity
) )
data class RssArticle( data class RssArticle(
var origin: String = "", var origin: String = "",
var sort: String = "",
var title: String = "", var title: String = "",
var order: Long = 0, var order: Long = 0,
var link: String = "", var link: String = "",
@ -36,6 +37,7 @@ data class RssArticle(
fun toStar(): RssStar { fun toStar(): RssStar {
return RssStar( return RssStar(
origin = origin, origin = origin,
sort = sort,
title = title, title = title,
starTime = System.currentTimeMillis(), starTime = System.currentTimeMillis(),
link = link, link = link,

@ -23,6 +23,7 @@ data class RssSource(
var sourceIcon: String = "", var sourceIcon: String = "",
var sourceGroup: String? = null, var sourceGroup: String? = null,
var enabled: Boolean = true, var enabled: Boolean = true,
var sortUrl: String? = null,
//列表规则 //列表规则
var ruleArticles: String? = null, var ruleArticles: String? = null,
var ruleNextPage: String? = null, var ruleNextPage: String? = null,
@ -99,4 +100,16 @@ data class RssSource(
return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
} }
fun sortUrls(): LinkedHashMap<String, String> {
val sortMap = linkedMapOf<String, String>()
sortUrl?.split("(&&|\n)+".toRegex())?.forEach { c ->
val d = c.split("::")
if (d.size > 1)
sortMap[d[0]] = d[1]
}
if (sortMap.isEmpty()) {
sortMap[""] = sourceUrl
}
return sortMap
}
} }

@ -9,6 +9,7 @@ import androidx.room.Entity
) )
data class RssStar( data class RssStar(
var origin: String = "", var origin: String = "",
var sort: String = "",
var title: String = "", var title: String = "",
var starTime: Long = 0, var starTime: Long = 0,
var link: String = "", var link: String = "",
@ -20,6 +21,7 @@ data class RssStar(
fun toRssArticle(): RssArticle { fun toRssArticle(): RssArticle {
return RssArticle( return RssArticle(
origin = origin, origin = origin,
sort = sort,
title = title, title = title,
link = link, link = link,
pubDate = pubDate, pubDate = pubDate,

@ -115,13 +115,19 @@ object AppConfig {
App.INSTANCE.putPrefBoolean("bookGroupAudio", value) App.INSTANCE.putPrefBoolean("bookGroupAudio", value)
} }
var bookGroupNoneShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupNone", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupNone", value)
}
var elevation: Int var elevation: Int
get() = App.INSTANCE.getPrefInt("elevation", -1) get() = App.INSTANCE.getPrefInt("elevation", -1)
set(value) { set(value) {
App.INSTANCE.putPrefInt("elevation", value) App.INSTANCE.putPrefInt("elevation", value)
} }
val readBodyToLh: Boolean get() = App.INSTANCE.getPrefBoolean("readBodyToLh", true) val readBodyToLh: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.readBodyToLh, true)
val isGooglePlay: Boolean get() = App.INSTANCE.channel == "google" val isGooglePlay: Boolean get() = App.INSTANCE.channel == "google"
} }

@ -1,6 +1,6 @@
package io.legado.app.help package io.legado.app.help
import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import com.hankcs.hanlp.HanLP
import io.legado.app.App import io.legado.app.App
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
@ -231,12 +231,17 @@ object BookHelp {
if (!c.substringBefore("\n").contains(title)) { if (!c.substringBefore("\n").contains(title)) {
c = "$title\n$c" c = "$title\n$c"
} }
try {
when (AppConfig.chineseConverterType) { when (AppConfig.chineseConverterType) {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = HanLP.convertToSimplifiedChinese(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = HanLP.convertToTraditionalChinese(c)
}
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("简繁转换出错")
}
} }
return c return c
.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") .replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}")
.replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行
} }
} }

@ -27,7 +27,6 @@ object ReadBookConfig {
GSON.fromJsonArray<Config>(json)!! GSON.fromJsonArray<Config>(json)!!
} }
val durConfig get() = getConfig(styleSelect) val durConfig get() = getConfig(styleSelect)
private val shareConfig get() = getConfig(5)
var bg: Drawable? = null var bg: Drawable? = null
var bgMeanColor: Int = 0 var bgMeanColor: Int = 0
@ -139,129 +138,145 @@ object ReadBookConfig {
var bodyIndent = " ".repeat(bodyIndentCount) var bodyIndent = " ".repeat(bodyIndentCount)
var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
private val config get() = if (shareLayout) getConfig(5) else durConfig
var textBold: Boolean var textBold: Boolean
get() = if (shareLayout) shareConfig.textBold else durConfig.textBold get() = config.textBold
set(value) = if (shareLayout) shareConfig.textBold = value else durConfig.textBold = value set(value) {
config.textBold = value
}
var textSize: Int var textSize: Int
get() = if (shareLayout) shareConfig.textSize else durConfig.textSize get() = config.textSize
set(value) = if (shareLayout) shareConfig.textSize = value else durConfig.textSize = value set(value) {
config.textSize = value
}
var letterSpacing: Float var letterSpacing: Float
get() = if (shareLayout) shareConfig.letterSpacing else durConfig.letterSpacing get() = config.letterSpacing
set(value) = set(value) {
if (shareLayout) shareConfig.letterSpacing = value else durConfig.letterSpacing = value config.letterSpacing = value
}
var lineSpacingExtra: Int var lineSpacingExtra: Int
get() = if (shareLayout) shareConfig.lineSpacingExtra else durConfig.lineSpacingExtra get() = config.lineSpacingExtra
set(value) = set(value) {
if (shareLayout) shareConfig.lineSpacingExtra = value config.lineSpacingExtra = value
else durConfig.lineSpacingExtra = value }
var paragraphSpacing: Int var paragraphSpacing: Int
get() = if (shareLayout) shareConfig.paragraphSpacing else durConfig.paragraphSpacing get() = config.paragraphSpacing
set(value) = set(value) {
if (shareLayout) shareConfig.paragraphSpacing = value config.paragraphSpacing = value
else durConfig.paragraphSpacing = value }
var titleMode: Int var titleMode: Int
get() = if (shareLayout) shareConfig.titleMode else durConfig.titleMode get() = config.titleMode
set(value) = set(value) {
if (shareLayout) shareConfig.titleMode = value else durConfig.titleMode = value config.titleMode = value
}
var titleSize: Int var titleSize: Int
get() = if (shareLayout) shareConfig.titleSize else durConfig.titleSize get() = config.titleSize
set(value) = set(value) {
if (shareLayout) shareConfig.titleSize = value else durConfig.titleSize = value config.titleSize = value
}
var titleTopSpacing: Int var titleTopSpacing: Int
get() = if (shareLayout) shareConfig.titleTopSpacing else durConfig.titleTopSpacing get() = config.titleTopSpacing
set(value) = set(value) {
if (shareLayout) shareConfig.titleTopSpacing = value config.titleTopSpacing = value
else durConfig.titleTopSpacing = value }
var titleBottomSpacing: Int var titleBottomSpacing: Int
get() = if (shareLayout) shareConfig.titleBottomSpacing else durConfig.titleBottomSpacing get() = config.titleBottomSpacing
set(value) = set(value) {
if (shareLayout) shareConfig.titleBottomSpacing = value config.titleBottomSpacing = value
else durConfig.titleBottomSpacing = value }
var paddingBottom: Int var paddingBottom: Int
get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom get() = config.paddingBottom
set(value) = set(value) {
if (shareLayout) shareConfig.paddingBottom = value else durConfig.paddingBottom = value config.paddingBottom = value
}
var paddingLeft: Int var paddingLeft: Int
get() = if (shareLayout) shareConfig.paddingLeft else durConfig.paddingLeft get() = config.paddingLeft
set(value) = set(value) {
if (shareLayout) shareConfig.paddingLeft = value else durConfig.paddingLeft = value config.paddingLeft = value
}
var paddingRight: Int var paddingRight: Int
get() = if (shareLayout) shareConfig.paddingRight else durConfig.paddingRight get() = config.paddingRight
set(value) = set(value) {
if (shareLayout) shareConfig.paddingRight = value else durConfig.paddingRight = value config.paddingRight = value
}
var paddingTop: Int var paddingTop: Int
get() = if (shareLayout) shareConfig.paddingTop else durConfig.paddingTop get() = config.paddingTop
set(value) = set(value) {
if (shareLayout) shareConfig.paddingTop = value else durConfig.paddingTop = value config.paddingTop = value
}
var headerPaddingBottom: Int var headerPaddingBottom: Int
get() = if (shareLayout) shareConfig.headerPaddingBottom else durConfig.headerPaddingBottom get() = config.headerPaddingBottom
set(value) = set(value) {
if (shareLayout) shareConfig.headerPaddingBottom = value config.headerPaddingBottom = value
else durConfig.headerPaddingBottom = value }
var headerPaddingLeft: Int var headerPaddingLeft: Int
get() = if (shareLayout) shareConfig.headerPaddingLeft else durConfig.headerPaddingLeft get() = config.headerPaddingLeft
set(value) = set(value) {
if (shareLayout) shareConfig.headerPaddingLeft = value config.headerPaddingLeft = value
else durConfig.headerPaddingLeft = value }
var headerPaddingRight: Int var headerPaddingRight: Int
get() = if (shareLayout) shareConfig.headerPaddingRight else durConfig.headerPaddingRight get() = config.headerPaddingRight
set(value) = set(value) {
if (shareLayout) shareConfig.headerPaddingRight = value config.headerPaddingRight = value
else durConfig.headerPaddingRight = value }
var headerPaddingTop: Int var headerPaddingTop: Int
get() = if (shareLayout) shareConfig.headerPaddingTop else durConfig.headerPaddingTop get() = config.headerPaddingTop
set(value) = set(value) {
if (shareLayout) shareConfig.headerPaddingTop = value config.headerPaddingTop = value
else durConfig.headerPaddingTop = value }
var footerPaddingBottom: Int var footerPaddingBottom: Int
get() = if (shareLayout) shareConfig.footerPaddingBottom else durConfig.footerPaddingBottom get() = config.footerPaddingBottom
set(value) = set(value) {
if (shareLayout) shareConfig.footerPaddingBottom = value config.footerPaddingBottom = value
else durConfig.footerPaddingBottom = value }
var footerPaddingLeft: Int var footerPaddingLeft: Int
get() = if (shareLayout) shareConfig.footerPaddingLeft else durConfig.footerPaddingLeft get() = config.footerPaddingLeft
set(value) = set(value) {
if (shareLayout) shareConfig.footerPaddingLeft = value config.footerPaddingLeft = value
else durConfig.footerPaddingLeft = value }
var footerPaddingRight: Int var footerPaddingRight: Int
get() = if (shareLayout) shareConfig.footerPaddingRight else durConfig.footerPaddingRight get() = config.footerPaddingRight
set(value) = set(value) {
if (shareLayout) shareConfig.footerPaddingRight = value config.footerPaddingRight = value
else durConfig.footerPaddingRight = value }
var footerPaddingTop: Int var footerPaddingTop: Int
get() = if (shareLayout) shareConfig.footerPaddingTop else durConfig.footerPaddingTop get() = config.footerPaddingTop
set(value) = set(value) {
if (shareLayout) shareConfig.footerPaddingTop = value config.footerPaddingTop = value
else durConfig.footerPaddingTop = value }
var showHeaderLine: Boolean var showHeaderLine: Boolean
get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine get() = config.showHeaderLine
set(value) = set(value) {
if (shareLayout) shareConfig.showHeaderLine = value config.showHeaderLine = value
else durConfig.showHeaderLine = value }
var showFooterLine: Boolean var showFooterLine: Boolean
get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine get() = config.showFooterLine
set(value) = set(value) {
if (shareLayout) shareConfig.showFooterLine = value config.showFooterLine = value
else durConfig.showFooterLine = value }
@Keep @Keep
class Config( class Config(

@ -0,0 +1,74 @@
package io.legado.app.help
import io.legado.app.App
import io.legado.app.R
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.putPrefInt
object ReadTipConfig {
val tipArray: Array<String> = App.INSTANCE.resources.getStringArray(R.array.read_tip)
const val none = 0
const val chapterTitle = 1
const val time = 2
const val battery = 3
const val page = 4
const val totalProgress = 5
const val pageAndTotal = 6
val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] }
val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] }
val tipHeaderRightStr: String get() = tipArray.getOrElse(tipHeaderRight) { tipArray[none] }
val tipFooterLeftStr: String get() = tipArray.getOrElse(tipFooterLeft) { tipArray[none] }
val tipFooterMiddleStr: String get() = tipArray.getOrElse(tipFooterMiddle) { tipArray[none] }
val tipFooterRightStr: String get() = tipArray.getOrElse(tipFooterRight) { tipArray[none] }
var tipHeaderLeft: Int
get() = App.INSTANCE.getPrefInt("tipHeaderLeft", time)
set(value) {
App.INSTANCE.putPrefInt("tipHeaderLeft", value)
}
var tipHeaderMiddle: Int
get() = App.INSTANCE.getPrefInt("tipHeaderMiddle", none)
set(value) {
App.INSTANCE.putPrefInt("tipHeaderMiddle", value)
}
var tipHeaderRight: Int
get() = App.INSTANCE.getPrefInt("tipHeaderRight", battery)
set(value) {
App.INSTANCE.putPrefInt("tipHeaderRight", value)
}
var tipFooterLeft: Int
get() = App.INSTANCE.getPrefInt("tipFooterLeft", chapterTitle)
set(value) {
App.INSTANCE.putPrefInt("tipFooterLeft", value)
}
var tipFooterMiddle: Int
get() = App.INSTANCE.getPrefInt("tipFooterMiddle", none)
set(value) {
App.INSTANCE.putPrefInt("tipFooterMiddle", value)
}
var tipFooterRight: Int
get() = App.INSTANCE.getPrefInt("tipFooterRight", pageAndTotal)
set(value) {
App.INSTANCE.putPrefInt("tipFooterRight", value)
}
var hideHeader: Boolean
get() = App.INSTANCE.getPrefBoolean("hideHeader", true)
set(value) {
App.INSTANCE.putPrefBoolean("hideHeader", value)
}
var hideFooter: Boolean
get() = App.INSTANCE.getPrefBoolean("hideFooter", false)
set(value) {
App.INSTANCE.putPrefBoolean("hideFooter", value)
}
}

@ -180,6 +180,7 @@ class AjaxWebView {
mWebView.get()?.evaluateJavascript(mJavaScript) { mWebView.get()?.evaluateJavascript(mJavaScript) {
if (it.isNotEmpty() && it != "null") { if (it.isNotEmpty() && it != "null") {
val content = StringEscapeUtils.unescapeJson(it) val content = StringEscapeUtils.unescapeJson(it)
.replace("^\"|\"$".toRegex(), "")
handler.obtainMessage(MSG_SUCCESS, Res(url, content)) handler.obtainMessage(MSG_SUCCESS, Res(url, content))
.sendToTarget() .sendToTarget()
handler.removeCallbacks(this) handler.removeCallbacks(this)

@ -32,9 +32,7 @@ object Backup {
val lastBackup = context.getPrefLong(PreferKey.lastBackup) val lastBackup = context.getPrefLong(PreferKey.lastBackup)
if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) { if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) {
Coroutine.async { Coroutine.async {
context.getPrefString(PreferKey.backupPath)?.let { backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true)
backup(context, it, true)
}
} }
} }
} }
@ -70,12 +68,16 @@ object Backup {
WebDavHelp.backUpWebDav(backupPath) WebDavHelp.backUpWebDav(backupPath)
if (path.isContentPath()) { if (path.isContentPath()) {
copyBackup(context, Uri.parse(path), isAuto) copyBackup(context, Uri.parse(path), isAuto)
} else {
if (path.isEmpty()) {
copyBackup(context.getExternalFilesDir(null)!!, false)
} else { } else {
copyBackup(File(path), isAuto) copyBackup(File(path), isAuto)
} }
} }
} }
} }
}
private fun writeListToJson(list: List<Any>, fileName: String, path: String) { private fun writeListToJson(list: List<Any>, fileName: String, path: String) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {

@ -19,7 +19,7 @@ object OldRule {
source = GSON.fromJsonObject<BookSource>(json.trim()) source = GSON.fromJsonObject<BookSource>(json.trim())
} }
runCatching { runCatching {
if (source == null || source?.searchUrl.isNullOrBlank()) { if (source == null || source?.ruleToc.isNullOrBlank()) {
source = BookSource().apply { source = BookSource().apply {
val jsonItem = jsonPath.parse(json.trim()) val jsonItem = jsonPath.parse(json.trim())
bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: "" bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: ""

@ -54,7 +54,8 @@ object Debug {
cancelDebug() cancelDebug()
debugSource = rssSource.sourceUrl debugSource = rssSource.sourceUrl
log(debugSource, "︾开始解析") log(debugSource, "︾开始解析")
Rss.getArticles(rssSource, null) val sort = rssSource.sortUrls().entries.first()
Rss.getArticles(sort.key, sort.value, rssSource, null)
.onSuccess { .onSuccess {
if (it.articles.isEmpty()) { if (it.articles.isEmpty()) {
log(debugSource, "⇒列表页解析成功,为空") log(debugSource, "⇒列表页解析成功,为空")

@ -15,6 +15,8 @@ import kotlin.coroutines.CoroutineContext
object Rss { object Rss {
fun getArticles( fun getArticles(
sortName: String,
sortUrl: String,
rssSource: RssSource, rssSource: RssSource,
pageUrl: String? = null, pageUrl: String? = null,
scope: CoroutineScope = Coroutine.DEFAULT, scope: CoroutineScope = Coroutine.DEFAULT,
@ -22,11 +24,11 @@ object Rss {
): Coroutine<Result> { ): Coroutine<Result> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
val analyzeUrl = AnalyzeUrl( val analyzeUrl = AnalyzeUrl(
pageUrl ?: rssSource.sourceUrl, pageUrl ?: sortUrl,
headerMapF = rssSource.getHeaderMap() headerMapF = rssSource.getHeaderMap()
) )
val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body
RssParserByRule.parseXML(body, rssSource) RssParserByRule.parseXML(sortName, sortUrl, body, rssSource)
} }
} }

@ -18,6 +18,7 @@ class AnalyzeTxtFile {
private val tocRules = arrayListOf<TxtTocRule>() private val tocRules = arrayListOf<TxtTocRule>()
private lateinit var charset: Charset private lateinit var charset: Charset
@Throws(Exception::class)
fun analyze(context: Context, book: Book): ArrayList<BookChapter> { fun analyze(context: Context, book: Book): ArrayList<BookChapter> {
val bookFile = getBookFile(context, book) val bookFile = getBookFile(context, book)
book.charset = EncodingDetect.getEncode(bookFile) book.charset = EncodingDetect.getEncode(bookFile)
@ -33,6 +34,7 @@ class AnalyzeTxtFile {
return analyze(bookStream, book, rulePattern) return analyze(bookStream, book, rulePattern)
} }
@Throws(Exception::class)
private fun analyze( private fun analyze(
bookStream: RandomAccessFile, bookStream: RandomAccessFile,
book: Book, book: Book,
@ -58,9 +60,12 @@ class AnalyzeTxtFile {
var allLength = 0 var allLength = 0
//获取文件中的数据到buffer,直到没有数据为止 //获取文件中的数据到buffer,直到没有数据为止
while (bookStream.read(buffer, 0, buffer.size).also { length = it } > 0) { while (bookStream.read(buffer).also { length = it } > 0) {
blockPos++ blockPos++
var blockContent = String(buffer, charset) //如果存在Chapter
if (rulePattern != null) {
//将数据转换成String, 不能超过length
var blockContent = String(buffer, 0, length, charset)
val lastN = blockContent.lastIndexOf("\n") val lastN = blockContent.lastIndexOf("\n")
if (lastN > 0) { if (lastN > 0) {
blockContent = blockContent.substring(0, lastN) blockContent = blockContent.substring(0, lastN)
@ -68,8 +73,6 @@ class AnalyzeTxtFile {
allLength += length allLength += length
bookStream.seek(allLength.toLong()) bookStream.seek(allLength.toLong())
} }
//如果存在Chapter
if (rulePattern != null) { //将数据转换成String
//当前Block下使过的String的指针 //当前Block下使过的String的指针
var seekPos = 0 var seekPos = 0
//进行正则匹配 //进行正则匹配
@ -79,35 +82,59 @@ class AnalyzeTxtFile {
val chapterStart = matcher.start() val chapterStart = matcher.start()
//获取章节内容 //获取章节内容
val chapterContent = blockContent.substring(seekPos, chapterStart) val chapterContent = blockContent.substring(seekPos, chapterStart)
if (chapterContent.length > 30000 && pattern == null) { val chapterLength = chapterContent.toByteArray(charset).size
if (chapterLength > 30000 && pattern == null) {
//移除不匹配的规则 //移除不匹配的规则
tocRules.remove(tocRule) tocRules.remove(tocRule)
return analyze(bookStream, book, null) return analyze(bookStream, book, null)
} }
//如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容
//第一种情况一定是序章 第二种情况可能是上一个章节的内容 //第一种情况一定是序章 第二种情况是上一个章节的内容
if (seekPos == 0 && chapterStart != 0 && toc.isEmpty()) { //获取当前章节的内容 if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容
val chapter = BookChapter() if (toc.isEmpty()) { //如果当前没有章节,那么就是序章
chapter.title = "前言" //加入简介
chapter.start = 0 if (StringUtils.trim(chapterContent).isNotEmpty()) {
chapter.end = chapterContent.toByteArray(charset).size.toLong() val qyChapter = BookChapter()
toc.add(chapter) qyChapter.title = "前言"
qyChapter.start = 0
qyChapter.end = chapterLength.toLong()
toc.add(qyChapter)
}
//创建当前章节 //创建当前章节
val curChapter = BookChapter() val curChapter = BookChapter()
curChapter.title = matcher.group() curChapter.title = matcher.group()
curChapter.start = chapter.end curChapter.start = chapterLength.toLong()
toc.add(curChapter)
} else { //否则就block分割之后,上一个章节的剩余内容
//获取上一章节
val lastChapter = toc.last()
//将当前段落添加上一章去
lastChapter.end =
lastChapter.end!! + chapterLength.toLong()
//创建当前章节
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = lastChapter.end
toc.add(curChapter) toc.add(curChapter)
} else {
val lastChapter = toc.lastOrNull()
lastChapter?.let {
//上一章节结束等于这一章节开始
it.end = it.start!! + chapterContent.toByteArray(charset).size
} }
} else {
if (toc.isNotEmpty()) { //获取章节内容
//获取上一章节
val lastChapter = toc.last()
lastChapter.end =
lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong()
//创建当前章节 //创建当前章节
val curChapter = BookChapter() val curChapter = BookChapter()
curChapter.title = matcher.group() curChapter.title = matcher.group()
curChapter.start = lastChapter?.end ?: 0 curChapter.start = lastChapter.end
toc.add(curChapter) toc.add(curChapter)
} else { //如果章节不存在则创建章节
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = 0
curChapter.end = 0
toc.add(curChapter)
}
} }
//设置指针偏移 //设置指针偏移
seekPos += chapterContent.length seekPos += chapterContent.length
@ -154,7 +181,8 @@ class AnalyzeTxtFile {
//block的偏移点 //block的偏移点
curOffset += length.toLong() curOffset += length.toLong()
if (rulePattern != null) { //设置上一章的结尾 if (rulePattern != null) {
//设置上一章的结尾
val lastChapter = toc.last() val lastChapter = toc.last()
lastChapter.end = curOffset lastChapter.end = curOffset
} }
@ -173,6 +201,7 @@ class AnalyzeTxtFile {
bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title) ?: "") bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title) ?: "")
} }
book.latestChapterTitle = toc.last().title book.latestChapterTitle = toc.last().title
book.totalChapterNum = toc.size
System.gc() System.gc()
System.runFinalization() System.runFinalization()
@ -220,10 +249,9 @@ class AnalyzeTxtFile {
val bookFile = getBookFile(App.INSTANCE, book) val bookFile = getBookFile(App.INSTANCE, book)
//获取文件流 //获取文件流
val bookStream = RandomAccessFile(bookFile, "r") val bookStream = RandomAccessFile(bookFile, "r")
bookStream.seek(bookChapter.start ?: 0) val content = ByteArray((bookChapter.end!! - bookChapter.start!!).toInt())
val extent = (bookChapter.end!! - bookChapter.start!!).toInt() bookStream.seek(bookChapter.start!!)
val content = ByteArray(extent) bookStream.read(content)
bookStream.read(content, 0, extent)
return String(content, book.fileCharset()) return String(content, book.fileCharset())
} }

@ -11,7 +11,7 @@ import java.io.StringReader
object RssParser { object RssParser {
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
fun parseXML(xml: String, sourceUrl: String): Result { fun parseXML(sortName: String, xml: String, sourceUrl: String): Result {
val articleList = mutableListOf<RssArticle>() val articleList = mutableListOf<RssArticle>()
var currentArticle = RssArticle() var currentArticle = RssArticle()
@ -87,6 +87,7 @@ object RssParser {
// The item is correctly parsed // The item is correctly parsed
insideItem = false insideItem = false
currentArticle.origin = sourceUrl currentArticle.origin = sourceUrl
currentArticle.sort = sortName
articleList.add(currentArticle) articleList.add(currentArticle)
currentArticle = RssArticle() currentArticle = RssArticle()
} }

@ -13,7 +13,7 @@ import io.legado.app.utils.NetworkUtils
object RssParserByRule { object RssParserByRule {
@Throws(Exception::class) @Throws(Exception::class)
fun parseXML(body: String?, rssSource: RssSource): Result { fun parseXML(sortName: String, sortUrl: String, body: String?, rssSource: RssSource): Result {
val sourceUrl = rssSource.sourceUrl val sourceUrl = rssSource.sourceUrl
var nextUrl: String? = null var nextUrl: String? = null
if (body.isNullOrBlank()) { if (body.isNullOrBlank()) {
@ -28,11 +28,11 @@ object RssParserByRule {
var ruleArticles = rssSource.ruleArticles var ruleArticles = rssSource.ruleArticles
if (ruleArticles.isNullOrBlank()) { if (ruleArticles.isNullOrBlank()) {
Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析") Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析")
return RssParser.parseXML(body, sourceUrl) return RssParser.parseXML(sortName, body, sourceUrl)
} else { } else {
val articleList = mutableListOf<RssArticle>() val articleList = mutableListOf<RssArticle>()
val analyzeRule = AnalyzeRule() val analyzeRule = AnalyzeRule()
analyzeRule.setContent(body, rssSource.sourceUrl) analyzeRule.setContent(body, sortUrl)
var reverse = false var reverse = false
if (ruleArticles.startsWith("-")) { if (ruleArticles.startsWith("-")) {
reverse = true reverse = true
@ -45,7 +45,7 @@ object RssParserByRule {
Debug.log(sourceUrl, "┌获取下一页链接") Debug.log(sourceUrl, "┌获取下一页链接")
nextUrl = analyzeRule.getString(rssSource.ruleNextPage) nextUrl = analyzeRule.getString(rssSource.ruleNextPage)
if (nextUrl.isNotEmpty()) { if (nextUrl.isNotEmpty()) {
nextUrl = NetworkUtils.getAbsoluteURL(sourceUrl, nextUrl) nextUrl = NetworkUtils.getAbsoluteURL(sortUrl, nextUrl)
} }
Debug.log(sourceUrl, "$nextUrl") Debug.log(sourceUrl, "$nextUrl")
} }
@ -59,7 +59,8 @@ object RssParserByRule {
sourceUrl, item, analyzeRule, index == 0, sourceUrl, item, analyzeRule, index == 0,
ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink
)?.let { )?.let {
it.origin = rssSource.sourceUrl it.sort = sortName
it.origin = sourceUrl
articleList.add(it) articleList.add(it)
} }
} }

@ -193,6 +193,7 @@ object BookChapterList {
list.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle list.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle
if (book.totalChapterNum < list.size) { if (book.totalChapterNum < list.size) {
book.lastCheckCount = list.size - book.totalChapterNum book.lastCheckCount = list.size - book.totalChapterNum
book.latestChapterTime = System.currentTimeMillis()
} }
book.totalChapterNum = list.size book.totalChapterNum = list.size
return list return list

@ -101,6 +101,7 @@ object BookContent {
} }
} }
content.deleteCharAt(content.length - 1)
Debug.log(bookSource.bookSourceUrl, "┌获取章节名称") Debug.log(bookSource.bookSourceUrl, "┌获取章节名称")
Debug.log(bookSource.bookSourceUrl, "${bookChapter.title}") Debug.log(bookSource.bookSourceUrl, "${bookChapter.title}")
Debug.log(bookSource.bookSourceUrl, "┌获取正文内容") Debug.log(bookSource.bookSourceUrl, "┌获取正文内容")

@ -8,9 +8,14 @@ import io.legado.app.App
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.help.ActivityHelp import io.legado.app.help.ActivityHelp
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.ui.audio.AudioPlayActivity import io.legado.app.ui.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 kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@ -50,11 +55,23 @@ class MediaButtonReceiver : BroadcastReceiver() {
private fun readAloud(context: Context) { private fun readAloud(context: Context) {
when { when {
BaseReadAloudService.isRun -> if (BaseReadAloudService.isPlay()) {
ReadAloud.pause(context)
AudioPlay.pause(context)
} else {
ReadAloud.resume(context)
AudioPlay.resume(context)
}
AudioPlayService.isRun -> if (AudioPlayService.pause) {
AudioPlay.resume(context)
} else {
AudioPlay.pause(context)
}
ActivityHelp.isExist(AudioPlayActivity::class.java) -> ActivityHelp.isExist(AudioPlayActivity::class.java) ->
postEvent(EventBus.MEDIA_BUTTON, true) postEvent(EventBus.MEDIA_BUTTON, true)
ActivityHelp.isExist(ReadBookActivity::class.java) -> ActivityHelp.isExist(ReadBookActivity::class.java) ->
postEvent(EventBus.MEDIA_BUTTON, true) postEvent(EventBus.MEDIA_BUTTON, true)
else -> { else -> if (context.getPrefBoolean("mediaButtonOnExit", true)) {
GlobalScope.launch(Main) { GlobalScope.launch(Main) {
val lastBook: Book? = withContext(IO) { val lastBook: Book? = withContext(IO) {
App.db.bookDao().lastReadBook App.db.bookDao().lastReadBook

@ -7,20 +7,22 @@ import io.legado.app.R
import io.legado.app.base.BaseService import io.legado.app.base.BaseService
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.entities.BookSource
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.CompositeCoroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min
class CheckSourceService : BaseService() { class CheckSourceService : BaseService() {
private val threadCount = AppConfig.threadCount private val threadCount = AppConfig.threadCount
private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
private var task: Coroutine<*>? = null private var tasks = CompositeCoroutine()
private val allIds = ArrayList<String>() private val allIds = ArrayList<String>()
private val checkedIds = ArrayList<String>() private val checkedIds = ArrayList<String>()
private var processIndex = 0 private var processIndex = 0
@ -42,24 +44,24 @@ class CheckSourceService : BaseService() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
task?.cancel() tasks.clear()
searchPool.close() searchPool.close()
} }
private fun check(ids: List<String>) { private fun check(ids: List<String>) {
task?.cancel() if (allIds.isNotEmpty()) {
toast("已有书源在校验,等完成后再试")
return
}
tasks.clear()
allIds.clear() allIds.clear()
checkedIds.clear() checkedIds.clear()
allIds.addAll(ids) allIds.addAll(ids)
processIndex = 0 processIndex = 0
updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) updateNotification(0, getString(R.string.progress_show, 0, allIds.size))
task = execute { for (i in 0 until min(threadCount, allIds.size)) {
for (i in 0 until threadCount) {
check() check()
} }
}.onError {
toast("校验书源出错:${it.localizedMessage}")
}
} }
@ -67,30 +69,44 @@ class CheckSourceService : BaseService() {
synchronized(this) { synchronized(this) {
processIndex++ processIndex++
} }
execute {
if (processIndex < allIds.size) { if (processIndex < allIds.size) {
val sourceUrl = allIds[processIndex] val sourceUrl = allIds[processIndex]
App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source ->
if (source.searchUrl.isNullOrEmpty()) {
onNext(sourceUrl)
} else {
check(source)
}
} ?: onNext(sourceUrl)
}
}
}
private fun check(source: BookSource) {
val webBook = WebBook(source) val webBook = WebBook(source)
webBook.searchBook("我的", scope = this, context = searchPool) tasks.add(webBook.searchBook("我的", scope = this, context = searchPool)
.onError(IO) { .onError(IO) {
source.addGroup("失效") source.addGroup("失效")
App.db.bookSourceDao().update(source) App.db.bookSourceDao().update(source)
}.onFinally(IO) { }.onFinally(IO) {
onNext(source.bookSourceUrl)
})
}
private fun onNext(sourceUrl: String) {
synchronized(this) {
check() check()
checkedIds.add(sourceUrl) checkedIds.add(sourceUrl)
updateNotification( updateNotification(
checkedIds.size, checkedIds.size,
getString(R.string.progress_show, checkedIds.size, allIds.size) getString(R.string.progress_show, checkedIds.size, allIds.size)
) )
synchronized(this) { if (processIndex >= allIds.size + min(threadCount, allIds.size) - 1) {
if (processIndex >= allIds.size + threadCount - 1) {
stopSelf() stopSelf()
} }
} }
} }
}
}
}
/** /**
* 更新通知 * 更新通知

@ -13,6 +13,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
@ -25,7 +26,7 @@ import java.util.concurrent.Executors
class DownloadService : BaseService() { class DownloadService : BaseService() {
private var searchPool = private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var tasks: ArrayList<Coroutine<*>> = arrayListOf() private var tasks = CompositeCoroutine()
private val handler = Handler() private val handler = Handler()
private var runnable: Runnable = Runnable { upDownload() } private var runnable: Runnable = Runnable { upDownload() }
private val downloadMap = hashMapOf<String, LinkedHashSet<BookChapter>>() private val downloadMap = hashMapOf<String, LinkedHashSet<BookChapter>>()
@ -125,11 +126,7 @@ class DownloadService : BaseService() {
chapter, chapter,
scope = this, scope = this,
context = searchPool context = searchPool
) ).onSuccess(IO) { content ->
//.onStart {
// notificationContent = "启动:" + chapter.title
//}
.onSuccess(IO) { content ->
downloadCount[entry.key]?.increaseSuccess() downloadCount[entry.key]?.increaseSuccess()
BookHelp.saveContent(book, chapter, content) BookHelp.saveContent(book, chapter, content)
} }
@ -165,7 +162,7 @@ class DownloadService : BaseService() {
tasks.add(task) tasks.add(task)
task.invokeOnCompletion { task.invokeOnCompletion {
tasks.remove(task) tasks.remove(task)
if (tasks.isEmpty()) { if (tasks.isEmpty) {
stopSelf() stopSelf()
} }
} }
@ -192,11 +189,14 @@ class DownloadService : BaseService() {
val notification = builder.build() val notification = builder.build()
startForeground(AppConst.notificationIdDownload, notification) startForeground(AppConst.notificationIdDownload, notification)
} }
}
class DownloadCount { class DownloadCount {
@Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量 @Volatile
@Volatile public var successCount = 0 //下载成功的条目数量 var downloadFinishedCount = 0 // 下载完成的条目数量
@Volatile
var successCount = 0 //下载成功的条目数量
fun increaseSuccess() { fun increaseSuccess() {
++successCount; ++successCount;
@ -206,3 +206,4 @@ class DownloadCount{
++downloadFinishedCount; ++downloadFinishedCount;
} }
} }
}

@ -331,7 +331,7 @@ object ReadBook {
book.durChapterTime = System.currentTimeMillis() book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex book.durChapterIndex = durChapterIndex
book.durChapterPos = durPageIndex book.durChapterPos = durPageIndex
curTextChapter?.let { App.db.bookChapterDao().getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.title book.durChapterTitle = it.title
} }
App.db.bookDao().update(book) App.db.bookDao().update(book)

@ -22,6 +22,7 @@ class AboutFragment : PreferenceFragmentCompat() {
private val qqGroups = linkedMapOf( private val qqGroups = linkedMapOf(
Pair("(QQ群VIP1)701903217", "-iolizL4cbJSutKRpeImHlXlpLDZnzeF"), Pair("(QQ群VIP1)701903217", "-iolizL4cbJSutKRpeImHlXlpLDZnzeF"),
Pair("(QQ群VIP2)263949160", "xwfh7_csb2Gf3Aw2qexEcEtviLfLfd4L"), Pair("(QQ群VIP2)263949160", "xwfh7_csb2Gf3Aw2qexEcEtviLfLfd4L"),
Pair("(QQ群VIP3)680280282", "_N0i7yZObjKSeZQvzoe2ej7j02kLnOOK"),
Pair("(QQ群1)805192012", "6GlFKjLeIk5RhQnR3PNVDaKB6j10royo"), Pair("(QQ群1)805192012", "6GlFKjLeIk5RhQnR3PNVDaKB6j10royo"),
Pair("(QQ群2)773736122", "5Bm5w6OgLupXnICbYvbgzpPUgf0UlsJF"), Pair("(QQ群2)773736122", "5Bm5w6OgLupXnICbYvbgzpPUgf0UlsJF"),
Pair("(QQ群3)981838750", "g_Sgmp2nQPKqcZQ5qPcKLHziwX_mpps9"), Pair("(QQ群3)981838750", "g_Sgmp2nQPKqcZQ5qPcKLHziwX_mpps9"),

@ -11,7 +11,6 @@ import io.legado.app.help.BookHelp
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AudioPlayViewModel(application: Application) : BaseViewModel(application) { class AudioPlayViewModel(application: Application) : BaseViewModel(application) {
@ -93,9 +92,6 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
AudioPlay.book?.let { AudioPlay.book?.let {
book1.order = it.order book1.order = it.order
App.db.bookDao().delete(it) App.db.bookDao().delete(it)
}
withContext(Dispatchers.Main) {
} }
App.db.bookDao().insert(book1) App.db.bookDao().insert(book1)
AudioPlay.book = book1 AudioPlay.book = book1

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppConst
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
@ -105,10 +106,10 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
booksLiveData?.removeObservers(this) booksLiveData?.removeObservers(this)
booksLiveData = booksLiveData =
when (groupId) { when (groupId) {
-1 -> App.db.bookDao().observeAll() AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll()
-2 -> App.db.bookDao().observeLocal() AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal()
-3 -> App.db.bookDao().observeAudio() AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio()
-11 -> App.db.bookDao().observeNoGroup() AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
booksLiveData?.observe(this, Observer { list -> booksLiveData?.observe(this, Observer { list ->
@ -129,22 +130,22 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
.show(supportFragmentManager, "groupManage") .show(supportFragmentManager, "groupManage")
R.id.menu_no_group -> { R.id.menu_no_group -> {
title_bar.subtitle = getString(R.string.no_group) title_bar.subtitle = getString(R.string.no_group)
groupId = -11 groupId = AppConst.bookGroupNone.groupId
initBookData() initBookData()
} }
R.id.menu_all -> { R.id.menu_all -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -1 groupId = AppConst.bookGroupAll.groupId
initBookData() initBookData()
} }
R.id.menu_local -> { R.id.menu_local -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -2 groupId = AppConst.bookGroupLocal.groupId
initBookData() initBookData()
} }
R.id.menu_audio -> { R.id.menu_audio -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -3 groupId = AppConst.bookGroupAudio.groupId
initBookData() initBookData()
} }
else -> if (item.groupId == R.id.menu_group) { else -> if (item.groupId == R.id.menu_group) {

@ -1,5 +1,6 @@
package io.legado.app.ui.book.chapterlist package io.legado.app.ui.book.chapterlist
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -13,7 +14,7 @@ import io.legado.app.constant.EventBus
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.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.bottomBackground
import io.legado.app.ui.widget.recycler.UpLinearLayoutManager import io.legado.app.ui.widget.recycler.UpLinearLayoutManager
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
@ -54,7 +55,7 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
} }
private fun initView() { private fun initView() {
ll_chapter_base_info.setBackgroundColor(backgroundColor) ll_chapter_base_info.setBackgroundColor(bottomBackground)
iv_chapter_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } iv_chapter_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) }
iv_chapter_bottom.onClick { iv_chapter_bottom.onClick {
if (adapter.itemCount > 0) { if (adapter.itemCount > 0) {
@ -66,6 +67,7 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
} }
} }
@SuppressLint("SetTextI18n")
private fun initBook() { private fun initBook() {
launch { launch {
withContext(IO) { withContext(IO) {
@ -74,7 +76,8 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
initDoc() initDoc()
book?.let { book?.let {
durChapterIndex = it.durChapterIndex durChapterIndex = it.durChapterIndex
tv_current_chapter_info.text = it.durChapterTitle tv_current_chapter_info.text =
"${it.durChapterTitle}(${it.durChapterIndex + 1}/${it.totalChapterNum})"
initCacheFileNames(it) initCacheFileNames(it)
} }
} }

@ -99,6 +99,8 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
.isChecked = AppConfig.bookGroupLocalShow .isChecked = AppConfig.bookGroupLocalShow
it.findItem(R.id.menu_group_audio) it.findItem(R.id.menu_group_audio)
.isChecked = AppConfig.bookGroupAudioShow .isChecked = AppConfig.bookGroupAudioShow
it.findItem(R.id.menu_group_none)
.isChecked = AppConfig.bookGroupNoneShow
} }
} }
@ -120,6 +122,10 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
AppConfig.bookGroupAudioShow = item.isChecked AppConfig.bookGroupAudioShow = item.isChecked
callBack?.upGroup() callBack?.upGroup()
} }
R.id.menu_group_none -> {
item.isChecked = !item.isChecked
AppConfig.bookGroupNoneShow = item.isChecked
}
} }
return true return true
} }

@ -94,8 +94,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
tool_bar.inflateMenu(R.menu.book_group_manage) tool_bar.inflateMenu(R.menu.book_group_manage)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_group_local).isVisible = false tool_bar.menu.setGroupVisible(R.id.menu_groups, false)
tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.addItemDecoration(VerticalDivider(requireContext()))

@ -112,6 +112,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
toast(R.string.error_no_source) toast(R.string.error_no_source)
} }
} }
}.onError {
toast("LoadTocError:${it.localizedMessage}")
} }
} }
@ -156,8 +158,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
chapters chapters
) )
book.durChapterTitle = chapters[book.durChapterIndex].title book.durChapterTitle = chapters[book.durChapterIndex].title
if (inBookshelf) {
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
App.db.bookChapterDao().insert(*chapters.toTypedArray()) App.db.bookChapterDao().insert(*chapters.toTypedArray())
}
bookData.postValue(book) bookData.postValue(book)
chapterListData.postValue(chapters) chapterListData.postValue(chapters)
} }
@ -169,6 +173,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
if (book.order == 0) { if (book.order == 0) {
book.order = App.db.bookDao().maxOrder + 1 book.order = App.db.bookDao().maxOrder + 1
} }
App.db.bookDao().getBook(book.name, book.author)?.let {
book.durChapterPos = it.durChapterPos
book.durChapterTitle = it.durChapterTitle
}
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
} }
}.onSuccess { }.onSuccess {
@ -192,6 +200,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
if (book.order == 0) { if (book.order == 0) {
book.order = App.db.bookDao().maxOrder + 1 book.order = App.db.bookDao().maxOrder + 1
} }
App.db.bookDao().getBook(book.name, book.author)?.let {
book.durChapterPos = it.durChapterPos
book.durChapterTitle = it.durChapterTitle
}
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
} }
chapterListData.value?.let { chapterListData.value?.let {

@ -134,6 +134,10 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
timeBatteryReceiver = null timeBatteryReceiver = null
} }
upSystemUiVisibility() upSystemUiVisibility()
if (!BuildConfig.DEBUG) {
SyncBookProgress.uploadBookProgress()
Backup.autoBack(this)
}
} }
/** /**
@ -225,6 +229,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
R.id.menu_copy_text -> R.id.menu_copy_text ->
TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent()) TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent())
R.id.menu_update_toc -> ReadBook.book?.let { R.id.menu_update_toc -> ReadBook.book?.let {
ReadBook.upMsg(getString(R.string.toc_updateing))
viewModel.loadChapterList(it) viewModel.loadChapterList(it)
} }
R.id.menu_enable_replace -> ReadBook.book?.let { R.id.menu_enable_replace -> ReadBook.book?.let {
@ -705,6 +710,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
observeEvent<Boolean>(EventBus.UP_CONFIG) { observeEvent<Boolean>(EventBus.UP_CONFIG) {
upSystemUiVisibility() upSystemUiVisibility()
page_view.upBg() page_view.upBg()
page_view.upTipStyle()
page_view.upStyle() page_view.upStyle()
if (it) { if (it) {
ReadBook.loadContent(resetPageOffset = false) ReadBook.loadContent(resetPageOffset = false)

@ -107,6 +107,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookChapterDao().insert(*it.toTypedArray())
App.db.bookDao().update(book) App.db.bookDao().update(book)
ReadBook.chapterSize = it.size ReadBook.chapterSize = it.size
ReadBook.upMsg(null)
ReadBook.loadContent(resetPageOffset = true) ReadBook.loadContent(resetPageOffset = true)
} }
} else { } else {
@ -129,6 +130,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.upMsg(context.getString(R.string.error_load_toc)) ReadBook.upMsg(context.getString(R.string.error_load_toc))
} }
} }
}.onError {
ReadBook.upMsg("LoadTocError:${it.localizedMessage}")
} }
} }
@ -175,7 +178,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
} }
}.onStart { }.onStart {
ReadBook.upMsg("正在自动换源") ReadBook.upMsg(context.getString(R.string.source_auto_changing))
}.onFinally { }.onFinally {
ReadBook.upMsg(null) ReadBook.upMsg(null)
} }

@ -94,6 +94,7 @@ class MoreConfigDialog : DialogFragment() {
key: String? key: String?
) { ) {
when (key) { when (key) {
PreferKey.readBodyToLh -> activity?.recreate()
PreferKey.hideStatusBar -> { PreferKey.hideStatusBar -> {
ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)

@ -11,10 +11,7 @@ import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.utils.dp
import io.legado.app.utils.gone
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_read_padding.* import kotlinx.android.synthetic.main.dialog_read_padding.*
class PaddingConfigDialog : DialogFragment() { class PaddingConfigDialog : DialogFragment() {
@ -54,13 +51,6 @@ class PaddingConfigDialog : DialogFragment() {
} }
private fun initData() = ReadBookConfig.apply { private fun initData() = ReadBookConfig.apply {
if (hideStatusBar) {
ll_header_padding.visible()
tv_body_padding.setPadding(0, 10.dp, 0, 10.dp)
} else {
ll_header_padding.gone()
tv_body_padding.setPadding(0, 0.dp, 0, 10.dp)
}
//正文 //正文
dsb_padding_top.progress = paddingTop dsb_padding_top.progress = paddingTop
dsb_padding_bottom.progress = paddingBottom dsb_padding_bottom.progress = paddingBottom

@ -104,10 +104,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
showTitleConfig() showTitleConfig()
} }
tv_text_bold.onClick { tv_text_bold.onClick {
ReadBookConfig.apply { ReadBookConfig.textBold = !ReadBookConfig.textBold
textBold = !textBold tv_text_bold.isSelected = ReadBookConfig.textBold
tv_text_bold.isSelected = textBold
}
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
tv_text_font.onClick { tv_text_font.onClick {
@ -126,6 +124,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
dismiss() dismiss()
callBack?.showPaddingConfig() callBack?.showPaddingConfig()
} }
tv_tip.onClick {
TipConfigDialog().show(childFragmentManager, "tipConfigDialog")
}
rg_page_anim.onCheckedChange { _, checkedId -> rg_page_anim.onCheckedChange { _, checkedId ->
ReadBookConfig.pageAnim = rg_page_anim.getIndexById(checkedId) ReadBookConfig.pageAnim = rg_page_anim.getIndexById(checkedId)
callBack?.page_view?.upPageAnim() callBack?.page_view?.upPageAnim()

@ -0,0 +1,117 @@
package io.legado.app.ui.book.read.config
import android.os.Bundle
import android.util.DisplayMetrics
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.EventBus
import io.legado.app.help.ReadTipConfig
import io.legado.app.lib.dialogs.selector
import io.legado.app.ui.book.read.Help
import io.legado.app.utils.postEvent
import kotlinx.android.synthetic.main.dialog_tip_config.*
import org.jetbrains.anko.sdk27.listeners.onCheckedChange
import org.jetbrains.anko.sdk27.listeners.onClick
class TipConfigDialog : BaseDialogFragment() {
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm)
}
dialog?.window?.let {
val attr = it.attributes
attr.dimAmount = 0.0f
it.attributes = attr
it.setLayout((dm.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_tip_config, container)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
initView()
initEvent()
}
private fun initView() {
tv_header_left.text = ReadTipConfig.tipHeaderLeftStr
tv_header_middle.text = ReadTipConfig.tipHeaderMiddleStr
tv_header_right.text = ReadTipConfig.tipHeaderRightStr
tv_footer_left.text = ReadTipConfig.tipFooterLeftStr
tv_footer_middle.text = ReadTipConfig.tipFooterMiddleStr
tv_footer_right.text = ReadTipConfig.tipFooterRightStr
sw_hide_header.isChecked = ReadTipConfig.hideHeader
sw_hide_footer.isChecked = ReadTipConfig.hideFooter
}
private fun initEvent() {
tv_header_left.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipHeaderLeft = i
tv_header_left.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
tv_header_middle.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipHeaderMiddle = i
tv_header_middle.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
tv_header_right.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipHeaderRight = i
tv_header_right.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
tv_footer_left.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipFooterLeft = i
tv_footer_left.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
tv_footer_middle.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipFooterMiddle = i
tv_footer_middle.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
tv_footer_right.onClick {
selector(items = ReadTipConfig.tipArray.toList()) { _, i ->
ReadTipConfig.tipFooterRight = i
tv_footer_right.text = ReadTipConfig.tipArray[i]
postEvent(EventBus.UP_CONFIG, true)
}
}
sw_hide_header.onCheckedChange { buttonView, isChecked ->
if (buttonView?.isPressed == true) {
ReadTipConfig.hideHeader = isChecked
postEvent(EventBus.UP_CONFIG, true)
}
}
sw_hide_footer.onCheckedChange { buttonView, isChecked ->
if (buttonView?.isPressed == true) {
ReadTipConfig.hideFooter = isChecked
postEvent(EventBus.UP_CONFIG, true)
}
}
}
}

@ -1,6 +1,7 @@
package io.legado.app.ui.book.read.page package io.legado.app.ui.book.read.page
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Build
import android.text.Layout import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
import android.text.TextPaint import android.text.TextPaint
@ -31,8 +32,8 @@ object ChapterProvider {
private var titleTopSpacing = 0 private var titleTopSpacing = 0
private var titleBottomSpacing = 0 private var titleBottomSpacing = 0
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
var titlePaint = TextPaint() lateinit var titlePaint: TextPaint
var contentPaint = TextPaint() lateinit var contentPaint: TextPaint
init { init {
upStyle() upStyle()
@ -262,30 +263,36 @@ object ChapterProvider {
Typeface.SANS_SERIF Typeface.SANS_SERIF
} }
//标题 //标题
titlePaint.isAntiAlias = true titlePaint = TextPaint()
titlePaint.color = ReadBookConfig.durConfig.textColor() titlePaint.color = ReadBookConfig.durConfig.textColor()
titlePaint.letterSpacing = ReadBookConfig.letterSpacing titlePaint.letterSpacing = ReadBookConfig.letterSpacing
titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) titlePaint.typeface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Typeface.create(typeface, if (ReadBookConfig.textBold) 900 else 700, false)
} else {
Typeface.create(typeface, Typeface.BOLD)
}
titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat() titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat()
titlePaint.isAntiAlias = true
//正文 //正文
contentPaint.isAntiAlias = true contentPaint = TextPaint()
contentPaint.color = ReadBookConfig.durConfig.textColor() contentPaint.color = ReadBookConfig.durConfig.textColor()
contentPaint.letterSpacing = ReadBookConfig.letterSpacing contentPaint.letterSpacing = ReadBookConfig.letterSpacing
val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL
contentPaint.typeface = Typeface.create(typeface, style) contentPaint.typeface = Typeface.create(typeface, style)
contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat()
contentPaint.isAntiAlias = true
//间距 //间距
lineSpacingExtra = ReadBookConfig.lineSpacingExtra lineSpacingExtra = ReadBookConfig.lineSpacingExtra
paragraphSpacing = ReadBookConfig.paragraphSpacing paragraphSpacing = ReadBookConfig.paragraphSpacing
titleTopSpacing = ReadBookConfig.titleTopSpacing.dp titleTopSpacing = ReadBookConfig.titleTopSpacing.dp
titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp
upSize() upViewSize()
} }
/** /**
* 更新View尺寸 * 更新View尺寸
*/ */
fun upSize() { fun upViewSize() {
paddingLeft = ReadBookConfig.paddingLeft.dp paddingLeft = ReadBookConfig.paddingLeft.dp
paddingTop = ReadBookConfig.paddingTop.dp paddingTop = ReadBookConfig.paddingTop.dp
visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp

@ -61,7 +61,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
super.onSizeChanged(w, h, oldw, oldh) super.onSizeChanged(w, h, oldw, oldh)
ChapterProvider.viewWidth = w ChapterProvider.viewWidth = w
ChapterProvider.viewHeight = h ChapterProvider.viewHeight = h
ChapterProvider.upSize() ChapterProvider.upViewSize()
upVisibleRect() upVisibleRect()
textPage.format() textPage.format()
} }

@ -5,13 +5,20 @@ import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.FrameLayout import android.widget.FrameLayout
import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import androidx.core.view.isGone
import androidx.core.view.isInvisible
import com.hankcs.hanlp.HanLP
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.timeFormat import io.legado.app.constant.AppConst.timeFormat
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.help.ReadTipConfig
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.* import io.legado.app.ui.widget.BatteryView
import io.legado.app.utils.dp
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.statusBarHeight
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.view_book_page.view.* import kotlinx.android.synthetic.main.view_book_page.view.*
import java.util.* import java.util.*
@ -19,14 +26,22 @@ import java.util.*
class ContentView(context: Context) : FrameLayout(context) { class ContentView(context: Context) : FrameLayout(context) {
private var battery = 100 private var battery = 100
private var tvTitle: BatteryView? = null
private var tvTime: BatteryView? = null
private var tvBattery: BatteryView? = null
private var tvPage: BatteryView? = null
private var tvTotalProgress: BatteryView? = null
private var tvPageAndTotal: BatteryView? = null
val headerHeight: Int
get() = if (ReadBookConfig.hideStatusBar) ll_header.height else context.statusBarHeight
init { init {
//设置背景防止切换背景时文字重叠 //设置背景防止切换背景时文字重叠
setBackgroundColor(context.getCompatColor(R.color.background)) setBackgroundColor(context.getCompatColor(R.color.background))
inflate(context, R.layout.view_book_page, this) inflate(context, R.layout.view_book_page, this)
upTipStyle()
upStyle() upStyle()
upTime()
content_text_view.upView = { content_text_view.upView = {
setProgress(it) setProgress(it)
} }
@ -34,25 +49,27 @@ class ContentView(context: Context) : FrameLayout(context) {
fun upStyle() { fun upStyle() {
ReadBookConfig.apply { ReadBookConfig.apply {
tv_top_left.typeface = ChapterProvider.typeface tv_header_left.typeface = ChapterProvider.typeface
tv_top_right.typeface = ChapterProvider.typeface tv_header_middle.typeface = ChapterProvider.typeface
tv_bottom_left.typeface = ChapterProvider.typeface tv_header_right.typeface = ChapterProvider.typeface
tv_bottom_right.typeface = ChapterProvider.typeface tv_footer_left.typeface = ChapterProvider.typeface
battery_view.typeface = ChapterProvider.typeface tv_footer_middle.typeface = ChapterProvider.typeface
tv_footer_right.typeface = ChapterProvider.typeface
tv_header_left.setColor(durConfig.textColor())
tv_header_middle.setColor(durConfig.textColor())
tv_header_right.setColor(durConfig.textColor())
tv_footer_left.setColor(durConfig.textColor())
tv_footer_middle.setColor(durConfig.textColor())
tv_footer_right.setColor(durConfig.textColor())
//显示状态栏时隐藏header //显示状态栏时隐藏header
if (hideStatusBar) { vw_status_bar.setPadding(0, context.statusBarHeight, 0, 0)
vw_status_bar.isGone = hideStatusBar
ll_header.setPadding( ll_header.setPadding(
headerPaddingLeft.dp, headerPaddingLeft.dp,
headerPaddingTop.dp, headerPaddingTop.dp,
headerPaddingRight.dp, headerPaddingRight.dp,
headerPaddingBottom.dp headerPaddingBottom.dp
) )
ll_header.visible()
page_panel.setPadding(0, 0, 0, 0)
} else {
ll_header.gone()
page_panel.setPadding(0, context.statusBarHeight, 0, 0)
}
ll_footer.setPadding( ll_footer.setPadding(
footerPaddingLeft.dp, footerPaddingLeft.dp,
footerPaddingTop.dp, footerPaddingTop.dp,
@ -62,29 +79,105 @@ class ContentView(context: Context) : FrameLayout(context) {
vw_top_divider.visible(showHeaderLine) vw_top_divider.visible(showHeaderLine)
vw_bottom_divider.visible(showFooterLine) vw_bottom_divider.visible(showFooterLine)
content_text_view.upVisibleRect() content_text_view.upVisibleRect()
durConfig.textColor().let {
tv_top_left.setTextColor(it)
tv_top_right.setTextColor(it)
tv_bottom_left.setTextColor(it)
tv_bottom_right.setTextColor(it)
battery_view.setColor(it)
} }
if (hideStatusBar) { upTime()
tv_bottom_left.text = timeFormat.format(Date(System.currentTimeMillis())) upBattery(battery)
battery_view.visible()
battery_view.setBattery(battery)
} else {
battery_view.gone()
} }
fun upTipStyle() {
ReadTipConfig.apply {
val tipHeaderLeftNone = tipHeaderLeft == none
val tipHeaderRightNone = tipHeaderRight == none
val tipHeaderMiddleNone = tipHeaderMiddle == none
val tipFooterLeftNone = tipFooterLeft == none
val tipFooterRightNone = tipFooterRight == none
val tipFooterMiddleNone = tipFooterMiddle == none
tv_header_left.isInvisible = tipHeaderLeftNone
tv_header_right.isGone = tipHeaderRightNone
tv_header_middle.isGone = tipHeaderMiddleNone
tv_footer_left.isInvisible = tipFooterLeftNone
tv_footer_right.isGone = tipFooterRightNone
tv_footer_middle.isGone = tipFooterMiddleNone
ll_header.isGone = hideHeader
ll_footer.isGone = hideFooter
} }
tvTitle = when (ReadTipConfig.chapterTitle) {
ReadTipConfig.tipHeaderLeft -> tv_header_left
ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
} }
tvTitle?.apply {
val headerHeight: Int isBattery = false
get() { textSize = 12f
return if (ReadBookConfig.hideStatusBar) { }
ll_header.height tvTime = when (ReadTipConfig.time) {
} else { ReadTipConfig.tipHeaderLeft -> tv_header_left
context.statusBarHeight ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
}
tvTime?.apply {
isBattery = false
textSize = 12f
}
tvBattery = when (ReadTipConfig.battery) {
ReadTipConfig.tipHeaderLeft -> tv_header_left
ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
}
tvBattery?.apply {
isBattery = true
textSize = 10f
}
tvPage = when (ReadTipConfig.page) {
ReadTipConfig.tipHeaderLeft -> tv_header_left
ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
}
tvPage?.apply {
isBattery = false
textSize = 12f
}
tvTotalProgress = when (ReadTipConfig.totalProgress) {
ReadTipConfig.tipHeaderLeft -> tv_header_left
ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
}
tvTotalProgress?.apply {
isBattery = false
textSize = 12f
}
tvPageAndTotal = when (ReadTipConfig.pageAndTotal) {
ReadTipConfig.tipHeaderLeft -> tv_header_left
ReadTipConfig.tipHeaderMiddle -> tv_header_middle
ReadTipConfig.tipHeaderRight -> tv_header_right
ReadTipConfig.tipFooterLeft -> tv_footer_left
ReadTipConfig.tipFooterMiddle -> tv_footer_middle
ReadTipConfig.tipFooterRight -> tv_footer_right
else -> null
}
tvPageAndTotal?.apply {
isBattery = false
textSize = 12f
} }
} }
@ -93,16 +186,12 @@ class ContentView(context: Context) : FrameLayout(context) {
} }
fun upTime() { fun upTime() {
if (ReadBookConfig.hideStatusBar) { tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
tv_bottom_right.text = timeFormat.format(Date(System.currentTimeMillis()))
}
} }
fun upBattery(battery: Int) { fun upBattery(battery: Int) {
this.battery = battery this.battery = battery
if (ReadBookConfig.hideStatusBar) { tvBattery?.setBattery(battery)
battery_view.setBattery(battery)
}
} }
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) { fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
@ -119,18 +208,14 @@ class ContentView(context: Context) : FrameLayout(context) {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun setProgress(textPage: TextPage) = textPage.apply { fun setProgress(textPage: TextPage) = textPage.apply {
val title = when (AppConfig.chineseConverterType) { val title = when (AppConfig.chineseConverterType) {
1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) 1 -> HanLP.convertToSimplifiedChinese(textPage.title)
2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) 2 -> HanLP.convertToTraditionalChinese(textPage.title)
else -> textPage.title else -> textPage.title
} }
if (ReadBookConfig.hideStatusBar) { tvTitle?.text = title
tv_top_left.text = title tvPage?.text = "${index.plus(1)}/$pageSize"
tv_top_right.text = readProgress tvTotalProgress?.text = readProgress
tv_bottom_left.text = "${index.plus(1)}/$pageSize" tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
} else {
tv_bottom_left.text = title
tv_bottom_right.text = "${index.plus(1)}/$pageSize $readProgress"
}
} }
fun onScroll(offset: Float) { fun onScroll(offset: Float) {

@ -110,6 +110,12 @@ class PageView(context: Context, attrs: AttributeSet) :
callBack.screenOffTimerStart() callBack.screenOffTimerStart()
} }
fun upTipStyle() {
curPage.upTipStyle()
prevPage.upTipStyle()
nextPage.upTipStyle()
}
fun upStyle() { fun upStyle() {
ChapterProvider.upStyle() ChapterProvider.upStyle()
curPage.upStyle() curPage.upStyle()

@ -44,12 +44,11 @@ data class TextPage(
if (y < 0) y = 0f if (y < 0) y = 0f
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
val textLine = TextLine() val textLine = TextLine()
textLine.lineTop = (ChapterProvider.paddingTop + y - textLine.lineTop = ChapterProvider.paddingTop + y + layout.getLineTop(lineIndex)
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))) textLine.lineBase =
textLine.lineBase = (ChapterProvider.paddingTop + y - ChapterProvider.paddingTop + y + layout.getLineBaseline(lineIndex)
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex)))
textLine.lineBottom = textLine.lineBottom =
textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent ChapterProvider.paddingTop + y + layout.getLineBottom(lineIndex)
var x = ChapterProvider.paddingLeft + var x = ChapterProvider.paddingLeft +
(ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2
textLine.text = textLine.text =

@ -16,7 +16,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.net.URLEncoder
class BookSourceViewModel(application: Application) : BaseViewModel(application) { class BookSourceViewModel(application: Application) : BaseViewModel(application) {
@ -228,7 +227,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
private fun importSourceUrl(url: String): Int { private fun importSourceUrl(url: String): Int {
HttpHelper.simpleGet(url)?.let { body -> HttpHelper.simpleGet(url, "UTF-8")?.let { body ->
val bookSources = mutableListOf<BookSource>() val bookSources = mutableListOf<BookSource>()
val items: List<Map<String, Any>> = jsonPath.parse(body).read("$") val items: List<Map<String, Any>> = jsonPath.parse(body).read("$")
for (item in items) { for (item in items) {

@ -15,6 +15,7 @@ import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
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
@ -77,7 +78,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode)
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
val log = String(assets.open("updateLog.md").readBytes()) val log = String(assets.open("updateLog.md").readBytes())
TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000) TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000, true)
} }
} }
} }
@ -113,6 +114,13 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
return super.onKeyUp(keyCode, event) return super.onKeyUp(keyCode, event)
} }
override fun onPause() {
super.onPause()
if (!BuildConfig.DEBUG) {
Backup.autoBack(this)
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
ReadAloud.stop(this) ReadAloud.stop(this)

@ -39,6 +39,9 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.fragment_bookshelf.*
import kotlinx.android.synthetic.main.view_tab_layout.* import kotlinx.android.synthetic.main.view_tab_layout.*
import kotlinx.android.synthetic.main.view_title_bar.* import kotlinx.android.synthetic.main.view_title_bar.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
@ -51,8 +54,10 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
get() = getViewModel(BookshelfViewModel::class.java) get() = getViewModel(BookshelfViewModel::class.java)
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null private var bookGroupLiveData: LiveData<List<BookGroup>>? = null
private var noGroupLiveData: LiveData<Int>? = null
private val bookGroups = mutableListOf<BookGroup>() private val bookGroups = mutableListOf<BookGroup>()
private val fragmentMap = hashMapOf<Int, Fragment>() private val fragmentMap = hashMapOf<Int, Fragment>()
private var showGroupNone = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(toolbar) setSupportToolbar(toolbar)
@ -99,8 +104,17 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
bookGroupLiveData = App.db.bookGroupDao().liveDataAll() bookGroupLiveData = App.db.bookGroupDao().liveDataAll()
bookGroupLiveData?.observe(viewLifecycleOwner, Observer { bookGroupLiveData?.observe(viewLifecycleOwner, Observer {
viewModel.checkGroup(it) viewModel.checkGroup(it)
launch {
synchronized(this) { synchronized(this) {
tab_layout.removeOnTabSelectedListener(this) tab_layout.removeOnTabSelectedListener(this@BookshelfFragment)
}
var noGroupSize = 0
withContext(IO) {
if (AppConfig.bookGroupNoneShow) {
noGroupSize = App.db.bookDao().noGroupSize
}
}
synchronized(this@BookshelfFragment) {
bookGroups.clear() bookGroups.clear()
if (AppConfig.bookGroupAllShow) { if (AppConfig.bookGroupAllShow) {
bookGroups.add(AppConst.bookGroupAll) bookGroups.add(AppConst.bookGroupAll)
@ -111,10 +125,28 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
if (AppConfig.bookGroupAudioShow) { if (AppConfig.bookGroupAudioShow) {
bookGroups.add(AppConst.bookGroupAudio) bookGroups.add(AppConst.bookGroupAudio)
} }
showGroupNone = if (noGroupSize > 0 && it.isNotEmpty()) {
bookGroups.add(AppConst.bookGroupNone)
true
} else {
false
}
bookGroups.addAll(it) bookGroups.addAll(it)
view_pager_bookshelf.adapter?.notifyDataSetChanged() view_pager_bookshelf.adapter?.notifyDataSetChanged()
tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select()
tab_layout.addOnTabSelectedListener(this) tab_layout.addOnTabSelectedListener(this@BookshelfFragment)
}
}
})
noGroupLiveData?.removeObservers(viewLifecycleOwner)
noGroupLiveData = App.db.bookDao().observeNoGroupSize()
noGroupLiveData?.observe(viewLifecycleOwner, Observer {
if (it > 0 && !showGroupNone && AppConfig.bookGroupNoneShow) {
showGroupNone = true
upGroup()
} else if (it == 0 && showGroupNone) {
showGroupNone = false
upGroup()
} }
}) })
} }
@ -129,10 +161,25 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
} }
override fun upGroup() { override fun upGroup() {
synchronized(this) { launch {
var noGroupSize = 0
withContext(IO) {
if (AppConfig.bookGroupNoneShow) {
noGroupSize = App.db.bookDao().noGroupSize
}
}
synchronized(this@BookshelfFragment) {
bookGroups.remove(AppConst.bookGroupAll) bookGroups.remove(AppConst.bookGroupAll)
bookGroups.remove(AppConst.bookGroupLocal) bookGroups.remove(AppConst.bookGroupLocal)
bookGroups.remove(AppConst.bookGroupAudio) bookGroups.remove(AppConst.bookGroupAudio)
bookGroups.remove(AppConst.bookGroupNone)
showGroupNone =
if (noGroupSize > 0 && bookGroups.isNotEmpty()) {
bookGroups.add(0, AppConst.bookGroupNone)
true
} else {
false
}
if (AppConfig.bookGroupAudioShow) { if (AppConfig.bookGroupAudioShow) {
bookGroups.add(0, AppConst.bookGroupAudio) bookGroups.add(0, AppConst.bookGroupAudio)
} }
@ -145,6 +192,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
view_pager_bookshelf.adapter?.notifyDataSetChanged() view_pager_bookshelf.adapter?.notifyDataSetChanged()
} }
} }
}
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private fun configBookshelf() { private fun configBookshelf() {

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import io.legado.app.constant.AppConst
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
@ -98,9 +99,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
private fun upRecyclerData() { private fun upRecyclerData() {
bookshelfLiveData?.removeObservers(this) bookshelfLiveData?.removeObservers(this)
bookshelfLiveData = when (groupId) { bookshelfLiveData = when (groupId) {
-1 -> App.db.bookDao().observeAll() AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll()
-2 -> App.db.bookDao().observeLocal() AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal()
-3 -> App.db.bookDao().observeAudio() AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio()
AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
bookshelfLiveData?.observe(this, Observer { list -> bookshelfLiveData?.observe(this, Observer { list ->

@ -12,7 +12,7 @@ import io.legado.app.base.BaseFragment
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.MainViewModel
import io.legado.app.ui.rss.article.RssArticlesActivity import io.legado.app.ui.rss.article.RssSortActivity
import io.legado.app.ui.rss.favorites.RssFavoritesActivity import io.legado.app.ui.rss.favorites.RssFavoritesActivity
import io.legado.app.ui.rss.source.manage.RssSourceActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
@ -60,6 +60,6 @@ class RssFragment : BaseFragment(R.layout.fragment_rss),
} }
override fun openRss(rssSource: RssSource) { override fun openRss(rssSource: RssSource) {
startActivity<RssArticlesActivity>(Pair("url", rssSource.sourceUrl)) startActivity<RssSortActivity>(Pair("url", rssSource.sourceUrl))
} }
} }

@ -1,83 +1,67 @@
package io.legado.app.ui.rss.article package io.legado.app.ui.rss.article
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.View
import android.view.MenuItem
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseFragment
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.LoadMoreView
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_rss_artivles.* import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.startActivity
import kotlinx.android.synthetic.main.fragment_rss_articles.*
import kotlinx.android.synthetic.main.view_load_more.view.* import kotlinx.android.synthetic.main.view_load_more.view.*
import kotlinx.android.synthetic.main.view_refresh_recycler.* import kotlinx.android.synthetic.main.view_refresh_recycler.*
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult
class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activity_rss_artivles), class RssArticlesFragment : VMBaseFragment<RssArticlesViewModel>(R.layout.fragment_rss_articles),
RssArticlesViewModel.CallBack,
RssArticlesAdapter.CallBack { RssArticlesAdapter.CallBack {
companion object {
fun create(sortName: String, sortUrl: String): RssArticlesFragment {
return RssArticlesFragment().apply {
val bundle = Bundle()
bundle.putString("sortName", sortName)
bundle.putString("sortUrl", sortUrl)
arguments = bundle
}
}
}
private val activityViewModel: RssSortViewModel
get() = getViewModelOfActivity(RssSortViewModel::class.java)
override val viewModel: RssArticlesViewModel override val viewModel: RssArticlesViewModel
get() = getViewModel(RssArticlesViewModel::class.java) get() = getViewModel(RssArticlesViewModel::class.java)
lateinit var adapter: RssArticlesAdapter
override lateinit var adapter: RssArticlesAdapter
private val editSource = 12319
private lateinit var loadMoreView: LoadMoreView private lateinit var loadMoreView: LoadMoreView
private var rssArticlesData: LiveData<List<RssArticle>>? = null private var rssArticlesData: LiveData<List<RssArticle>>? = null
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
viewModel.callBack = this viewModel.init(arguments)
viewModel.titleLiveData.observe(this, Observer {
title_bar.title = it
})
initView() initView()
viewModel.initData(intent) {
initData()
refresh_recycler_view.startLoading() refresh_recycler_view.startLoading()
} initView()
} initData()
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.rss_articles, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let {
startActivityForResult<RssSourceEditActivity>(editSource, Pair("data", it))
}
R.id.menu_clear -> {
viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true
viewModel.clearArticles()
}
}
}
return super.onCompatOptionsItemSelected(item)
} }
private fun initView() { private fun initView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(this)) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
adapter = RssArticlesAdapter(this, this) adapter = RssArticlesAdapter(requireContext(), this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
loadMoreView = LoadMoreView(this) loadMoreView = LoadMoreView(requireContext())
adapter.addFooterView(loadMoreView) adapter.addFooterView(loadMoreView)
refresh_recycler_view.onRefreshStart = { refresh_recycler_view.onRefreshStart = {
viewModel.url?.let { activityViewModel.rssSource?.let {
viewModel.loadContent() viewModel.loadContent(it)
} }
} }
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
@ -91,10 +75,10 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
} }
private fun initData() { private fun initData() {
viewModel.url?.let { activityViewModel.url?.let {
rssArticlesData?.removeObservers(this) rssArticlesData?.removeObservers(this)
rssArticlesData = App.db.rssArticleDao().liveByOrigin(it) rssArticlesData = App.db.rssArticleDao().liveByOriginSort(it, viewModel.sortName)
rssArticlesData?.observe(this, Observer { list -> rssArticlesData?.observe(viewLifecycleOwner, Observer { list ->
adapter.setItems(list) adapter.setItems(list)
}) })
} }
@ -104,21 +88,25 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
if (viewModel.isLoading) return if (viewModel.isLoading) return
if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) { if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) {
loadMoreView.rotate_loading.show() loadMoreView.rotate_loading.show()
viewModel.loadMore() activityViewModel.rssSource?.let {
viewModel.loadMore(it)
}
} }
} }
override fun loadFinally(hasMore: Boolean) { override fun observeLiveBus() {
viewModel.loadFinally.observe(viewLifecycleOwner, Observer {
refresh_recycler_view.stopLoading() refresh_recycler_view.stopLoading()
if (hasMore) { if (it) {
loadMoreView.startLoad() loadMoreView.startLoad()
} else { } else {
loadMoreView.noMore() loadMoreView.noMore()
} }
})
} }
override fun readRss(rssArticle: RssArticle) { override fun readRss(rssArticle: RssArticle) {
viewModel.read(rssArticle) activityViewModel.read(rssArticle)
startActivity<ReadRssActivity>( startActivity<ReadRssActivity>(
Pair("title", rssArticle.title), Pair("title", rssArticle.title),
Pair("origin", rssArticle.origin), Pair("origin", rssArticle.origin),

@ -1,49 +1,37 @@
package io.legado.app.ui.rss.article package io.legado.app.ui.rss.article
import android.app.Application import android.app.Application
import android.content.Intent import android.os.Bundle
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssReadRecord
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.model.Rss import io.legado.app.model.Rss
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class RssArticlesViewModel(application: Application) : BaseViewModel(application) { class RssArticlesViewModel(application: Application) : BaseViewModel(application) {
var callBack: CallBack? = null val loadFinally = MutableLiveData<Boolean>()
var url: String? = null
var rssSource: RssSource? = null
val titleLiveData = MutableLiveData<String>()
var isLoading = true var isLoading = true
var order = System.currentTimeMillis() var order = System.currentTimeMillis()
private var nextPageUrl: String? = null private var nextPageUrl: String? = null
private val articles = arrayListOf<RssArticle>()
var sortName: String = ""
var sortUrl: String = ""
fun initData(intent: Intent, finally: () -> Unit) { fun init(bundle: Bundle?) {
execute { bundle?.let {
url = intent.getStringExtra("url") sortName = it.getString("sortName") ?: ""
url?.let { url -> sortUrl = it.getString("sortUrl") ?: ""
rssSource = App.db.rssSourceDao().getByKey(url)
rssSource?.let {
titleLiveData.postValue(it.sourceName)
} ?: let {
rssSource = RssSource(sourceUrl = url)
}
}
}.onFinally {
finally()
} }
} }
fun loadContent() {
fun loadContent(rssSource: RssSource) {
isLoading = true isLoading = true
rssSource?.let { rssSource -> Rss.getArticles(sortName, sortUrl, rssSource, null)
Rss.getArticles(rssSource, null) .onSuccess(Dispatchers.IO) {
.onSuccess(IO) {
nextPageUrl = it.nextPageUrl nextPageUrl = it.nextPageUrl
it.articles.let { list -> it.articles.let { list ->
list.forEach { rssArticle -> list.forEach { rssArticle ->
@ -51,40 +39,34 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
} }
App.db.rssArticleDao().insert(*list.toTypedArray()) App.db.rssArticleDao().insert(*list.toTypedArray())
if (!rssSource.ruleNextPage.isNullOrEmpty()) { if (!rssSource.ruleNextPage.isNullOrEmpty()) {
App.db.rssArticleDao().clearOld(url!!, order) App.db.rssArticleDao().clearOld(rssSource.sourceUrl, sortName, order)
withContext(Main) { loadFinally.postValue(true)
callBack?.loadFinally(true)
}
} else { } else {
withContext(Main) { withContext(Dispatchers.Main) {
callBack?.loadFinally(false) loadFinally.postValue(false)
} }
} }
isLoading = false isLoading = false
} }
}.onError { }.onError {
toast(it.localizedMessage) toast(it.localizedMessage)
} }
} }
}
fun loadMore() { fun loadMore(rssSource: RssSource) {
isLoading = true isLoading = true
val source = rssSource
val pageUrl = nextPageUrl val pageUrl = nextPageUrl
if (source != null && !pageUrl.isNullOrEmpty()) { if (!pageUrl.isNullOrEmpty()) {
Rss.getArticles(source, pageUrl) Rss.getArticles(sortName, pageUrl, rssSource, pageUrl)
.onSuccess(IO) { .onSuccess(Dispatchers.IO) {
nextPageUrl = it.nextPageUrl nextPageUrl = it.nextPageUrl
it.articles.let { list -> it.articles.let { list ->
if (list.isEmpty()) { if (list.isEmpty()) {
callBack?.loadFinally(false) loadFinally.postValue(true)
return@let return@let
} }
callBack?.adapter?.getItems()?.let { adapterItems -> if (articles.contains(list.first())) {
if (adapterItems.contains(list.first())) { loadFinally.postValue(false)
callBack?.loadFinally(false)
} else { } else {
list.forEach { rssArticle -> list.forEach { rssArticle ->
rssArticle.order = order-- rssArticle.order = order--
@ -92,33 +74,12 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
App.db.rssArticleDao().insert(*list.toTypedArray()) App.db.rssArticleDao().insert(*list.toTypedArray())
} }
} }
}
isLoading = false isLoading = false
} }
} else { } else {
callBack?.loadFinally(false) loadFinally.postValue(false)
} }
} }
fun read(rssArticle: RssArticle) {
execute {
App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link))
}
}
fun clearArticles() {
execute {
url?.let {
App.db.rssArticleDao().delete(it)
}
order = System.currentTimeMillis()
}.onSuccess {
loadContent()
}
}
interface CallBack {
var adapter: RssArticlesAdapter
fun loadFinally(hasMore: Boolean)
}
} }

@ -0,0 +1,100 @@
package io.legado.app.ui.rss.article
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.Observer
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.utils.getViewModel
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.activity_rss_artivles.*
import org.jetbrains.anko.startActivityForResult
class RssSortActivity : VMBaseActivity<RssSortViewModel>(R.layout.activity_rss_artivles) {
override val viewModel: RssSortViewModel
get() = getViewModel(RssSortViewModel::class.java)
private val editSource = 12319
private val fragments = linkedMapOf<String, RssArticlesFragment>()
private lateinit var adapter: TabFragmentPageAdapter
override fun onActivityCreated(savedInstanceState: Bundle?) {
adapter = TabFragmentPageAdapter(supportFragmentManager)
tab_layout.setupWithViewPager(view_pager)
view_pager.adapter = adapter
viewModel.titleLiveData.observe(this, Observer {
title_bar.title = it
})
viewModel.initData(intent) {
upFragments()
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.rss_articles, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let {
startActivityForResult<RssSourceEditActivity>(editSource, Pair("data", it))
}
R.id.menu_clear -> {
viewModel.url?.let {
viewModel.clearArticles()
}
}
}
return super.onCompatOptionsItemSelected(item)
}
private fun upFragments() {
fragments.clear()
viewModel.rssSource?.sortUrls()?.forEach {
fragments[it.key] = RssArticlesFragment.create(it.key, it.value)
}
if (fragments.size == 1) {
tab_layout.gone()
} else {
tab_layout.visible()
}
adapter.notifyDataSetChanged()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
editSource -> if (resultCode == Activity.RESULT_OK) {
viewModel.initData(intent) {
upFragments()
}
}
}
}
private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getPageTitle(position: Int): CharSequence? {
return fragments.keys.elementAt(position)
}
override fun getItem(position: Int): Fragment {
return fragments.values.elementAt(position)
}
override fun getCount(): Int {
return fragments.size
}
}
}

@ -0,0 +1,52 @@
package io.legado.app.ui.rss.article
import android.app.Application
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssReadRecord
import io.legado.app.data.entities.RssSource
class RssSortViewModel(application: Application) : BaseViewModel(application) {
var url: String? = null
var rssSource: RssSource? = null
val titleLiveData = MutableLiveData<String>()
var order = System.currentTimeMillis()
fun initData(intent: Intent, finally: () -> Unit) {
execute {
url = intent.getStringExtra("url")
url?.let { url ->
rssSource = App.db.rssSourceDao().getByKey(url)
rssSource?.let {
titleLiveData.postValue(it.sourceName)
} ?: let {
rssSource = RssSource(sourceUrl = url)
}
}
}.onFinally {
finally()
}
}
fun read(rssArticle: RssArticle) {
execute {
App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link))
}
}
fun clearArticles() {
execute {
url?.let {
App.db.rssArticleDao().delete(it)
}
order = System.currentTimeMillis()
}.onSuccess {
}
}
}

@ -24,7 +24,7 @@ import org.jetbrains.anko.toast
import org.jsoup.Jsoup import org.jsoup.Jsoup
class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_read), class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_read, false),
FileChooserDialog.CallBack, FileChooserDialog.CallBack,
ReadRssViewModel.CallBack { ReadRssViewModel.CallBack {
@ -252,6 +252,7 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
web_view.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
web_view.evaluateJavascript("document.documentElement.outerHTML") { web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it) val html = StringEscapeUtils.unescapeJson(it)
.replace("^\"|\"$".toRegex(), "")
Jsoup.parse(html).text() Jsoup.parse(html).text()
viewModel.readAloud(Jsoup.parse(html).textArray()) viewModel.readAloud(Jsoup.parse(html).textArray())
} }

@ -135,6 +135,7 @@ class RssSourceEditActivity :
add(EditEntity("sourceUrl", rssSource?.sourceUrl, R.string.source_url)) add(EditEntity("sourceUrl", rssSource?.sourceUrl, R.string.source_url))
add(EditEntity("sourceIcon", rssSource?.sourceIcon, R.string.source_icon)) add(EditEntity("sourceIcon", rssSource?.sourceIcon, R.string.source_icon))
add(EditEntity("sourceGroup", rssSource?.sourceGroup, R.string.source_group)) add(EditEntity("sourceGroup", rssSource?.sourceGroup, R.string.source_group))
add(EditEntity("sortUrl", rssSource?.sortUrl, R.string.sort_url))
add(EditEntity("ruleArticles", rssSource?.ruleArticles, R.string.r_articles)) add(EditEntity("ruleArticles", rssSource?.ruleArticles, R.string.r_articles))
add(EditEntity("ruleNextPage", rssSource?.ruleNextPage, R.string.r_next)) add(EditEntity("ruleNextPage", rssSource?.ruleNextPage, R.string.r_next))
add(EditEntity("ruleTitle", rssSource?.ruleTitle, R.string.r_title)) add(EditEntity("ruleTitle", rssSource?.ruleTitle, R.string.r_title))
@ -159,6 +160,7 @@ class RssSourceEditActivity :
"sourceUrl" -> source.sourceUrl = it.value ?: "" "sourceUrl" -> source.sourceUrl = it.value ?: ""
"sourceIcon" -> source.sourceIcon = it.value ?: "" "sourceIcon" -> source.sourceIcon = it.value ?: ""
"sourceGroup" -> source.sourceGroup = it.value "sourceGroup" -> source.sourceGroup = it.value
"sortUrl" -> source.sortUrl = it.value
"ruleArticles" -> source.ruleArticles = it.value "ruleArticles" -> source.ruleArticles = it.value
"ruleNextPage" -> source.ruleNextPage = it.value "ruleNextPage" -> source.ruleNextPage = it.value
"ruleTitle" -> source.ruleTitle = it.value "ruleTitle" -> source.ruleTitle = it.value

@ -204,7 +204,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
} }
private fun importSourceUrl(url: String): Int { private fun importSourceUrl(url: String): Int {
HttpHelper.simpleGet(url)?.let { body -> HttpHelper.simpleGet(url, "UTF-8")?.let { body ->
val sources = mutableListOf<RssSource>() val sources = mutableListOf<RssSource>()
val items: List<Map<String, Any>> = jsonPath.parse(body).read("$") val items: List<Map<String, Any>> = jsonPath.parse(body).read("$")
for (item in items) { for (item in items) {

@ -2,7 +2,7 @@ package io.legado.app.ui.welcome
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.github.houbb.opencc4j.util.ZhConverterUtil import com.hankcs.hanlp.HanLP
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
@ -37,8 +37,8 @@ open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
.clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)) .clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
//初始化简繁转换引擎 //初始化简繁转换引擎
when (AppConfig.chineseConverterType) { when (AppConfig.chineseConverterType) {
1 -> ZhConverterUtil.toSimple("初始化") 1 -> HanLP.convertToSimplifiedChinese("初始化")
2 -> ZhConverterUtil.toTraditional("初始化") 2 -> HanLP.convertToTraditionalChinese("初始化")
else -> null else -> null
} }
} }

@ -14,6 +14,7 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co
private val batteryPaint = Paint() private val batteryPaint = Paint()
private val outFrame = Rect() private val outFrame = Rect()
private val polar = Rect() private val polar = Rect()
var isBattery = false
init { init {
setPadding(4.dp, 0, 6.dp, 0) setPadding(4.dp, 0, 6.dp, 0)
@ -35,6 +36,7 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
if (!isBattery) return
outFrame.set( outFrame.set(
1.dp, 1.dp,
layout.getLineTop(0) + 2.dp, layout.getLineTop(0) + 2.dp,

@ -23,7 +23,8 @@ class TextDialog : BaseDialogFragment() {
fragmentManager: FragmentManager, fragmentManager: FragmentManager,
content: String?, content: String?,
mode: Int = 0, mode: Int = 0,
time: Long = 0 time: Long = 0,
autoClose: Boolean = false
) { ) {
TextDialog().apply { TextDialog().apply {
val bundle = Bundle() val bundle = Bundle()
@ -32,6 +33,7 @@ class TextDialog : BaseDialogFragment() {
bundle.putLong("time", time) bundle.putLong("time", time)
arguments = bundle arguments = bundle
isCancelable = false isCancelable = false
this.autoClose = autoClose
}.show(fragmentManager, "textDialog") }.show(fragmentManager, "textDialog")
} }
@ -39,6 +41,8 @@ class TextDialog : BaseDialogFragment() {
private var time = 0L private var time = 0L
private var autoClose: Boolean = false
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
@ -79,6 +83,7 @@ class TextDialog : BaseDialogFragment() {
if (time <= 0) { if (time <= 0) {
view.post { view.post {
dialog?.setCancelable(true) dialog?.setCancelable(true)
if (autoClose) dialog?.cancel()
} }
} }
} }
@ -86,6 +91,7 @@ class TextDialog : BaseDialogFragment() {
} else { } else {
view.post { view.post {
dialog?.setCancelable(true) dialog?.setCancelable(true)
if (autoClose) dialog?.cancel()
} }
} }
} }

@ -8,6 +8,7 @@ import androidx.core.view.isVisible
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ColorUtils
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
@ -36,13 +37,18 @@ class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCate
view.isVisible = title != null && title.isNotEmpty() view.isVisible = title != null && title.isNotEmpty()
val da = it.findViewById(R.id.preference_divider_above) val da = it.findViewById(R.id.preference_divider_above)
val dividerColor = if (AppConfig.isNightTheme) {
ColorUtils.shiftColor(context.backgroundColor, 1.05f)
} else {
ColorUtils.shiftColor(context.backgroundColor, 0.95f)
}
if (da is View) { if (da is View) {
da.setBackgroundColor(ColorUtils.darkenColor(context.backgroundColor)) da.setBackgroundColor(dividerColor)
da.isVisible = it.isDividerAllowedAbove da.isVisible = it.isDividerAllowedAbove
} }
val db = it.findViewById(R.id.preference_divider_below) val db = it.findViewById(R.id.preference_divider_below)
if (db is View) { if (db is View) {
db.setBackgroundColor(ColorUtils.darkenColor(context.backgroundColor)) db.setBackgroundColor(dividerColor)
db.isVisible = it.isDividerAllowedBelow db.isVisible = it.isDividerAllowedBelow
} }
} }

@ -0,0 +1,266 @@
package io.legado.app.utils
import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.transition.TransitionManager
class ConstraintUtil(val constraintLayout: ConstraintLayout) {
private var begin: ConstraintBegin? = null
private val applyConstraintSet = ConstraintSet()
private val resetConstraintSet = ConstraintSet()
init {
resetConstraintSet.clone(constraintLayout)
}
/**
* 开始修改
*/
fun begin(): ConstraintBegin? {
synchronized(ConstraintBegin::class.java) {
if (begin == null) {
begin = ConstraintBegin()
}
}
applyConstraintSet.clone(constraintLayout)
return begin
}
/**
* 带动画的修改
* @return
*/
fun beginWithAnim(): ConstraintBegin? {
TransitionManager.beginDelayedTransition(constraintLayout)
return begin()
}
/**
* 重置
*/
fun reSet() {
resetConstraintSet.applyTo(constraintLayout)
}
/**
* 带动画的重置
*/
fun reSetWidthAnim() {
TransitionManager.beginDelayedTransition(constraintLayout)
resetConstraintSet.applyTo(constraintLayout)
}
inner class ConstraintBegin {
/**
* 清除关系<br></br>
* 注意这里不仅仅会清除关系还会清除对应控件的宽高为 w:0,h:0
* @param viewIds
* @return
*/
fun clear(@IdRes vararg viewIds: Int): ConstraintBegin {
for (viewId in viewIds) {
applyConstraintSet.clear(viewId)
}
return this
}
/**
* 清除某个控件的某个关系
* @param viewId
* @param anchor
* @return
*/
fun clear(viewId: Int, anchor: Int): ConstraintBegin {
applyConstraintSet.clear(viewId, anchor)
return this
}
/**
* 为某个控件设置 margin
* @param viewId 某个控件ID
* @param left marginLeft
* @param top marginTop
* @param right marginRight
* @param bottom marginBottom
* @return
*/
fun setMargin(
@IdRes viewId: Int,
left: Int,
top: Int,
right: Int,
bottom: Int
): ConstraintBegin {
setMarginLeft(viewId, left)
setMarginTop(viewId, top)
setMarginRight(viewId, right)
setMarginBottom(viewId, bottom)
return this
}
/**
* 为某个控件设置 marginLeft
* @param viewId 某个控件ID
* @param left marginLeft
* @return
*/
fun setMarginLeft(@IdRes viewId: Int, left: Int): ConstraintBegin {
applyConstraintSet.setMargin(viewId, ConstraintSet.LEFT, left)
return this
}
/**
* 为某个控件设置 marginRight
* @param viewId 某个控件ID
* @param right marginRight
* @return
*/
fun setMarginRight(@IdRes viewId: Int, right: Int): ConstraintBegin {
applyConstraintSet.setMargin(viewId, ConstraintSet.RIGHT, right)
return this
}
/**
* 为某个控件设置 marginTop
* @param viewId 某个控件ID
* @param top marginTop
* @return
*/
fun setMarginTop(@IdRes viewId: Int, top: Int): ConstraintBegin {
applyConstraintSet.setMargin(viewId, ConstraintSet.TOP, top)
return this
}
/**
* 为某个控件设置marginBottom
* @param viewId 某个控件ID
* @param bottom marginBottom
* @return
*/
fun setMarginBottom(@IdRes viewId: Int, bottom: Int): ConstraintBegin {
applyConstraintSet.setMargin(viewId, ConstraintSet.BOTTOM, bottom)
return this
}
/**
* 为某个控件设置关联关系 left_to_left_of
* @param startId
* @param endId
* @return
*/
fun Left_toLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.LEFT)
return this
}
/**
* 为某个控件设置关联关系 left_to_right_of
* @param startId
* @param endId
* @return
*/
fun Left_toRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.RIGHT)
return this
}
/**
* 为某个控件设置关联关系 top_to_top_of
* @param startId
* @param endId
* @return
*/
fun Top_toTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.TOP)
return this
}
/**
* 为某个控件设置关联关系 top_to_bottom_of
* @param startId
* @param endId
* @return
*/
fun Top_toBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.BOTTOM)
return this
}
/**
* 为某个控件设置关联关系 right_to_left_of
* @param startId
* @param endId
* @return
*/
fun Right_toLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.LEFT)
return this
}
/**
* 为某个控件设置关联关系 right_to_right_of
* @param startId
* @param endId
* @return
*/
fun Right_toRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.RIGHT)
return this
}
/**
* 为某个控件设置关联关系 bottom_to_bottom_of
* @param startId
* @param endId
* @return
*/
fun Bottom_toBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.BOTTOM)
return this
}
/**
* 为某个控件设置关联关系 bottom_to_top_of
* @param startId
* @param endId
* @return
*/
fun Bottom_toTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {
applyConstraintSet.connect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.TOP)
return this
}
/**
* 为某个控件设置宽度
* @param viewId
* @param width
* @return
*/
fun setWidth(@IdRes viewId: Int, width: Int): ConstraintBegin {
applyConstraintSet.constrainWidth(viewId, width)
return this
}
/**
* 某个控件设置高度
* @param viewId
* @param height
* @return
*/
fun setHeight(@IdRes viewId: Int, height: Int): ConstraintBegin {
applyConstraintSet.constrainHeight(viewId, height)
return this
}
/**
* 提交应用生效
*/
fun commit() {
applyConstraintSet.applyTo(constraintLayout)
}
}
}

@ -15,8 +15,7 @@ val GSON: Gson by lazy {
.create() .create()
} }
inline fun <reified T> genericType() = object : TypeToken<T>() {}.type inline fun <reified T> genericType(): Type = object : TypeToken<T>() {}.type
@Throws(JsonSyntaxException::class) @Throws(JsonSyntaxException::class)
inline fun <reified T> Gson.fromJsonObject(json: String?): T? {//可转成任意类型 inline fun <reified T> Gson.fromJsonObject(json: String?): T? {//可转成任意类型

@ -361,7 +361,8 @@
android:text="@string/book_intro" android:text="@string/book_intro"
android:textColor="@color/tv_text_secondary" android:textColor="@color/tv_text_secondary"
android:textSize="14sp" android:textSize="14sp"
android:visibility="visible" /> android:visibility="visible"
tools:ignore="RtlHardcoded,RtlSymmetry" />
</LinearLayout> </LinearLayout>

@ -155,7 +155,7 @@
android:text="@string/author" android:text="@string/author"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
</LinearLayout> </LinearLayout>
@ -189,7 +189,7 @@
android:text="@string/origin_format" android:text="@string/origin_format"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_change_source" android:id="@+id/tv_change_source"
@ -234,7 +234,7 @@
android:text="@string/read_dur_progress" android:text="@string/read_dur_progress"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
</LinearLayout> </LinearLayout>
@ -267,7 +267,7 @@
android:text="@string/group_s" android:text="@string/group_s"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_change_group" android:id="@+id/tv_change_group"
@ -311,7 +311,7 @@
android:text="@string/toc_s" android:text="@string/toc_s"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_toc_view" android:id="@+id/tv_toc_view"
@ -340,7 +340,8 @@
android:textSize="14sp" android:textSize="14sp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:visibility="visible" /> android:visibility="visible"
tools:ignore="RtlHardcoded,RtlSymmetry" />
</LinearLayout> </LinearLayout>
@ -349,7 +350,7 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="8dp" android:layout_height="8dp"
android:background="@color/background"></View> android:background="@color/background" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"

@ -1,28 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
app:layout_constraintTop_toTopOf="parent" />
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/content_view" android:id="@+id/tab_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:tabMode="scrollable" />
app:layout_constraintTop_toBottomOf="@id/title_bar">
<io.legado.app.ui.widget.recycler.RefreshRecyclerView <androidx.viewpager.widget.ViewPager
android:id="@+id/refresh_recycler_view" android:id="@+id/view_pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="wrap_content" />
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

@ -2,6 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
@ -13,7 +14,8 @@
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
app:fitStatusBar="false" />
<io.legado.app.ui.rss.read.VisibleWebView <io.legado.app.ui.rss.read.VisibleWebView
android:id="@+id/web_view" android:id="@+id/web_view"

@ -121,6 +121,24 @@
android:textSize="14sp" android:textSize="14sp"
app:radius="3dp" /> app:radius="3dp" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<io.legado.app.ui.widget.text.StrokeTextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:text="@string/information"
android:gravity="center"
android:textSize="14sp"
app:radius="3dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:gravity="center_vertical"
tools:ignore="RtlHardcoded,RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/left" />
<TextView
android:id="@+id/tv_header_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/middle" />
<TextView
android:id="@+id/tv_header_middle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/right" />
<TextView
android:id="@+id/tv_header_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/footer" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:gravity="center_vertical"
tools:ignore="RtlHardcoded,RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/left" />
<TextView
android:id="@+id/tv_footer_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/middle" />
<TextView
android:id="@+id/tv_footer_middle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/right" />
<TextView
android:id="@+id/tv_footer_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<Switch
android:id="@+id/sw_hide_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp"
android:text="@string/hideHeader" />
<Switch
android:id="@+id/sw_hide_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp"
android:text="@string/hideFooter" />
</LinearLayout>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_bar">
<io.legado.app.ui.widget.recycler.RefreshRecyclerView
android:id="@+id/refresh_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>

@ -1,38 +1,61 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/page_panel" android:id="@+id/page_panel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <FrameLayout
android:id="@+id/vw_status_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_header" android:id="@+id/ll_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical">
android:orientation="horizontal">
<TextView <io.legado.app.ui.widget.BatteryView
android:id="@+id/tv_top_left" android:id="@+id/tv_header_left"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:textSize="12sp" /> android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <io.legado.app.ui.widget.BatteryView
android:id="@+id/tv_top_right" android:id="@+id/tv_header_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<io.legado.app.ui.widget.BatteryView
android:id="@+id/tv_header_right"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="3dp" android:layout_marginLeft="3dp"
android:textSize="12sp" android:textSize="12sp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,RtlSymmetry" /> tools:ignore="RtlHardcoded,RtlSymmetry" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
android:id="@+id/vw_top_divider" android:id="@+id/vw_top_divider"
@ -54,42 +77,49 @@
android:background="@color/divider" android:background="@color/divider"
android:visibility="invisible" /> android:visibility="invisible" />
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_footer" android:id="@+id/ll_footer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical">
android:orientation="horizontal">
<TextView <io.legado.app.ui.widget.BatteryView
android:id="@+id/tv_bottom_left" android:id="@+id/tv_footer_left"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:textSize="12sp" /> android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <io.legado.app.ui.widget.BatteryView
android:id="@+id/tv_bottom_right" android:id="@+id/tv_footer_middle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:textSize="12sp" android:textSize="12sp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
tools:ignore="RtlHardcoded,RtlSymmetry" /> android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<io.legado.app.ui.widget.BatteryView <io.legado.app.ui.widget.BatteryView
android:id="@+id/battery_view" android:id="@+id/tv_footer_right"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="3dp" android:layout_marginLeft="3dp"
android:textSize="10sp" android:textSize="12sp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,RtlSymmetry,SmallSp" /> tools:ignore="RtlHardcoded,RtlSymmetry,SmallSp" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.LinearLayoutCompat> </LinearLayout>

@ -10,6 +10,8 @@
app:showAsAction="always" app:showAsAction="always"
tools:ignore="AlwaysShowAction" /> tools:ignore="AlwaysShowAction" />
<group android:id="@+id/menu_groups">
<item <item
android:id="@+id/menu_group_all" android:id="@+id/menu_group_all"
android:title="@string/all" android:title="@string/all"
@ -21,14 +23,23 @@
android:id="@+id/menu_group_local" android:id="@+id/menu_group_local"
android:title="@string/local" android:title="@string/local"
android:checkable="true" android:checkable="true"
android:checked="true" android:checked="false"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/menu_group_audio" android:id="@+id/menu_group_audio"
android:title="@string/audio" android:title="@string/audio"
android:checkable="true" android:checkable="true"
android:checked="true" android:checked="false"
app:showAsAction="never" />
<item
android:id="@+id/menu_group_none"
android:title="@string/no_group"
android:checkable="true"
android:checked="false"
app:showAsAction="never" /> app:showAsAction="never" />
</group>
</menu> </menu>

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="tts_speech_per">
<item>度小美</item>
<item>度小宇</item>
<item>度逍遙</item>
<item>度丫丫</item>
<item>度小嬌</item>
<item>度米朵</item>
<item>度博文</item>
<item>度小童</item>
<item>度小萌</item>
<item>百度騷男</item>
<item>百度評書</item>
<item>百度主持</item>
</string-array>
<string-array name="theme_mode">
<item>跟隨系統</item>
<item>亮色主題</item>
<item>暗色主題</item>
</string-array>
<string-array name="NavBarColors">
<item>自動</item>
<item>黑色</item>
<item>白色</item>
<item>跟隨背景</item>
</string-array>
<string-array name="screen_time_out">
<item>默認</item>
<item>1分鐘</item>
<item>2分鐘</item>
<item>3分鐘</item>
<item>常亮</item>
</string-array>
<string-array name="chinese_mode">
<item>關閉</item>
<item>繁體轉簡體</item>
<item>簡體轉繁體</item>
</string-array>
<string-array name="system_typefaces">
<item>系統默認字體</item>
<item>系統襯線字體</item>
<item>系統等寬字體</item>
</string-array>
</resources>

@ -0,0 +1,688 @@
<resources>
<!--App-->
<string name="app_name">閲讀</string>
<string name="receiving_shared_label">閲讀·搜尋</string>
<string name="tip_perm_request_storage">閲讀需要訪問存儲卡權限,請前往「設定」—「應用程式權限」—開啟所需要的權限</string>
<!--Other-->
<string name="menu_backup">Home</string>
<string name="menu_restore">還原</string>
<string name="menu_import_old">導入閲讀數據</string>
<string name="mkdirs">創建子文件夾</string>
<string name="mkdirs_description">創建 legado 文件夾作爲備份路徑</string>
<string name="backup_path">備份路徑</string>
<string name="menu_import_old_version">導入舊版數據</string>
<string name="menu_import_github">導入 Github 數據</string>
<string name="menu_replace_rule">淨化替換</string>
<string name="menu_send">Send</string>
<string name="dialog_title">提示</string>
<string name="dialog_cancel">取消</string>
<string name="dialog_confirm">確認</string>
<string name="dialog_setting">去設定</string>
<string name="tip_cannot_jump_setting_page">無法轉跳至設定介面</string>
<string name="dynamic_click_retry">點擊重試</string>
<string name="dynamic_loading">正在加載</string>
<string name="draw">提醒</string>
<string name="edit">編輯</string>
<string name="delete">刪除</string>
<string name="replace">替換</string>
<string name="replace_purify">替換淨化</string>
<string name="replace_purify_desc">配置替換淨化規則</string>
<string name="not_available">暫無</string>
<string name="enable">啟用</string>
<string name="replace_purify_search">替換淨化-搜尋</string>
<string name="bookshelf">書架</string>
<string name="favorites">收藏夾</string>
<string name="favorite">收藏</string>
<string name="in_favorites">已收藏</string>
<string name="out_favorites">未收藏</string>
<string name="rss">訂閲</string>
<string name="all">全部</string>
<string name="recent_reading">最近閲讀</string>
<string name="last_read">最後閲讀</string>
<string name="update_log">更新日誌</string>
<string name="bookshelf_empty">書架還空着,快去添加吧!</string>
<string name="action_search">搜尋</string>
<string name="action_download">下載</string>
<string name="layout_list">列表</string>
<string name="layout_grid3">網格三列</string>
<string name="layout_grid4">網格四列</string>
<string name="layout_grid5">網格五列</string>
<string name="layout_grid6">網格六列</string>
<string name="bookshelf_layout">書架佈局</string>
<string name="view">視圖</string>
<string name="book_library">書城</string>
<string name="book_local">添加本地</string>
<string name="book_source">書源</string>
<string name="book_source_manage">書源管理</string>
<string name="book_source_manage_desc">新建/導入/編輯/管理書源</string>
<string name="setting">設定</string>
<string name="theme_setting">主題設定</string>
<string name="theme_setting_s">同主題/顏色相關的一些設定</string>
<string name="other_setting">其它設定</string>
<string name="other_setting_s">與功能相關的一些設定</string>
<string name="about">關於</string>
<string name="donate">捐贈</string>
<string name="exit">退出</string>
<string name="exit_no_save">尚未保存,是否繼續編輯</string>
<string name="read_style">閲讀樣式設定</string>
<string name="version">版本</string>
<string name="local">本地</string>
<string name="search">搜尋</string>
<string name="origin_format">來源: %s</string>
<string name="read_dur_progress">最近: %s</string>
<string name="book_name">書名</string>
<string name="lasted_show">最新: %s</string>
<string name="check_add_bookshelf">是否將《%s》放入書架?</string>
<string name="import_books_count">共 %s 個 Text 文件</string>
<string name="is_loading">載入中…</string>
<string name="retry">重試</string>
<string name="web_service">Web 服務</string>
<string name="web_service_desc">啟用 Web 服務</string>
<string name="web_edit_source">web 編輯書源</string>
<string name="http_ip">http://%1$s:%2$d</string>
<string name="download_offline">離線下載</string>
<string name="download_offline_t">離線下載</string>
<string name="download_offline_s">下載已選擇的章節到本地</string>
<string name="change_origin">換源</string>
<string name="about_description">
\u3000\u3000這是一款使用 Kotlin 全新開發的開源的閲讀應用程式,歡迎你的加入。關注公眾號[开源阅读软件]!
</string>
<string name="app_share_description">
閲讀3.0下載地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app
</string>
<string name="version_name">Version %s</string>
<string name="pt_auto_refresh">自動刷新</string>
<string name="ps_auto_refresh">打開程式時自動更新書輯</string>
<string name="pt_auto_download">自動下載最新章節</string>
<string name="ps_auto_download">更新書輯時自動下載最新章節</string>
<string name="backup_restore">備份與還原</string>
<string name="web_dav_set">WebDav 設定</string>
<string name="web_dav_set_import_old">WebDav 設定/還原舊版本數據</string>
<string name="backup">備份</string>
<string name="restore">還原</string>
<string name="backup_permission">備份請給予存儲權限</string>
<string name="restore_permission">還原請給予存儲權限</string>
<string name="ok">確認</string>
<string name="cancel">取消</string>
<string name="backup_confirmation">確認備份嗎?</string>
<string name="backup_message">新備份會覆蓋原有備份。\n備份路徑YueDu</string>
<string name="restore_confirmation">確認還原嗎?</string>
<string name="restore_message">還原成功會覆蓋原有書架。</string>
<string name="backup_success">備份成功</string>
<string name="backup_fail">備份失敗</string>
<string name="on_restore">正在還原</string>
<string name="restore_success">還原成功</string>
<string name="restore_fail">還原失敗</string>
<string name="screen_direction">屏幕方向</string>
<string name="screen_sensor">跟隨傳感器</string>
<string name="screen_landscape">橫向</string>
<string name="screen_portrait">豎向</string>
<string name="screen_unspecified">跟隨系統</string>
<string name="disclaimer">免責聲明</string>
<string name="all_chapter_num">共%d章</string>
<string name="interface_setting">介面</string>
<string name="brightness">亮度</string>
<string name="chapter_list">目錄</string>
<string name="next_chapter">下一章</string>
<string name="previous_chapter">上一章</string>
<string name="pt_hide_status_bar">隱藏狀態欄</string>
<string name="ps_hide_status_bar">閲讀介面隱藏狀態欄</string>
<string name="read_aloud">朗讀</string>
<string name="read_aloud_t">正在朗讀</string>
<string name="read_aloud_s">點擊打開閲讀介面</string>
<string name="audio_play">播放</string>
<string name="audio_play_t">正在播放</string>
<string name="audio_play_s">點擊打開播放介面</string>
<string name="audio_pause">播放暫停</string>
<string name="text_return">返回</string>
<string name="refresh">刷新</string>
<string name="start">開始</string>
<string name="stop">停止</string>
<string name="pause">暫停</string>
<string name="resume">繼續</string>
<string name="set_timer">定時</string>
<string name="read_aloud_pause">朗讀暫停</string>
<string name="read_aloud_timer">正在朗讀(剩餘 %d 分鐘)</string>
<string name="ps_hide_navigation_bar">閲讀介面隱藏導航欄</string>
<string name="pt_hide_navigation_bar">隱藏導航欄</string>
<string name="re_navigation_bar_color">導航欄顏色</string>
<string name="git_hub">GitHub</string>
<string name="scoring">評分</string>
<string name="send_mail">發送電子郵件</string>
<string name="can_not_open">無法打開</string>
<string name="can_not_share">分享失敗</string>
<string name="no_chapter">無章節</string>
<string name="add_url">添加網址</string>
<string name="add_book_url">添加書輯網址</string>
<string name="background">背景</string>
<string name="author">作者</string>
<string name="author_show">作者: %s</string>
<string name="aloud_stop">朗讀停止</string>
<string name="clear_cache">清除緩存</string>
<string name="clear_cache_success">成功清除緩存</string>
<string name="action_save">保存</string>
<string name="edit_source">編輯源</string>
<string name="edit_book_source">編輯書源</string>
<string name="disable_book_source">禁用書源</string>
<string name="add_book_source">新建書源</string>
<string name="add_rss_source">新建訂閲源</string>
<string name="book_file_selector">添加書輯</string>
<string name="scan_book_source">掃描</string>
<string name="copy_source">拷貝源</string>
<string name="paste_source">粘帖源</string>
<string name="source_rule_s">源規則説明</string>
<string name="check_update">檢查更新</string>
<string name="camera_scan">掃描 QR Code</string>
<string name="scan_image">掃描本地圖片</string>
<string name="rule_summary">規則説明</string>
<string name="share">分享</string>
<string name="share_app">應用程式分享</string>
<string name="flow_sys">跟隨系統</string>
<string name="add">添加</string>
<string name="import_book_source">導入書源</string>
<string name="import_book_source_local">本地導入</string>
<string name="import_book_source_on_line">網絡導入</string>
<string name="replace_rule_title">替換淨化</string>
<string name="replace_rule_edit">替換規則編輯</string>
<string name="replace_rule">替換規則</string>
<string name="replace_to">替換為</string>
<string name="img_cover">封面</string>
<string name="book"></string>
<string name="volume_key_page">音量鍵翻頁</string>
<string name="click_turn_page">點擊翻頁</string>
<string name="click_all_next_page">點擊總是翻下一頁</string>
<string name="page_anim">翻頁動畫</string>
<string name="keep_light">屏幕超時</string>
<string name="back">返回</string>
<string name="menu">菜單</string>
<string name="adjust">調節</string>
<string name="scroll_bar">滾動條</string>
<string name="clear_all_content">清除緩存會刪除所有已保存的章節,確認是否清除?</string>
<string name="book_source_share_url">書源共享</string>
<string name="replace_rule_summary">規則替換名稱</string>
<string name="replace_rule_invalid">替換規則為空或不滿足正則表達式要求</string>
<string name="select_action">選擇操作</string>
<string name="select_all">全選</string>
<string name="select_all_count">全選 (%1$d/%2$d)</string>
<string name="select_cancel_count">取消 (%1$d/%2$d)</string>
<string name="dark_theme">深色模式</string>
<string name="welcome">啟動頁</string>
<string name="download_start">開始下載</string>
<string name="download_cancel">取消下載</string>
<string name="no_download">暫無任務</string>
<string name="download_count">已下載 %1$d/%2$d</string>
<string name="import_select_book">導入選擇書輯</string>
<string name="threads_num_title">更新/搜尋線程數,太多會卡頓</string>
<string name="change_icon">切換圖標</string>
<string name="remove_from_bookshelf">刪除書輯</string>
<string name="start_read">開始閲讀</string>
<string name="data_loading">數據載入中…</string>
<string name="load_error_retry">載入失敗,點擊重試</string>
<string name="book_intro">內容簡介</string>
<string name="intro_show">簡介: %s</string>
<string name="open_from_other">打開外部書籍</string>
<string name="origin_show">來源: %s</string>
<string name="import_replace_rule">本地導入</string>
<string name="import_replace_rule_on_line">導入在線規則</string>
<string name="check_update_interval">檢查更新間隔</string>
<string name="bookshelf_px_0">按閲讀時間</string>
<string name="bookshelf_px_1">按更新時間</string>
<string name="bookshelf_px_2">按書名</string>
<string name="bookshelf_px_3">手動排序</string>
<string name="read_type">閲讀方式</string>
<string name="compose_type">排版</string>
<string name="del_select">刪除所選</string>
<string name="del_msg">是否確認刪除?</string>
<string name="clear_font">默認字體</string>
<string name="find_on_www">發現</string>
<string name="find_source_manage">發現管理</string>
<string name="find_empty">沒有內容,去書源裏自定義吧!</string>
<string name="del_all">刪除所有</string>
<string name="searchHistory">搜索歷史</string>
<string name="clear">清除</string>
<string name="showTitle">正文顯示標題</string>
<string name="refresh_default">書源同步</string>
<string name="no_last_chapter">無最新章節信息</string>
<string name="showTimeBattery">顯示時間和電量</string>
<string name="showLine">顯示分隔線</string>
<string name="dark_status_icon">深色狀態欄圖標</string>
<string name="content">內容</string>
<string name="copy_text">拷貝內容</string>
<string name="download_all">一鍵緩存</string>
<string name="content_sl">這是一段測試文字\n\u3000\u3000只是讓你看看效果的</string>
<string name="text_bg_style">文字顏色和背景(長按自定義)</string>
<string name="immersion_status_bar">沉浸式狀態欄</string>
<string name="un_download">還剩 %d 章未下載</string>
<string name="non_select">沒有選擇</string>
<string name="long_click_input_color">長按輸入顏色值</string>
<string name="loading">加載中…</string>
<string name="group_zg">追更區</string>
<string name="group_yf">養肥區</string>
<string name="bookmark">書籤</string>
<string name="bookmark_add">添加書籤</string>
<string name="action_del">刪除</string>
<string name="load_over_time">加載超時</string>
<string name="join_group">關注: %s</string>
<string name="copy_complete">已拷貝</string>
<string name="arrange_bookshelf">整理書架</string>
<string name="clear_bookshelf_s">這將會刪除所有書籍,請謹慎操作。</string>
<string name="search_book_source">搜索書源</string>
<string name="search_rss_source">搜索訂閲源</string>
<string name="search_book_source_num">搜索(共 %d 個書源)</string>
<string name="chapter_list_size">目錄(%d)</string>
<string name="text_bold">加粗</string>
<string name="text_font">字體</string>
<string name="text">文字</string>
<string name="home_page">軟件主頁</string>
<string name="right"></string>
<string name="left"></string>
<string name="bottom"></string>
<string name="top"></string>
<string name="padding">邊距</string>
<string name="padding_top">上邊距</string>
<string name="padding_bottom">下邊距</string>
<string name="padding_left">左邊距</string>
<string name="padding_right">右邊距</string>
<string name="check_book_source">校驗書源</string>
<string name="check_select_source">校驗所選</string>
<string name="progress_show">進度 %1$d/%2$d</string>
<string name="tts_fix">請安裝並選擇中文 TTS!</string>
<string name="tts_init_failed">TTS 初始化失敗!</string>
<string name="jf_convert">簡繁轉換</string>
<string name="jf_convert_o">關閉</string>
<string name="jf_convert_f">簡轉繁</string>
<string name="jf_convert_j">繁轉簡</string>
<string name="page_mode">翻頁模式</string>
<string name="nb_file_sub_count">%1$d 項</string>
<string name="nb_file_path">存儲卡:</string>
<string name="nb_file_add_shelf">加入書架</string>
<string name="nb_file_add_shelves">加入書架 (%1$d)</string>
<string name="nb_file_add_succeed">成功添加 %1$d 本書</string>
<string name="fonts_folder">請將字體文件放到 SD 根目錄 Fonts 文件夾下重新選擇</string>
<string name="default_font">默認字體</string>
<string name="select_font">選擇字體</string>
<string name="text_size">字號</string>
<string name="line_size">行距</string>
<string name="paragraph_size">段距</string>
<string name="to_top">置頂</string>
<string name="auto_expand_find">自動展開發現</string>
<string name="default_expand_first">默認展開第一組發現</string>
<string name="threads_num">當前線程數 %s</string>
<string name="read_aloud_speed">朗讀語速</string>
<string name="auto_next_page">自動翻頁</string>
<string name="auto_next_page_stop">停止自動翻頁</string>
<string name="auto_next_page_speed">自動翻頁間隔</string>
<string name="book_info">書籍信息</string>
<string name="book_info_edit">書籍信息編輯</string>
<string name="ps_default_read">默認打開書架</string>
<string name="pt_default_read">自動跳轉最近閲讀</string>
<string name="use_to">替換範圍,選填書名或者源名</string>
<string name="menu_action_group">分組</string>
<string name="download_path">內容緩存路徑</string>
<string name="cleanCache">清理緩存</string>
<string name="sys_file_picker">系統文件選擇器</string>
<string name="new_version">新版本</string>
<string name="download_update">下載更新</string>
<string name="volume_key_page_on_play">朗讀時音量鍵翻頁</string>
<string name="tip_margin_change">Tip 邊距跟隨邊距調整</string>
<string name="allow_update">允許更新</string>
<string name="disable_update">禁止更新</string>
<string name="revert_selection">反選</string>
<string name="search_book_key">搜索書名、作者</string>
<string name="debug_hint">書名、作者、URL</string>
<string name="faq">常見問題</string>
<string name="pt_show_all_find">顯示所有發現</string>
<string name="ps_show_all_find">關閉則只顯示勾選源的發現</string>
<string name="update_toc">更新目錄</string>
<string name="txt_toc_regex">Txt目錄正則</string>
<string name="set_charset">設置編碼</string>
<string name="swap_sort">倒序-順序</string>
<string name="sort">排序</string>
<string name="sort_auto">智能排序</string>
<string name="sort_manual">手動排序</string>
<string name="sort_pin_yin">拼音排序</string>
<string name="go_to_top">滾動到頂部</string>
<string name="go_to_bottom">滾動到底部</string>
<string name="read_y">已讀: %s</string>
<string name="pursue_more">追更</string>
<string name="fattening">養肥</string>
<string name="finish">完結</string>
<string name="all_book">所有書籍</string>
<string name="pursue_more_book">追更書籍</string>
<string name="fattening_book">養肥書籍</string>
<string name="finish_book">完結書籍</string>
<string name="local_book">本地書籍</string>
<string name="status_bar_immersion">狀態欄顏色透明</string>
<string name="navigation_bar_color_change">導航欄變色</string>
<string name="navigation_bar_color_change_s">導航欄根據夜間模式變化</string>
<string name="add_to_shelf">放入書架</string>
<string name="continue_read">繼續閲讀</string>
<string name="cover_path">封面地址</string>
<string name="page_anim_cover">覆蓋</string>
<string name="page_anim_slide">滑動</string>
<string name="page_anim_simulation">仿真</string>
<string name="page_anim_scroll">滾動</string>
<string name="page_anim_none">無動畫</string>
<string name="donate_s">此書源使用了高級功能,請到捐贈裏點擊支付寶紅包搜索碼領取紅包開啟。</string>
<string name="up_change_source_last_chapter_t">後台更新換源最新章節</string>
<string name="up_change_source_last_chapter_s">開啟則會在軟件打開 1 分鐘後開始更新</string>
<string name="behavior_main_t">書架 ToolBar 自動隱藏</string>
<string name="behavior_main_s">滾動書架時 ToolBar 自動隱藏與顯示</string>
<string name="login">登錄</string>
<string name="login_source">登錄 %s</string>
<string name="success">成功</string>
<string name="source_no_login">當前源沒有配置登陸地址</string>
<!-- source start-->
<string name="source_name">源名稱 (sourceName)</string>
<string name="source_url">源URL (sourceUrl)</string>
<string name="source_group">源分組 (sourceGroup)</string>
<string name="sort_url">分類 Url</string>
<string name="login_url">登錄 URL(loginUrl)</string>
<string name="r_search_url">搜索地址 (url)</string>
<string name="r_find_url">發現地址規則 (url)</string>
<string name="r_book_list">書籍列表規則 (bookList)</string>
<string name="r_book_name">書名規則 (name)</string>
<string name="r_book_url">詳情頁 url 規則 (bookUrl)</string>
<string name="r_author">作者規則 (author)</string>
<string name="rule_book_kind">分類規則 (kind)</string>
<string name="rule_book_intro">簡介規則 (intro)</string>
<string name="rule_cover_url">封面規則 (coverUrl)</string>
<string name="rule_last_chapter">最新章節規則 (lastChapter)</string>
<string name="rule_word_count">字數規則 (wordCount)</string>
<string name="book_url_pattern">書籍 URL 正則 (bookUrlPattern)</string>
<string name="rule_book_info_init">預處理規則 (bookInfoInit)</string>
<string name="rule_toc_url">目錄 URL 規則 (tocUrl)</string>
<string name="rule_next_toc_url">目錄下一頁規則 (nextTocUrl)</string>
<string name="rule_chapter_list">目錄列表規則 (chapterList)</string>
<string name="rule_chapter_name">章節名稱規則 (ChapterName)</string>
<string name="rule_chapter_url">章節 URL 規則 (chapterUrl)</string>
<string name="rule_is_vip">VIP 標識 (isVip)</string>
<string name="rule_update_time">章節信息 (ChapterInfo)</string>
<string name="rule_book_content">正文規則 (content)</string>
<string name="rule_next_content">正文下一頁 URL 規則 (nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
<string name="rule_source_regex">資源正則 (sourceRegex)</string>
<string name="source_icon">圖標 (sourceIcon)</string>
<string name="r_articles">列表規則 (ruleArticles)</string>
<string name="r_next">列表下一頁規則 (ruleArticles)</string>
<string name="r_title">標題規則 (ruleTitle)</string>
<string name="r_guid">guid 規則 (ruleGuid)</string>
<string name="r_date">時間規則 (rulePubDate)</string>
<string name="r_categories">類別規則 (ruleCategories)</string>
<string name="r_description">描述規則 (ruleDescription)</string>
<string name="r_image">圖片 url 規則 (ruleImage)</string>
<string name="r_content">內容規則 (ruleContent)</string>
<string name="r_link">鏈接規則 (ruleLink)</string>
<!-- source end-->
<!--error string start-->
<string name="error_no_source">沒有書源</string>
<string name="error_get_book_info">書籍信息獲取失敗</string>
<string name="error_get_content">內容獲取失敗</string>
<string name="error_get_chapter_list">目錄獲取失敗</string>
<string name="error_get_web_content">訪問網站失敗: %s</string>
<string name="error_read_file">文件讀取失敗</string>
<string name="error_load_toc">加載目錄失敗</string>
<string name="error_get_data">獲取數據失敗!</string>
<string name="error_load_msg">加載失敗\n%s</string>
<string name="net_error_10001">沒有網絡</string>
<string name="net_error_10002">網絡連接超時</string>
<string name="net_error_10003">數據解析失敗</string>
<!--error string end-->
<string name="source_http_header">請求頭 (header)</string>
<string name="debug_source">調試源</string>
<string name="import_by_qr_code">二維碼導入</string>
<string name="scan_qr_code">掃描二維碼</string>
<string name="click_on_selected_show_menu">選中時點擊可彈出菜單</string>
<string name="theme">主題</string>
<string name="theme_mode">主題模式</string>
<string name="theme_mode_desc">選擇主題模式</string>
<string name="default_theme">默認主題</string>
<string name="restore_default_theme">恢復主題為默認配色</string>
<string name="join_qq_group">加入QQ羣</string>
<string name="bg_image_per">獲取背景圖片需存儲權限</string>
<string name="input_book_source_url">輸入書源網址</string>
<string name="del_file">刪除文件</string>
<string name="del_file_success">刪除文件成功</string>
<string name="sure_del_file">確定刪除文件嗎?</string>
<string name="files_tree">手機目錄</string>
<string name="intelligent_import">智能導入</string>
<string name="find">發現</string>
<string name="switch_display_style">切換顯示樣式</string>
<string name="import_per">導入本地書籍需存儲權限</string>
<string name="night_theme">夜間模式</string>
<string name="eink_theme">E-Ink 模式</string>
<string name="eink_theme_desc">電子墨水屏模式</string>
<string name="get_storage_per">本軟件需要存儲權限來存儲備份書籍信息</string>
<string name="double_click_exit">再按一次退出程式</string>
<string name="import_book_per">導入本地書籍需存儲權限</string>
<string name="network_connection_unavailable">網絡連接不可用</string>
<string name="yes"></string>
<string name="no"></string>
<string name="sure">確認</string>
<string name="sure_del">是否確認刪除?</string>
<string name="sure_del_all_book">是否刪除全部書籍?</string>
<string name="sure_del_download_book">是否同時刪除已下載的書籍目錄?</string>
<string name="qr_per">掃描二維碼需相機權限</string>
<string name="aloud_can_not_auto_page">朗讀正在運行,不能自動翻頁</string>
<string name="input_charset">輸入編碼</string>
<string name="text_chapter_list_rule">TXT 目錄規則</string>
<string name="open_local_book_per">打開外部書籍需獲取存儲權限</string>
<string name="no_book_name">未獲取到書名</string>
<string name="input_replace_url">輸入替換規則網址</string>
<string name="get_book_list_success">搜索列表獲取成功%d</string>
<string name="non_null_source_name_url">書源名稱和 URL 不能為空</string>
<string name="gallery">圖庫</string>
<string name="get_ali_pay_hb">領支付寶紅包</string>
<string name="non_update_url">沒有獲取到更新地址</string>
<string name="check_host_cookie">正在打開首頁,成功自動返回主界面</string>
<string name="click_check_after_success">登錄成功後請點擊右上角圖標進行首頁訪問測試</string>
<string name="chapter"></string>
<string name="to"></string>
<string name="use_regex">使用正則表達式</string>
<string name="text_indent">縮進</string>
<string name="indent_0">無縮進</string>
<string name="indent_1">一字符縮進</string>
<string name="indent_2">二字符縮進</string>
<string name="indent_3">三字符縮進</string>
<string name="indent_4">四字符縮進</string>
<string name="select_folder">選擇文件夾</string>
<string name="select_file">選擇文件</string>
<string name="no_find">沒有發現,可以在書源裏添加。</string>
<string name="restore_default">恢復默認</string>
<string name="set_download_per">自定義緩存路徑需要存儲權限</string>
<string name="black">黑色</string>
<string name="content_empty">文章內容為空</string>
<string name="on_change_source">正在換源請等待…</string>
<string name="chapter_list_empty">目錄列表為空</string>
<string name="text_letter_spacing">字距</string>
<string name="source_tab_base">基本</string>
<string name="source_tab_search">搜索</string>
<string name="source_tab_find">發現</string>
<string name="source_tab_info">詳情</string>
<string name="source_tab_toc">目錄</string>
<string name="source_tab_content">正文</string>
<string name="e_ink_mode">E-Ink 模式</string>
<string name="e_ink_mode_detail">去除動畫,優化電紙書使用體驗</string>
<string name="web_menu">Web 服務</string>
<string name="web_port_title">web 端口</string>
<string name="web_port_summary">當前端口 %s</string>
<string name="qr_share">二維碼分享</string>
<string name="str_share">字符串分享</string>
<string name="wifi_share">wifi 分享</string>
<string name="please_grant_storage_permission">請給於存儲權限</string>
<string name="fast_rewind">減速</string>
<string name="fast_forward">加速</string>
<string name="skip_previous">上一個</string>
<string name="skip_next">下一個</string>
<string name="music">音樂</string>
<string name="audio">音頻</string>
<string name="is_enable">啟用</string>
<string name="enable_js">啟用 JS</string>
<string name="load_with_base_url">加載 BaseUrl</string>
<string name="all_source">全部書源</string>
<string name="cannot_empty">輸入不能為空</string>
<string name="clear_find_cache">清空發現緩存</string>
<string name="edit_find">編輯發現</string>
<string name="change_icon_summary">切換軟件顯示在桌面的圖標</string>
<string name="help">幫助</string>
<string name="my">我的</string>
<string name="reading">閲讀</string>
<string name="battery_show">%d%%</string>
<string name="timer_m">%d 分鐘</string>
<string name="brightness_auto">自動亮度 %s</string>
<string name="read_aloud_by_page">按頁朗讀</string>
<string name="read_aloud_on_line">在線朗讀</string>
<string name="bg_image">背景圖片</string>
<string name="bg_color">背景顏色</string>
<string name="text_color">文字顏色</string>
<string name="select_image">選擇圖片</string>
<string name="group_manage">分組管理</string>
<string name="group_select">分組選擇</string>
<string name="group_edit">編輯分組</string>
<string name="move_to_group">移入分組</string>
<string name="add_group">添加分組</string>
<string name="add_replace_rule">新建替換</string>
<string name="group">分組</string>
<string name="group_s">分組: %s</string>
<string name="toc_s">目錄: %s</string>
<string name="enable_explore">啟用發現</string>
<string name="disable_explore">禁用發現</string>
<string name="enable_selection">啟用所選</string>
<string name="disable_selection">禁用所選</string>
<string name="export_selection">導出所選</string>
<string name="export">導出</string>
<string name="load_toc">加載目錄</string>
<string name="tts">TTS</string>
<string name="web_dav_pw">WebDav 密碼</string>
<string name="web_dav_pw_s">輸入你的 WebDav 授權密碼</string>
<string name="web_dav_url_s">輸入你的服務器地址</string>
<string name="web_dav_url">WebDav 服務器地址</string>
<string name="web_dav_account">WebDav 賬號</string>
<string name="web_dav_account_s">輸入你的 WebDav 賬號</string>
<string name="rss_source">訂閲源</string>
<string name="rss_source_edit">編輯訂閲源</string>
<string name="screen">篩選</string>
<string name="screen_find">篩選發現</string>
<string name="dur_pos">當前位置:</string>
<string name="precision_search">精準搜索</string>
<string name="service_starting">正在啟動服務</string>
<string name="empty"></string>
<string name="file_chooser">文件選擇</string>
<string name="folder_chooser">文件夾選擇</string>
<string name="bottom_line">我是有底線的</string>
<string name="uri_to_path_fail">Uri 轉 Path 失敗</string>
<string name="refresh_cover">刷新封面</string>
<string name="change_cover_source">封面換源</string>
<string name="select_local_image">選擇本地圖片</string>
<string name="book_type">類型: </string>
<string name="book_type_text">文本</string>
<string name="book_type_audio">音頻</string>
<string name="to_backstage">後台</string>
<string name="importing">正在導入</string>
<string name="exporting">正在導出</string>
<string name="custom_page_key">自定義翻頁按鍵</string>
<string name="prev_page_key">上一頁按鍵</string>
<string name="next_page_key">下一頁按鍵</string>
<string name="after_add_bookshelf">先將書籍加入書架</string>
<string name="no_group">未分組</string>
<string name="prev_sentence">上一句</string>
<string name="next_sentence">下一句</string>
<string name="other_folder">其它目錄</string>
<string name="text_too_long_qr_error">文字太多,生成二維碼失敗</string>
<string name="share_rss_source">分享RSS源</string>
<string name="share_book_source">分享書源</string>
<string name="auto_dark_mode">自動切換夜間模式</string>
<string name="auto_dark_mode_s">夜間模式跟隨系統</string>
<string name="go_back">上級</string>
<string name="tone_colour">在線朗讀音色</string>
<string name="select_count">(%1$d/%2$d)</string>
<string name="show_rss">顯示訂閲</string>
<string name="service_stop">服務已停止</string>
<string name="service_start">正在啟動服務\n具體信息查看通知欄</string>
<string name="default_path">默認路徑</string>
<string name="sys_folder_picker">系統文件夾選擇器</string>
<string name="app_folder_picker">自帶選擇器\n(Android10 以上因權限限制可能無法使用)</string>
<string name="a10_permission_toast">Android10 以上因權限限制可能無法讀寫文件</string>
<string name="add_to_text_context_menu_s">長按文字在操作菜單中顯示閲讀·搜索</string>
<string name="add_to_text_context_menu_t">文字操作顯示搜索</string>
<string name="record_log">記錄日誌</string>
<string name="chinese_converter">中文簡繁體轉換</string>
<string name="chage_icon_error">圖標為矢量圖標,Android8.0 以前不支持</string>
<string name="aloud_config">朗讀設置</string>
<string name="main_activity">主界面</string>
<string name="selectText">長按選擇文本</string>
<string name="header">頁眉</string>
<string name="main_body">正文</string>
<string name="footer">頁腳</string>
<string name="select_end">文本選擇結束位置</string>
<string name="select_start">文本選擇開始位置</string>
<string name="share_layout">共用佈局</string>
<string name="browser">瀏覽器</string>
<string name="import_default_rule">導入默認規則</string>
<string name="name">名稱</string>
<string name="regex">正則</string>
<string name="more_menu">更多菜單</string>
<string name="reduce"></string>
<string name="plus"></string>
<string name="system_typeface">系統內置字體樣式</string>
<string name="delete_book_file">刪除源文件</string>
<string name="default1">預設一</string>
<string name="default2">預設二</string>
<string name="default3">預設三</string>
<string name="title">標題</string>
<string name="title_left">靠左</string>
<string name="title_center">居中</string>
<string name="title_hide">隱藏</string>
<string name="add_to_group">加入分組</string>
<string name="save_image">保存圖片</string>
<string name="no_default_path">沒有默認路徑</string>
<string name="change_group">設置分組</string>
<string name="view_toc">查看目錄</string>
<string name="bar_elevation">導航欄陰影</string>
<string name="bar_elevation_s">當前陰影大小 (elevation): %s</string>
<string name="btn_default_s">默認</string>
<string name="main_menu">主菜單</string>
<string name="request_permission">點擊授予權限</string>
<string name="tip_local_perm_request_storage">閲讀需要訪問存儲卡權限,請點擊下方的"授予權限"按鈕,或前往「設定」—「應用程式權限」—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的「選擇文件夾」,使用系統文件夾選擇器。</string>
<string name="alouding_disable">全文朗讀中不能朗讀選中文字</string>
<string name="read_body_to_lh">擴展到劉海</string>
<string name="toc_updateing">更新目錄中</string>
<string name="media_button_on_exit_title">全程響應耳機按鍵</string>
<string name="media_button_on_exit_summary">即使退出軟件也響應耳機按鍵</string>
<string name="contributors">開發人員</string>
<string name="contact">聯繫我們</string>
<string name="license">開源許可</string>
<string name="follow_official_account">關注公衆號</string>
<string name="wechat">WeChat</string>
<string name="thanks">你的支持是我更新的動力</string>
<string name="about_official_account">公众号[开源阅读软件]</string>
<string name="source_auto_changing">正在自動換源</string>
<string name="click_to_apply">點擊加入</string>
<string name="middle"></string>
<string name="information">信息</string>
<!--color-->
<string name="primary">主色調</string>
<string name="accent">強調色</string>
<string name="background_color">背景色</string>
<string name="navbar_color">導航欄顏色</string>
<string name="day">白天</string>
<string name="day_color_primary">白天,主色調</string>
<string name="day_color_accent">白天,強調色</string>
<string name="day_background_color">白天,背景色</string>
<string name="day_navbar_color">白天,導航欄顏色</string>
<string name="night">夜間</string>
<string name="night_primary">夜間,主色調</string>
<string name="night_accent">夜間,強調色</string>
<string name="night_background_color">夜間,背景色</string>
<string name="night_navbar_color">夜間,導航欄顏色</string>
</resources>

@ -96,4 +96,14 @@
<item>系统等宽字体</item> <item>系统等宽字体</item>
</string-array> </string-array>
<string-array name="read_tip">
<item></item>
<item>标题</item>
<item>时间</item>
<item>电量</item>
<item>页数</item>
<item>进度</item>
<item>页数及进度</item>
</string-array>
</resources> </resources>

@ -8,6 +8,10 @@
<string name="menu_backup">Home</string> <string name="menu_backup">Home</string>
<string name="menu_restore">恢复</string> <string name="menu_restore">恢复</string>
<string name="menu_import_old">导入阅读数据</string> <string name="menu_import_old">导入阅读数据</string>
<string name="mkdirs">创建子文件夹</string>
<string name="mkdirs_description">创建legado文件夹作为备份文件夹</string>
<string name="backup_path">备份路径</string>
<string name="menu_import_old_version">导入旧版数据</string>
<string name="menu_import_github">导入Github数据</string> <string name="menu_import_github">导入Github数据</string>
<string name="menu_replace_rule">净化替换</string> <string name="menu_replace_rule">净化替换</string>
<string name="menu_send">Send</string> <string name="menu_send">Send</string>
@ -376,6 +380,7 @@
<string name="source_name">源名称(sourceName)</string> <string name="source_name">源名称(sourceName)</string>
<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="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="r_search_url">搜索地址(url)</string> <string name="r_search_url">搜索地址(url)</string>
<string name="r_find_url">发现地址规则(url)</string> <string name="r_find_url">发现地址规则(url)</string>
@ -557,8 +562,11 @@
<string name="export">导出</string> <string name="export">导出</string>
<string name="load_toc">加载目录</string> <string name="load_toc">加载目录</string>
<string name="tts">TTS</string> <string name="tts">TTS</string>
<string name="web_dav_pw">WebDav 密码</string>
<string name="web_dav_pw_s">输入你的WebDav授权密码</string> <string name="web_dav_pw_s">输入你的WebDav授权密码</string>
<string name="web_dav_url_s">输入你的服务器地址</string> <string name="web_dav_url_s">输入你的服务器地址</string>
<string name="web_dav_url">WebDav 服务器地址</string>
<string name="web_dav_account">WebDav 账号</string>
<string name="web_dav_account_s">输入你的WebDav账号</string> <string name="web_dav_account_s">输入你的WebDav账号</string>
<string name="rss_source">订阅源</string> <string name="rss_source">订阅源</string>
<string name="rss_source_edit">编辑订阅源</string> <string name="rss_source_edit">编辑订阅源</string>
@ -604,7 +612,7 @@
<string name="sys_folder_picker">系统文件夹选择器</string> <string name="sys_folder_picker">系统文件夹选择器</string>
<string name="app_folder_picker">自带选择器\n(Android10以上因权限限制可能无法使用)</string> <string name="app_folder_picker">自带选择器\n(Android10以上因权限限制可能无法使用)</string>
<string name="a10_permission_toast">Android10以上因权限限制可能无法读写文件</string> <string name="a10_permission_toast">Android10以上因权限限制可能无法读写文件</string>
<string name="add_to_text_context_menu_s">长按文字在操作菜单中显示欣悦·搜索</string> <string name="add_to_text_context_menu_s">长按文字在操作菜单中显示阅读·搜索</string>
<string name="add_to_text_context_menu_t">文字操作显示搜索</string> <string name="add_to_text_context_menu_t">文字操作显示搜索</string>
<string name="record_log">记录日志</string> <string name="record_log">记录日志</string>
<string name="chinese_converter">中文简繁体转换</string> <string name="chinese_converter">中文简繁体转换</string>
@ -647,4 +655,39 @@
<string name="tip_local_perm_request_storage">阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。</string> <string name="tip_local_perm_request_storage">阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。</string>
<string name="alouding_disable">全文朗读中不能朗读选中文字</string> <string name="alouding_disable">全文朗读中不能朗读选中文字</string>
<string name="read_body_to_lh">扩展到刘海</string> <string name="read_body_to_lh">扩展到刘海</string>
<string name="toc_updateing">更新目录中</string>
<string name="media_button_on_exit_title">全程响应耳机按键</string>
<string name="media_button_on_exit_summary">即使退出软件也响应耳机按键</string>
<string name="contributors">开发人员</string>
<string name="contact">联系我们</string>
<string name="license">开源许可</string>
<string name="other" translatable="false">其它</string>
<string name="official_account" translatable="false">开源阅读软件</string>
<string name="follow_official_account">关注公众号</string>
<string name="wechat">微信</string>
<string name="thanks">您的支持是我更新的动力</string>
<string name="about_official_account">公众号[开源阅读软件]</string>
<string name="source_auto_changing">正在自动换源</string>
<string name="click_to_apply">点击加入</string>
<string name="middle"></string>
<string name="information">信息</string>
<string name="hideHeader">隐藏页眉</string>
<string name="hideFooter">隐藏页脚</string>
<!--color-->
<string name="primary">主色调</string>
<string name="accent">强调色</string>
<string name="background_color">背景色</string>
<string name="navbar_color">底部操作栏颜色</string>
<string name="day">白天</string>
<string name="day_color_primary">白天,主色调</string>
<string name="day_color_accent">白天,强调色</string>
<string name="day_background_color">白天,背景色</string>
<string name="day_navbar_color">白天,底栏色</string>
<string name="night">夜间</string>
<string name="night_primary">夜间,主色调</string>
<string name="night_accent">夜间,强调色</string>
<string name="night_background_color">夜间,背景色</string>
<string name="night_navbar_color">夜间,底栏色</string>
</resources> </resources>

@ -4,7 +4,7 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="contributors" android:key="contributors"
android:title="开发人员" android:title="@string/contributors"
android:summary="gedoor,Invinciblelee等, 详情请在github中查看" android:summary="gedoor,Invinciblelee等, 详情请在github中查看"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
@ -21,7 +21,7 @@
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="联系我们" android:title="@string/contact"
app:layout="@layout/view_preference_category" app:layout="@layout/view_preference_category"
app:allowDividerAbove="true" app:allowDividerAbove="true"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -29,14 +29,14 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="gzGzh" android:key="gzGzh"
android:title="关注公众号" android:title="@string/follow_official_account"
android:summary="开源阅读软件" android:summary="@string/official_account"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="qq" android:key="qq"
android:title="@string/join_qq_group" android:title="@string/join_qq_group"
android:summary="点击加入" android:summary="@string/click_to_apply"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
@ -62,7 +62,7 @@
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="其它" android:title="@string/other"
app:layout="@layout/view_preference_category" app:layout="@layout/view_preference_category"
app:allowDividerAbove="true" app:allowDividerAbove="true"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -70,7 +70,7 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="license" android:key="license"
android:title="开源许可" android:title="@string/license"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="微信" android:title="@string/wechat"
app:layout="@layout/view_preference_category" app:layout="@layout/view_preference_category"
app:allowDividerAbove="false" app:allowDividerAbove="false"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -11,7 +11,7 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="gzGzh" android:key="gzGzh"
android:title="关注公众号" android:title="@string/follow_official_account"
android:summary="关注[开源阅读软件]点击广告支持我" android:summary="关注[开源阅读软件]点击广告支持我"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />

@ -11,27 +11,27 @@
<io.legado.app.ui.widget.prefs.EditTextPreference <io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_url" android:key="web_dav_url"
android:title="WebDav 服务器地址" android:title="@string/web_dav_url"
android:summary="@string/web_dav_url_s" android:summary="@string/web_dav_url_s"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.EditTextPreference <io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_account" android:key="web_dav_account"
android:title="WebDav 账号" android:title="@string/web_dav_account"
android:summary="@string/web_dav_account_s" android:summary="@string/web_dav_account_s"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.EditTextPreference <io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_password" android:key="web_dav_password"
android:title="WebDav 密码" android:title="@string/web_dav_pw"
android:summary="@string/web_dav_pw_s" android:summary="@string/web_dav_pw_s"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference <io.legado.app.ui.widget.prefs.SwitchPreference
android:key="webDavCreateDir" android:key="webDavCreateDir"
android:defaultValue="true" android:defaultValue="true"
android:title="创建子文件夹" android:title="@string/mkdirs"
android:summary="创建legado文件夹作为备份文件夹" android:summary="@string/mkdirs_description"
app:allowDividerAbove="false" app:allowDividerAbove="false"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
@ -45,22 +45,22 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="backupUri" android:key="backupUri"
android:title="备份路径" android:title="@string/backup_path"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="web_dav_backup" android:key="web_dav_backup"
android:title="备份" android:title="@string/backup"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="web_dav_restore" android:key="web_dav_restore"
android:title="恢复" android:title="@string/restore"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="import_old" android:key="import_old"
android:title="导入旧版本数据" android:title="@string/menu_import_old_version"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>

@ -45,6 +45,13 @@
android:title="@string/threads_num_title" android:title="@string/threads_num_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="true"
android:key="mediaButtonOnExit"
android:title="@string/media_button_on_exit_title"
android:summary="@string/media_button_on_exit_summary"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="webPort" android:key="webPort"
android:title="@string/web_port_title" android:title="@string/web_port_title"

@ -42,7 +42,7 @@
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="白天" android:title="@string/day"
app:layout="@layout/view_preference_category" app:layout="@layout/view_preference_category"
app:allowDividerAbove="true" app:allowDividerAbove="true"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -51,32 +51,32 @@
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_light_blue_600" android:defaultValue="@color/md_light_blue_600"
android:key="colorPrimary" android:key="colorPrimary"
android:summary="白天,主色调" android:summary="@string/day_color_primary"
android:title="主色调" android:title="@string/primary"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_pink_800" android:defaultValue="@color/md_pink_800"
android:key="colorAccent" android:key="colorAccent"
android:summary="白天,强调色" android:summary="@string/day_color_accent"
android:title="强调色" android:title="@string/accent"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_100" android:defaultValue="@color/md_grey_100"
android:key="colorBackground" android:key="colorBackground"
android:summary="白天,背景色" android:summary="@string/day_background_color"
android:title="背景色" android:title="@string/background_color"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_200" android:defaultValue="@color/md_grey_200"
android:key="colorBottomBackground" android:key="colorBottomBackground"
android:summary="白天,底栏色" android:summary="@string/day_navbar_color"
android:title="底部操作栏颜色" android:title="@string/navbar_color"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:allowDividerAbove="false" app:allowDividerAbove="false"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -85,7 +85,7 @@
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="夜间" android:title="@string/night"
app:layout="@layout/view_preference_category" app:layout="@layout/view_preference_category"
app:allowDividerAbove="true" app:allowDividerAbove="true"
app:allowDividerBelow="false" app:allowDividerBelow="false"
@ -94,32 +94,32 @@
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_900" android:defaultValue="@color/md_grey_900"
android:key="colorPrimaryNight" android:key="colorPrimaryNight"
android:summary="夜间,主色调" android:summary="@string/night_primary"
android:title="主色调" android:title="@string/primary"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_deep_orange_800" android:defaultValue="@color/md_deep_orange_800"
android:key="colorAccentNight" android:key="colorAccentNight"
android:summary="夜间,强调色" android:summary="@string/night_accent"
android:title="强调色" android:title="@string/accent"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_black_1000" android:defaultValue="@color/md_black_1000"
android:key="colorBackgroundNight" android:key="colorBackgroundNight"
android:summary="夜间,背景色" android:summary="@string/night_background_color"
android:title="背景色" android:title="@string/background_color"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference <io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_800" android:defaultValue="@color/md_grey_800"
android:key="colorBottomBackgroundNight" android:key="colorBottomBackgroundNight"
android:summary="夜间,底栏色" android:summary="@string/night_navbar_color"
android:title="底部操作栏颜色" android:title="@string/navbar_color"
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />

@ -86,14 +86,14 @@
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="donate" android:key="donate"
android:summary="您的支持是我更新的动力" android:summary="@string/thanks"
android:title="@string/donate" android:title="@string/donate"
android:icon="@drawable/ic_cfg_jz" android:icon="@drawable/ic_cfg_jz"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="about" android:key="about"
android:summary="公众号[开源阅读软件]" android:summary="@string/about_official_account"
android:title="@string/about" android:title="@string/about"
android:icon="@drawable/ic_cfg_about" android:icon="@drawable/ic_cfg_about"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />

@ -1,15 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.71' ext.kotlin_version = '1.3.72'
repositories { repositories {
google() google()
jcenter() //jcenter()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url 'https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.1' classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3' classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3'
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
@ -20,7 +21,8 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() //jcenter()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url 'https://maven.google.com/' } maven { url 'https://maven.google.com/' }
maven { url 'https://github.com/psiegman/mvn-repo/raw/master/releases' } maven { url 'https://github.com/psiegman/mvn-repo/raw/master/releases' }

Loading…
Cancel
Save