diff --git a/app/build.gradle b/app/build.gradle index 9175af2d1..844f12227 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: "de.timfreiheit.resourceplaceholders" +apply plugin: 'de.timfreiheit.resourceplaceholders' apply plugin: 'io.fabric' androidExtensions { @@ -19,6 +19,7 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec android { compileSdkVersion 29 + flavorDimensions ("version") signingConfigs { if (project.hasProperty("RELEASE_STORE_FILE")) { myConfig { @@ -37,7 +38,6 @@ android { targetSdkVersion 29 versionCode gitCommits versionName version - flavorDimensions "versionCode" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" project.ext.set("archivesBaseName", name + "_" + version) multiDexEnabled true @@ -74,13 +74,15 @@ android { } } } - productFlavors{ - app{ - manifestPlaceholders = [APP_CHANNEL_VALUE:"app"] + productFlavors { + app { + dimension "version" + manifestPlaceholders = [APP_CHANNEL_VALUE: "app"] } - google{ + google { + dimension "version" applicationId "io.legado.play" - manifestPlaceholders = [APP_CHANNEL_VALUE:"google"] + manifestPlaceholders = [APP_CHANNEL_VALUE: "google"] } } compileOptions { @@ -115,14 +117,14 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" //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' //androidX implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.appcompat:appcompat: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.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' @@ -182,14 +184,14 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' //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' //MarkDown implementation 'ru.noties.markwon:core:3.1.0' //转换繁体 - implementation 'com.github.houbb:opencc4j:1.4.0' + implementation 'com.hankcs:hanlp:portable-1.7.7' } diff --git a/app/src/google/res/values-zh-rHK/strings.xml b/app/src/google/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..daa6a610a --- /dev/null +++ b/app/src/google/res/values-zh-rHK/strings.xml @@ -0,0 +1,6 @@ + + + + 閱讀Pro + + \ No newline at end of file diff --git a/app/src/google/res/values/strings.xml b/app/src/google/res/values/strings.xml new file mode 100644 index 000000000..ed2d735c4 --- /dev/null +++ b/app/src/google/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + 阅读Pro + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97b4132cb..b33892324 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -257,7 +257,7 @@ android:launchMode="singleTop" /> 【备份与恢复】,选择【导入旧版本数据】。 -* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 -* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 +* 请关注公众号[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +* 新公众号[开源阅读]()已启用,[开源阅读软件]()备用 + +**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** * 修复7.1.1的网络问题,是retrofit2库最新版本的bug,暂时退回上版本 diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 7cb9eff1f..5c4c02964 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -73,7 +73,7 @@ class App : Application() { .primaryColor( getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600)) ).accentColor( - getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800)) + getPrefInt("colorAccentNight", getCompatColor(R.color.md_deep_orange_800)) ).backgroundColor( getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) ).bottomBackground( diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 810e6deb2..57724c62f 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -49,6 +49,7 @@ object AppConst { val bookGroupAll = BookGroup(-1, App.INSTANCE.getString(R.string.all)) val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) 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 notificationIdAudio = 1144772 diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index e14ec42f6..8913956d3 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -42,4 +42,5 @@ object PreferKey { const val shareLayout = "shareLayout" const val readStyleSelect = "readStyleSelect" const val systemTypefaces = "system_typefaces" + const val readBodyToLh = "readBodyToLh" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index f32d6a199..f7eabc4b9 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, RssStar::class, TxtTocRule::class], - version = 8, + version = 10, exportSchema = true ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt index 38cc452f2..d0588bec8 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt @@ -10,16 +10,16 @@ import io.legado.app.data.entities.BookChapter @Dao interface BookChapterDao { - @Query("select * from chapters where bookUrl = :bookUrl") + @Query("select * from chapters where bookUrl = :bookUrl order by `index`") fun observeByBook(bookUrl: String): LiveData> - @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> - @Query("select * from chapters where bookUrl = :bookUrl") + @Query("select * from chapters where bookUrl = :bookUrl order by `index`") fun getChapterList(bookUrl: String): List - @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 @Query("select * from chapters where bookUrl = :bookUrl and `index` = :index") diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index d8d960df3..c91e01648 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -30,6 +30,9 @@ interface BookDao { @Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") fun observeNoGroup(): LiveData> + @Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") + fun observeNoGroupSize(): LiveData + @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") fun liveDataSearch(key: String): LiveData> @@ -42,6 +45,12 @@ interface BookDao { @Query("SELECT * FROM books WHERE bookUrl = :bookUrl") 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") val webBooks: List diff --git a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt index fbf3c3611..10bf63933 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt @@ -12,17 +12,18 @@ interface RssArticleDao { fun get(origin: String, link: String): RssArticle? @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 - 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> + fun liveByOriginSort(origin: String, sort: String): LiveData> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg rssArticle: RssArticle) - @Query("delete from rssArticles where origin = :origin and `order` < :order") - fun clearOld(origin: String, order: Long) + @Query("delete from rssArticles where origin = :origin and sort = :sort and `order` < :order") + fun clearOld(origin: String, sort: String, order: Long) @Update fun update(vararg rssArticle: RssArticle) diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 0dd5cab9e..fd96d137b 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -4,7 +4,6 @@ import android.os.Parcelable import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index -import androidx.room.PrimaryKey import io.legado.app.constant.AppPattern import io.legado.app.constant.BookType import io.legado.app.utils.GSON @@ -15,9 +14,12 @@ import java.nio.charset.Charset import kotlin.math.max @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( - @PrimaryKey override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) var tocUrl: String = "", // 目录页Url (toc=table of Contents) var origin: String = BookType.local, // 书源URL(默认BookType.local) diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index 78852a4ec..ad16bf99f 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt @@ -169,7 +169,7 @@ data class BookSource( } } val b = a.split("(&&|\n)+".toRegex()) - b.map { c -> + b.forEach { c -> val d = c.split("::") if (d.size > 1) exploreKinds.add(ExploreKind(d[0], d[1])) diff --git a/app/src/main/java/io/legado/app/data/entities/RssArticle.kt b/app/src/main/java/io/legado/app/data/entities/RssArticle.kt index c74597e74..15dafa3ab 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssArticle.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssArticle.kt @@ -9,6 +9,7 @@ import androidx.room.Entity ) data class RssArticle( var origin: String = "", + var sort: String = "", var title: String = "", var order: Long = 0, var link: String = "", @@ -36,6 +37,7 @@ data class RssArticle( fun toStar(): RssStar { return RssStar( origin = origin, + sort = sort, title = title, starTime = System.currentTimeMillis(), link = link, diff --git a/app/src/main/java/io/legado/app/data/entities/RssSource.kt b/app/src/main/java/io/legado/app/data/entities/RssSource.kt index 999b12523..e9a58ee98 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssSource.kt @@ -23,6 +23,7 @@ data class RssSource( var sourceIcon: String = "", var sourceGroup: String? = null, var enabled: Boolean = true, + var sortUrl: String? = null, //列表规则 var ruleArticles: String? = null, var ruleNextPage: String? = null, @@ -99,4 +100,16 @@ data class RssSource( return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) } + fun sortUrls(): LinkedHashMap { + val sortMap = linkedMapOf() + 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 + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/RssStar.kt b/app/src/main/java/io/legado/app/data/entities/RssStar.kt index 1d0c266f8..94196416b 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssStar.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssStar.kt @@ -9,6 +9,7 @@ import androidx.room.Entity ) data class RssStar( var origin: String = "", + var sort: String = "", var title: String = "", var starTime: Long = 0, var link: String = "", @@ -20,6 +21,7 @@ data class RssStar( fun toRssArticle(): RssArticle { return RssArticle( origin = origin, + sort = sort, title = title, link = link, pubDate = pubDate, diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index f2e34c3b6..9d2a3f679 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -115,13 +115,19 @@ object AppConfig { App.INSTANCE.putPrefBoolean("bookGroupAudio", value) } + var bookGroupNoneShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupNone", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupNone", value) + } + var elevation: Int get() = App.INSTANCE.getPrefInt("elevation", -1) set(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" } diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index a147e140d..514c5327c 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -1,6 +1,6 @@ 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.constant.EventBus import io.legado.app.data.entities.Book @@ -231,12 +231,17 @@ object BookHelp { if (!c.substringBefore("\n").contains(title)) { c = "$title\n$c" } - when (AppConfig.chineseConverterType) { - 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) - 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) + try { + when (AppConfig.chineseConverterType) { + 1 -> c = HanLP.convertToSimplifiedChinese(c) + 2 -> c = HanLP.convertToTraditionalChinese(c) + } + } catch (e: Exception) { + withContext(Main) { + App.INSTANCE.toast("简繁转换出错") + } } return c .replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") - .replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行 } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index d0e67e901..ff4180081 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -27,7 +27,6 @@ object ReadBookConfig { GSON.fromJsonArray(json)!! } val durConfig get() = getConfig(styleSelect) - private val shareConfig get() = getConfig(5) var bg: Drawable? = null var bgMeanColor: Int = 0 @@ -139,129 +138,145 @@ object ReadBookConfig { var bodyIndent = " ".repeat(bodyIndentCount) var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) + + private val config get() = if (shareLayout) getConfig(5) else durConfig + var textBold: Boolean - get() = if (shareLayout) shareConfig.textBold else durConfig.textBold - set(value) = if (shareLayout) shareConfig.textBold = value else durConfig.textBold = value + get() = config.textBold + set(value) { + config.textBold = value + } var textSize: Int - get() = if (shareLayout) shareConfig.textSize else durConfig.textSize - set(value) = if (shareLayout) shareConfig.textSize = value else durConfig.textSize = value + get() = config.textSize + set(value) { + config.textSize = value + } var letterSpacing: Float - get() = if (shareLayout) shareConfig.letterSpacing else durConfig.letterSpacing - set(value) = - if (shareLayout) shareConfig.letterSpacing = value else durConfig.letterSpacing = value + get() = config.letterSpacing + set(value) { + config.letterSpacing = value + } var lineSpacingExtra: Int - get() = if (shareLayout) shareConfig.lineSpacingExtra else durConfig.lineSpacingExtra - set(value) = - if (shareLayout) shareConfig.lineSpacingExtra = value - else durConfig.lineSpacingExtra = value + get() = config.lineSpacingExtra + set(value) { + config.lineSpacingExtra = value + } var paragraphSpacing: Int - get() = if (shareLayout) shareConfig.paragraphSpacing else durConfig.paragraphSpacing - set(value) = - if (shareLayout) shareConfig.paragraphSpacing = value - else durConfig.paragraphSpacing = value + get() = config.paragraphSpacing + set(value) { + config.paragraphSpacing = value + } var titleMode: Int - get() = if (shareLayout) shareConfig.titleMode else durConfig.titleMode - set(value) = - if (shareLayout) shareConfig.titleMode = value else durConfig.titleMode = value + get() = config.titleMode + set(value) { + config.titleMode = value + } var titleSize: Int - get() = if (shareLayout) shareConfig.titleSize else durConfig.titleSize - set(value) = - if (shareLayout) shareConfig.titleSize = value else durConfig.titleSize = value + get() = config.titleSize + set(value) { + config.titleSize = value + } + var titleTopSpacing: Int - get() = if (shareLayout) shareConfig.titleTopSpacing else durConfig.titleTopSpacing - set(value) = - if (shareLayout) shareConfig.titleTopSpacing = value - else durConfig.titleTopSpacing = value + get() = config.titleTopSpacing + set(value) { + config.titleTopSpacing = value + } + var titleBottomSpacing: Int - get() = if (shareLayout) shareConfig.titleBottomSpacing else durConfig.titleBottomSpacing - set(value) = - if (shareLayout) shareConfig.titleBottomSpacing = value - else durConfig.titleBottomSpacing = value + get() = config.titleBottomSpacing + set(value) { + config.titleBottomSpacing = value + } var paddingBottom: Int - get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom - set(value) = - if (shareLayout) shareConfig.paddingBottom = value else durConfig.paddingBottom = value + get() = config.paddingBottom + set(value) { + config.paddingBottom = value + } var paddingLeft: Int - get() = if (shareLayout) shareConfig.paddingLeft else durConfig.paddingLeft - set(value) = - if (shareLayout) shareConfig.paddingLeft = value else durConfig.paddingLeft = value + get() = config.paddingLeft + set(value) { + config.paddingLeft = value + } var paddingRight: Int - get() = if (shareLayout) shareConfig.paddingRight else durConfig.paddingRight - set(value) = - if (shareLayout) shareConfig.paddingRight = value else durConfig.paddingRight = value + get() = config.paddingRight + set(value) { + config.paddingRight = value + } var paddingTop: Int - get() = if (shareLayout) shareConfig.paddingTop else durConfig.paddingTop - set(value) = - if (shareLayout) shareConfig.paddingTop = value else durConfig.paddingTop = value + get() = config.paddingTop + set(value) { + config.paddingTop = value + } var headerPaddingBottom: Int - get() = if (shareLayout) shareConfig.headerPaddingBottom else durConfig.headerPaddingBottom - set(value) = - if (shareLayout) shareConfig.headerPaddingBottom = value - else durConfig.headerPaddingBottom = value + get() = config.headerPaddingBottom + set(value) { + config.headerPaddingBottom = value + } var headerPaddingLeft: Int - get() = if (shareLayout) shareConfig.headerPaddingLeft else durConfig.headerPaddingLeft - set(value) = - if (shareLayout) shareConfig.headerPaddingLeft = value - else durConfig.headerPaddingLeft = value + get() = config.headerPaddingLeft + set(value) { + config.headerPaddingLeft = value + } var headerPaddingRight: Int - get() = if (shareLayout) shareConfig.headerPaddingRight else durConfig.headerPaddingRight - set(value) = - if (shareLayout) shareConfig.headerPaddingRight = value - else durConfig.headerPaddingRight = value + get() = config.headerPaddingRight + set(value) { + config.headerPaddingRight = value + } var headerPaddingTop: Int - get() = if (shareLayout) shareConfig.headerPaddingTop else durConfig.headerPaddingTop - set(value) = - if (shareLayout) shareConfig.headerPaddingTop = value - else durConfig.headerPaddingTop = value + get() = config.headerPaddingTop + set(value) { + config.headerPaddingTop = value + } var footerPaddingBottom: Int - get() = if (shareLayout) shareConfig.footerPaddingBottom else durConfig.footerPaddingBottom - set(value) = - if (shareLayout) shareConfig.footerPaddingBottom = value - else durConfig.footerPaddingBottom = value + get() = config.footerPaddingBottom + set(value) { + config.footerPaddingBottom = value + } var footerPaddingLeft: Int - get() = if (shareLayout) shareConfig.footerPaddingLeft else durConfig.footerPaddingLeft - set(value) = - if (shareLayout) shareConfig.footerPaddingLeft = value - else durConfig.footerPaddingLeft = value + get() = config.footerPaddingLeft + set(value) { + config.footerPaddingLeft = value + } var footerPaddingRight: Int - get() = if (shareLayout) shareConfig.footerPaddingRight else durConfig.footerPaddingRight - set(value) = - if (shareLayout) shareConfig.footerPaddingRight = value - else durConfig.footerPaddingRight = value + get() = config.footerPaddingRight + set(value) { + config.footerPaddingRight = value + } var footerPaddingTop: Int - get() = if (shareLayout) shareConfig.footerPaddingTop else durConfig.footerPaddingTop - set(value) = - if (shareLayout) shareConfig.footerPaddingTop = value - else durConfig.footerPaddingTop = value + get() = config.footerPaddingTop + set(value) { + config.footerPaddingTop = value + } var showHeaderLine: Boolean - get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine - set(value) = - if (shareLayout) shareConfig.showHeaderLine = value - else durConfig.showHeaderLine = value + get() = config.showHeaderLine + set(value) { + config.showHeaderLine = value + } var showFooterLine: Boolean - get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine - set(value) = - if (shareLayout) shareConfig.showFooterLine = value - else durConfig.showFooterLine = value + get() = config.showFooterLine + set(value) { + config.showFooterLine = value + } @Keep class Config( diff --git a/app/src/main/java/io/legado/app/help/ReadTipConfig.kt b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt new file mode 100644 index 000000000..9d6452b38 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt @@ -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 = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt b/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt index f501fcb71..4f5cbda46 100644 --- a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt +++ b/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt @@ -180,6 +180,7 @@ class AjaxWebView { mWebView.get()?.evaluateJavascript(mJavaScript) { if (it.isNotEmpty() && it != "null") { val content = StringEscapeUtils.unescapeJson(it) + .replace("^\"|\"$".toRegex(), "") handler.obtainMessage(MSG_SUCCESS, Res(url, content)) .sendToTarget() handler.removeCallbacks(this) diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index c47951385..5b1e88186 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -32,9 +32,7 @@ object Backup { val lastBackup = context.getPrefLong(PreferKey.lastBackup) if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) { Coroutine.async { - context.getPrefString(PreferKey.backupPath)?.let { - backup(context, it, true) - } + backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true) } } } @@ -71,7 +69,11 @@ object Backup { if (path.isContentPath()) { copyBackup(context, Uri.parse(path), isAuto) } else { - copyBackup(File(path), isAuto) + if (path.isEmpty()) { + copyBackup(context.getExternalFilesDir(null)!!, false) + } else { + copyBackup(File(path), isAuto) + } } } } diff --git a/app/src/main/java/io/legado/app/help/storage/OldRule.kt b/app/src/main/java/io/legado/app/help/storage/OldRule.kt index 7696bb383..d605b02fe 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldRule.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldRule.kt @@ -19,7 +19,7 @@ object OldRule { source = GSON.fromJsonObject(json.trim()) } runCatching { - if (source == null || source?.searchUrl.isNullOrBlank()) { + if (source == null || source?.ruleToc.isNullOrBlank()) { source = BookSource().apply { val jsonItem = jsonPath.parse(json.trim()) bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: "" diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 51ae12afd..58944e05b 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -54,7 +54,8 @@ object Debug { cancelDebug() debugSource = rssSource.sourceUrl log(debugSource, "︾开始解析") - Rss.getArticles(rssSource, null) + val sort = rssSource.sortUrls().entries.first() + Rss.getArticles(sort.key, sort.value, rssSource, null) .onSuccess { if (it.articles.isEmpty()) { log(debugSource, "⇒列表页解析成功,为空") diff --git a/app/src/main/java/io/legado/app/model/Rss.kt b/app/src/main/java/io/legado/app/model/Rss.kt index 24dadade0..8b29913d9 100644 --- a/app/src/main/java/io/legado/app/model/Rss.kt +++ b/app/src/main/java/io/legado/app/model/Rss.kt @@ -15,6 +15,8 @@ import kotlin.coroutines.CoroutineContext object Rss { fun getArticles( + sortName: String, + sortUrl: String, rssSource: RssSource, pageUrl: String? = null, scope: CoroutineScope = Coroutine.DEFAULT, @@ -22,11 +24,11 @@ object Rss { ): Coroutine { return Coroutine.async(scope, context) { val analyzeUrl = AnalyzeUrl( - pageUrl ?: rssSource.sourceUrl, + pageUrl ?: sortUrl, headerMapF = rssSource.getHeaderMap() ) val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body - RssParserByRule.parseXML(body, rssSource) + RssParserByRule.parseXML(sortName, sortUrl, body, rssSource) } } diff --git a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt index 96de16e8c..494437d92 100644 --- a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt @@ -18,6 +18,7 @@ class AnalyzeTxtFile { private val tocRules = arrayListOf() private lateinit var charset: Charset + @Throws(Exception::class) fun analyze(context: Context, book: Book): ArrayList { val bookFile = getBookFile(context, book) book.charset = EncodingDetect.getEncode(bookFile) @@ -33,6 +34,7 @@ class AnalyzeTxtFile { return analyze(bookStream, book, rulePattern) } + @Throws(Exception::class) private fun analyze( bookStream: RandomAccessFile, book: Book, @@ -58,18 +60,19 @@ class AnalyzeTxtFile { var allLength = 0 //获取文件中的数据到buffer,直到没有数据为止 - while (bookStream.read(buffer, 0, buffer.size).also { length = it } > 0) { + while (bookStream.read(buffer).also { length = it } > 0) { blockPos++ - var blockContent = String(buffer, charset) - val lastN = blockContent.lastIndexOf("\n") - if (lastN > 0) { - blockContent = blockContent.substring(0, lastN) - length = blockContent.toByteArray(charset).size - allLength += length - bookStream.seek(allLength.toLong()) - } //如果存在Chapter - if (rulePattern != null) { //将数据转换成String + if (rulePattern != null) { + //将数据转换成String, 不能超过length + var blockContent = String(buffer, 0, length, charset) + val lastN = blockContent.lastIndexOf("\n") + if (lastN > 0) { + blockContent = blockContent.substring(0, lastN) + length = blockContent.toByteArray(charset).size + allLength += length + bookStream.seek(allLength.toLong()) + } //当前Block下使过的String的指针 var seekPos = 0 //进行正则匹配 @@ -79,35 +82,59 @@ class AnalyzeTxtFile { val chapterStart = matcher.start() //获取章节内容 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) return analyze(bookStream, book, null) } //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 - //第一种情况一定是序章 第二种情况可能是上一个章节的内容 - if (seekPos == 0 && chapterStart != 0 && toc.isEmpty()) { //获取当前章节的内容 - val chapter = BookChapter() - chapter.title = "前言" - chapter.start = 0 - chapter.end = chapterContent.toByteArray(charset).size.toLong() - toc.add(chapter) - //创建当前章节 - val curChapter = BookChapter() - curChapter.title = matcher.group() - curChapter.start = chapter.end - toc.add(curChapter) + //第一种情况一定是序章 第二种情况是上一个章节的内容 + if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容 + if (toc.isEmpty()) { //如果当前没有章节,那么就是序章 + //加入简介 + if (StringUtils.trim(chapterContent).isNotEmpty()) { + val qyChapter = BookChapter() + qyChapter.title = "前言" + qyChapter.start = 0 + qyChapter.end = chapterLength.toLong() + toc.add(qyChapter) + } + //创建当前章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + 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) + } } else { - val lastChapter = toc.lastOrNull() - lastChapter?.let { - //上一章节结束等于这一章节开始 - it.end = it.start!! + chapterContent.toByteArray(charset).size + if (toc.isNotEmpty()) { //获取章节内容 + //获取上一章节 + val lastChapter = toc.last() + lastChapter.end = + lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong() + //创建当前章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = lastChapter.end + toc.add(curChapter) + } else { //如果章节不存在则创建章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = 0 + curChapter.end = 0 + toc.add(curChapter) } - //创建当前章节 - val curChapter = BookChapter() - curChapter.title = matcher.group() - curChapter.start = lastChapter?.end ?: 0 - toc.add(curChapter) } //设置指针偏移 seekPos += chapterContent.length @@ -154,7 +181,8 @@ class AnalyzeTxtFile { //block的偏移点 curOffset += length.toLong() - if (rulePattern != null) { //设置上一章的结尾 + if (rulePattern != null) { + //设置上一章的结尾 val lastChapter = toc.last() lastChapter.end = curOffset } @@ -173,6 +201,7 @@ class AnalyzeTxtFile { bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title) ?: "") } book.latestChapterTitle = toc.last().title + book.totalChapterNum = toc.size System.gc() System.runFinalization() @@ -220,10 +249,9 @@ class AnalyzeTxtFile { val bookFile = getBookFile(App.INSTANCE, book) //获取文件流 val bookStream = RandomAccessFile(bookFile, "r") - bookStream.seek(bookChapter.start ?: 0) - val extent = (bookChapter.end!! - bookChapter.start!!).toInt() - val content = ByteArray(extent) - bookStream.read(content, 0, extent) + val content = ByteArray((bookChapter.end!! - bookChapter.start!!).toInt()) + bookStream.seek(bookChapter.start!!) + bookStream.read(content) return String(content, book.fileCharset()) } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParser.kt b/app/src/main/java/io/legado/app/model/rss/RssParser.kt index 7bda16e6d..09cd756bb 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParser.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParser.kt @@ -11,7 +11,7 @@ import java.io.StringReader object RssParser { @Throws(XmlPullParserException::class, IOException::class) - fun parseXML(xml: String, sourceUrl: String): Result { + fun parseXML(sortName: String, xml: String, sourceUrl: String): Result { val articleList = mutableListOf() var currentArticle = RssArticle() @@ -87,6 +87,7 @@ object RssParser { // The item is correctly parsed insideItem = false currentArticle.origin = sourceUrl + currentArticle.sort = sortName articleList.add(currentArticle) currentArticle = RssArticle() } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index b06950031..cd90b6b1d 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -13,7 +13,7 @@ import io.legado.app.utils.NetworkUtils object RssParserByRule { @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 var nextUrl: String? = null if (body.isNullOrBlank()) { @@ -28,11 +28,11 @@ object RssParserByRule { var ruleArticles = rssSource.ruleArticles if (ruleArticles.isNullOrBlank()) { Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析") - return RssParser.parseXML(body, sourceUrl) + return RssParser.parseXML(sortName, body, sourceUrl) } else { val articleList = mutableListOf() val analyzeRule = AnalyzeRule() - analyzeRule.setContent(body, rssSource.sourceUrl) + analyzeRule.setContent(body, sortUrl) var reverse = false if (ruleArticles.startsWith("-")) { reverse = true @@ -45,7 +45,7 @@ object RssParserByRule { Debug.log(sourceUrl, "┌获取下一页链接") nextUrl = analyzeRule.getString(rssSource.ruleNextPage) if (nextUrl.isNotEmpty()) { - nextUrl = NetworkUtils.getAbsoluteURL(sourceUrl, nextUrl) + nextUrl = NetworkUtils.getAbsoluteURL(sortUrl, nextUrl) } Debug.log(sourceUrl, "└$nextUrl") } @@ -59,7 +59,8 @@ object RssParserByRule { sourceUrl, item, analyzeRule, index == 0, ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink )?.let { - it.origin = rssSource.sourceUrl + it.sort = sortName + it.origin = sourceUrl articleList.add(it) } } diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 8559351f4..78a139345 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -193,6 +193,7 @@ object BookChapterList { list.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle if (book.totalChapterNum < list.size) { book.lastCheckCount = list.size - book.totalChapterNum + book.latestChapterTime = System.currentTimeMillis() } book.totalChapterNum = list.size return list diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 873b6b3a9..d83ffb832 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -101,6 +101,7 @@ object BookContent { } } + content.deleteCharAt(content.length - 1) Debug.log(bookSource.bookSourceUrl, "┌获取章节名称") Debug.log(bookSource.bookSourceUrl, "└${bookChapter.title}") Debug.log(bookSource.bookSourceUrl, "┌获取正文内容") diff --git a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt index 00c9d0522..fb1f5df59 100644 --- a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt @@ -8,9 +8,14 @@ import io.legado.app.App import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book 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.book.read.ReadBookActivity import io.legado.app.ui.main.MainActivity +import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.postEvent import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main @@ -50,11 +55,23 @@ class MediaButtonReceiver : BroadcastReceiver() { private fun readAloud(context: Context) { 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) -> postEvent(EventBus.MEDIA_BUTTON, true) ActivityHelp.isExist(ReadBookActivity::class.java) -> postEvent(EventBus.MEDIA_BUTTON, true) - else -> { + else -> if (context.getPrefBoolean("mediaButtonOnExit", true)) { GlobalScope.launch(Main) { val lastBook: Book? = withContext(IO) { App.db.bookDao().lastReadBook diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index 336dfb18d..8cbd80afd 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -7,20 +7,22 @@ import io.legado.app.R import io.legado.app.base.BaseService import io.legado.app.constant.AppConst import io.legado.app.constant.IntentAction +import io.legado.app.data.entities.BookSource import io.legado.app.help.AppConfig 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.ui.book.source.manage.BookSourceActivity import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher import org.jetbrains.anko.toast import java.util.concurrent.Executors +import kotlin.math.min class CheckSourceService : BaseService() { private val threadCount = AppConfig.threadCount private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() - private var task: Coroutine<*>? = null + private var tasks = CompositeCoroutine() private val allIds = ArrayList() private val checkedIds = ArrayList() private var processIndex = 0 @@ -42,23 +44,23 @@ class CheckSourceService : BaseService() { override fun onDestroy() { super.onDestroy() - task?.cancel() + tasks.clear() searchPool.close() } private fun check(ids: List) { - task?.cancel() + if (allIds.isNotEmpty()) { + toast("已有书源在校验,等完成后再试") + return + } + tasks.clear() allIds.clear() checkedIds.clear() allIds.addAll(ids) processIndex = 0 updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) - task = execute { - for (i in 0 until threadCount) { - check() - } - }.onError { - toast("校验书源出错:${it.localizedMessage}") + for (i in 0 until min(threadCount, allIds.size)) { + check() } } @@ -67,27 +69,41 @@ class CheckSourceService : BaseService() { synchronized(this) { processIndex++ } - if (processIndex < allIds.size) { - val sourceUrl = allIds[processIndex] - App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> - val webBook = WebBook(source) - webBook.searchBook("我的", scope = this, context = searchPool) - .onError(IO) { - source.addGroup("失效") - App.db.bookSourceDao().update(source) - }.onFinally(IO) { - check() - checkedIds.add(sourceUrl) - updateNotification( - checkedIds.size, - getString(R.string.progress_show, checkedIds.size, allIds.size) - ) - synchronized(this) { - if (processIndex >= allIds.size + threadCount - 1) { - stopSelf() - } - } + execute { + if (processIndex < allIds.size) { + val sourceUrl = allIds[processIndex] + 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) + tasks.add(webBook.searchBook("我的", scope = this, context = searchPool) + .onError(IO) { + source.addGroup("失效") + App.db.bookSourceDao().update(source) + }.onFinally(IO) { + onNext(source.bookSourceUrl) + }) + } + + private fun onNext(sourceUrl: String) { + synchronized(this) { + check() + checkedIds.add(sourceUrl) + updateNotification( + checkedIds.size, + getString(R.string.progress_show, checkedIds.size, allIds.size) + ) + if (processIndex >= allIds.size + min(threadCount, allIds.size) - 1) { + stopSelf() } } } diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index dd48933e8..1cbfe44f4 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -13,6 +13,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.IntentHelp +import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.WebBook import io.legado.app.utils.postEvent @@ -25,7 +26,7 @@ import java.util.concurrent.Executors class DownloadService : BaseService() { private var searchPool = Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var tasks: ArrayList> = arrayListOf() + private var tasks = CompositeCoroutine() private val handler = Handler() private var runnable: Runnable = Runnable { upDownload() } private val downloadMap = hashMapOf>() @@ -125,11 +126,7 @@ class DownloadService : BaseService() { chapter, scope = this, context = searchPool - ) - //.onStart { - // notificationContent = "启动:" + chapter.title - //} - .onSuccess(IO) { content -> + ).onSuccess(IO) { content -> downloadCount[entry.key]?.increaseSuccess() BookHelp.saveContent(book, chapter, content) } @@ -165,7 +162,7 @@ class DownloadService : BaseService() { tasks.add(task) task.invokeOnCompletion { tasks.remove(task) - if (tasks.isEmpty()) { + if (tasks.isEmpty) { stopSelf() } } @@ -192,17 +189,21 @@ class DownloadService : BaseService() { val notification = builder.build() startForeground(AppConst.notificationIdDownload, notification) } -} -class DownloadCount{ - @Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量 - @Volatile public var successCount = 0 //下载成功的条目数量 - fun increaseSuccess(){ - ++successCount; - } + class DownloadCount { + @Volatile + var downloadFinishedCount = 0 // 下载完成的条目数量 + + @Volatile + var successCount = 0 //下载成功的条目数量 - fun increaseFinished(){ - ++downloadFinishedCount; + fun increaseSuccess() { + ++successCount; + } + + fun increaseFinished() { + ++downloadFinishedCount; + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index f48f13d11..89f730ae5 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -331,7 +331,7 @@ object ReadBook { book.durChapterTime = System.currentTimeMillis() book.durChapterIndex = durChapterIndex book.durChapterPos = durPageIndex - curTextChapter?.let { + App.db.bookChapterDao().getChapter(book.bookUrl, durChapterIndex)?.let { book.durChapterTitle = it.title } App.db.bookDao().update(book) diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt index 365381fbd..3b56737cf 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt @@ -22,6 +22,7 @@ class AboutFragment : PreferenceFragmentCompat() { private val qqGroups = linkedMapOf( Pair("(QQ群VIP1)701903217", "-iolizL4cbJSutKRpeImHlXlpLDZnzeF"), Pair("(QQ群VIP2)263949160", "xwfh7_csb2Gf3Aw2qexEcEtviLfLfd4L"), + Pair("(QQ群VIP3)680280282", "_N0i7yZObjKSeZQvzoe2ej7j02kLnOOK"), Pair("(QQ群1)805192012", "6GlFKjLeIk5RhQnR3PNVDaKB6j10royo"), Pair("(QQ群2)773736122", "5Bm5w6OgLupXnICbYvbgzpPUgf0UlsJF"), Pair("(QQ群3)981838750", "g_Sgmp2nQPKqcZQ5qPcKLHziwX_mpps9"), diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index 8f0cd9fa4..124fd710f 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -11,7 +11,6 @@ import io.legado.app.help.BookHelp import io.legado.app.model.WebBook import io.legado.app.service.help.AudioPlay import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext class AudioPlayViewModel(application: Application) : BaseViewModel(application) { @@ -93,9 +92,6 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) AudioPlay.book?.let { book1.order = it.order App.db.bookDao().delete(it) - } - withContext(Dispatchers.Main) { - } App.db.bookDao().insert(book1) AudioPlay.book = book1 diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index e7d78a6f4..491922cb1 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.AppConst import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup @@ -105,10 +106,10 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi booksLiveData?.removeObservers(this) booksLiveData = when (groupId) { - -1 -> App.db.bookDao().observeAll() - -2 -> App.db.bookDao().observeLocal() - -3 -> App.db.bookDao().observeAudio() - -11 -> App.db.bookDao().observeNoGroup() + AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll() + AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal() + AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio() + AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } booksLiveData?.observe(this, Observer { list -> @@ -129,22 +130,22 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi .show(supportFragmentManager, "groupManage") R.id.menu_no_group -> { title_bar.subtitle = getString(R.string.no_group) - groupId = -11 + groupId = AppConst.bookGroupNone.groupId initBookData() } R.id.menu_all -> { title_bar.subtitle = item.title - groupId = -1 + groupId = AppConst.bookGroupAll.groupId initBookData() } R.id.menu_local -> { title_bar.subtitle = item.title - groupId = -2 + groupId = AppConst.bookGroupLocal.groupId initBookData() } R.id.menu_audio -> { title_bar.subtitle = item.title - groupId = -3 + groupId = AppConst.bookGroupAudio.groupId initBookData() } else -> if (item.groupId == R.id.menu_group) { diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt index d717cb25f..104f78e23 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.chapterlist +import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent 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.BookChapter 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.VerticalDivider import io.legado.app.utils.getViewModelOfActivity @@ -54,7 +55,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } 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_bottom.onClick { if (adapter.itemCount > 0) { @@ -66,6 +67,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } } + @SuppressLint("SetTextI18n") private fun initBook() { launch { withContext(IO) { @@ -74,7 +76,8 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme initDoc() book?.let { durChapterIndex = it.durChapterIndex - tv_current_chapter_info.text = it.durChapterTitle + tv_current_chapter_info.text = + "${it.durChapterTitle}(${it.durChapterIndex + 1}/${it.totalChapterNum})" initCacheFileNames(it) } } diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 60010f46d..a5d719b57 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -99,6 +99,8 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { .isChecked = AppConfig.bookGroupLocalShow it.findItem(R.id.menu_group_audio) .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 callBack?.upGroup() } + R.id.menu_group_none -> { + item.isChecked = !item.isChecked + AppConfig.bookGroupNoneShow = item.isChecked + } } return true } diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt index 29df624e7..9440a7c46 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt @@ -94,8 +94,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.inflateMenu(R.menu.book_group_manage) tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.setOnMenuItemClickListener(this) - tool_bar.menu.findItem(R.id.menu_group_local).isVisible = false - tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false + tool_bar.menu.setGroupVisible(R.id.menu_groups, false) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index ba83c10ef..47dc6c33f 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -112,6 +112,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { toast(R.string.error_no_source) } } + }.onError { + toast("LoadTocError:${it.localizedMessage}") } } @@ -156,8 +158,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { chapters ) book.durChapterTitle = chapters[book.durChapterIndex].title - App.db.bookDao().insert(book) - App.db.bookChapterDao().insert(*chapters.toTypedArray()) + if (inBookshelf) { + App.db.bookDao().insert(book) + App.db.bookChapterDao().insert(*chapters.toTypedArray()) + } bookData.postValue(book) chapterListData.postValue(chapters) } @@ -169,6 +173,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { if (book.order == 0) { 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) } }.onSuccess { @@ -192,6 +200,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { if (book.order == 0) { 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) } chapterListData.value?.let { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 043c419eb..43d03c332 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -134,6 +134,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo timeBatteryReceiver = null } upSystemUiVisibility() + if (!BuildConfig.DEBUG) { + SyncBookProgress.uploadBookProgress() + Backup.autoBack(this) + } } /** @@ -225,6 +229,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo R.id.menu_copy_text -> TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent()) R.id.menu_update_toc -> ReadBook.book?.let { + ReadBook.upMsg(getString(R.string.toc_updateing)) viewModel.loadChapterList(it) } R.id.menu_enable_replace -> ReadBook.book?.let { @@ -705,6 +710,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo observeEvent(EventBus.UP_CONFIG) { upSystemUiVisibility() page_view.upBg() + page_view.upTipStyle() page_view.upStyle() if (it) { ReadBook.loadContent(resetPageOffset = false) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 977a65a10..cc129056a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -107,6 +107,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = it.size + ReadBook.upMsg(null) ReadBook.loadContent(resetPageOffset = true) } } else { @@ -129,6 +130,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { 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 { - ReadBook.upMsg("正在自动换源") + ReadBook.upMsg(context.getString(R.string.source_auto_changing)) }.onFinally { ReadBook.upMsg(null) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index a23725143..8672d04bb 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -94,6 +94,7 @@ class MoreConfigDialog : DialogFragment() { key: String? ) { when (key) { + PreferKey.readBodyToLh -> activity?.recreate() PreferKey.hideStatusBar -> { ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) postEvent(EventBus.UP_CONFIG, true) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index ed1c296de..4534898e4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -11,10 +11,7 @@ import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.help.ReadBookConfig 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.visible import kotlinx.android.synthetic.main.dialog_read_padding.* class PaddingConfigDialog : DialogFragment() { @@ -54,13 +51,6 @@ class PaddingConfigDialog : DialogFragment() { } 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_bottom.progress = paddingBottom diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index f29c4ddba..19cad6738 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -104,10 +104,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { showTitleConfig() } tv_text_bold.onClick { - ReadBookConfig.apply { - textBold = !textBold - tv_text_bold.isSelected = textBold - } + ReadBookConfig.textBold = !ReadBookConfig.textBold + tv_text_bold.isSelected = ReadBookConfig.textBold postEvent(EventBus.UP_CONFIG, true) } tv_text_font.onClick { @@ -126,6 +124,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { dismiss() callBack?.showPaddingConfig() } + tv_tip.onClick { + TipConfigDialog().show(childFragmentManager, "tipConfigDialog") + } rg_page_anim.onCheckedChange { _, checkedId -> ReadBookConfig.pageAnim = rg_page_anim.getIndexById(checkedId) callBack?.page_view?.upPageAnim() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt new file mode 100644 index 000000000..cb9cfa594 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt @@ -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) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt index 0d6a0009e..ea0a1bd37 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.book.read.page import android.graphics.Typeface +import android.os.Build import android.text.Layout import android.text.StaticLayout import android.text.TextPaint @@ -31,8 +32,8 @@ object ChapterProvider { private var titleTopSpacing = 0 private var titleBottomSpacing = 0 var typeface: Typeface = Typeface.SANS_SERIF - var titlePaint = TextPaint() - var contentPaint = TextPaint() + lateinit var titlePaint: TextPaint + lateinit var contentPaint: TextPaint init { upStyle() @@ -262,30 +263,36 @@ object ChapterProvider { Typeface.SANS_SERIF } //标题 - titlePaint.isAntiAlias = true + titlePaint = TextPaint() titlePaint.color = ReadBookConfig.durConfig.textColor() 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.isAntiAlias = true //正文 - contentPaint.isAntiAlias = true + contentPaint = TextPaint() contentPaint.color = ReadBookConfig.durConfig.textColor() contentPaint.letterSpacing = ReadBookConfig.letterSpacing val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL contentPaint.typeface = Typeface.create(typeface, style) contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() + contentPaint.isAntiAlias = true //间距 lineSpacingExtra = ReadBookConfig.lineSpacingExtra paragraphSpacing = ReadBookConfig.paragraphSpacing titleTopSpacing = ReadBookConfig.titleTopSpacing.dp titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp - upSize() + upViewSize() } /** * 更新View尺寸 */ - fun upSize() { + fun upViewSize() { paddingLeft = ReadBookConfig.paddingLeft.dp paddingTop = ReadBookConfig.paddingTop.dp visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 53f03462f..62f240303 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -61,7 +61,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at super.onSizeChanged(w, h, oldw, oldh) ChapterProvider.viewWidth = w ChapterProvider.viewHeight = h - ChapterProvider.upSize() + ChapterProvider.upViewSize() upVisibleRect() textPage.format() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index d87311f09..67e40bf0e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -5,13 +5,20 @@ import android.content.Context import android.graphics.drawable.Drawable import android.view.MotionEvent 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.constant.AppConst.timeFormat import io.legado.app.help.AppConfig 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.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 java.util.* @@ -19,14 +26,22 @@ import java.util.* class ContentView(context: Context) : FrameLayout(context) { 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 { //设置背景防止切换背景时文字重叠 setBackgroundColor(context.getCompatColor(R.color.background)) inflate(context, R.layout.view_book_page, this) - + upTipStyle() upStyle() - upTime() content_text_view.upView = { setProgress(it) } @@ -34,25 +49,27 @@ class ContentView(context: Context) : FrameLayout(context) { fun upStyle() { ReadBookConfig.apply { - tv_top_left.typeface = ChapterProvider.typeface - tv_top_right.typeface = ChapterProvider.typeface - tv_bottom_left.typeface = ChapterProvider.typeface - tv_bottom_right.typeface = ChapterProvider.typeface - battery_view.typeface = ChapterProvider.typeface + tv_header_left.typeface = ChapterProvider.typeface + tv_header_middle.typeface = ChapterProvider.typeface + tv_header_right.typeface = ChapterProvider.typeface + tv_footer_left.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 - if (hideStatusBar) { - ll_header.setPadding( - headerPaddingLeft.dp, - headerPaddingTop.dp, - headerPaddingRight.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) - } + vw_status_bar.setPadding(0, context.statusBarHeight, 0, 0) + vw_status_bar.isGone = hideStatusBar + ll_header.setPadding( + headerPaddingLeft.dp, + headerPaddingTop.dp, + headerPaddingRight.dp, + headerPaddingBottom.dp + ) ll_footer.setPadding( footerPaddingLeft.dp, footerPaddingTop.dp, @@ -62,47 +79,119 @@ class ContentView(context: Context) : FrameLayout(context) { vw_top_divider.visible(showHeaderLine) vw_bottom_divider.visible(showFooterLine) 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) { - tv_bottom_left.text = timeFormat.format(Date(System.currentTimeMillis())) - battery_view.visible() - battery_view.setBattery(battery) - } else { - battery_view.gone() - } } + upTime() + upBattery(battery) } - val headerHeight: Int - get() { - return if (ReadBookConfig.hideStatusBar) { - ll_header.height - } else { - context.statusBarHeight - } + 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 { + isBattery = false + textSize = 12f + } + tvTime = when (ReadTipConfig.time) { + 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 + } + 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 + } + } fun setBg(bg: Drawable?) { page_panel.background = bg } fun upTime() { - if (ReadBookConfig.hideStatusBar) { - tv_bottom_right.text = timeFormat.format(Date(System.currentTimeMillis())) - } + tvTime?.text = timeFormat.format(Date(System.currentTimeMillis())) } fun upBattery(battery: Int) { this.battery = battery - if (ReadBookConfig.hideStatusBar) { - battery_view.setBattery(battery) - } + tvBattery?.setBattery(battery) } fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) { @@ -119,18 +208,14 @@ class ContentView(context: Context) : FrameLayout(context) { @SuppressLint("SetTextI18n") fun setProgress(textPage: TextPage) = textPage.apply { val title = when (AppConfig.chineseConverterType) { - 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) - 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) + 1 -> HanLP.convertToSimplifiedChinese(textPage.title) + 2 -> HanLP.convertToTraditionalChinese(textPage.title) else -> textPage.title } - if (ReadBookConfig.hideStatusBar) { - tv_top_left.text = title - tv_top_right.text = readProgress - tv_bottom_left.text = "${index.plus(1)}/$pageSize" - } else { - tv_bottom_left.text = title - tv_bottom_right.text = "${index.plus(1)}/$pageSize $readProgress" - } + tvTitle?.text = title + tvPage?.text = "${index.plus(1)}/$pageSize" + tvTotalProgress?.text = readProgress + tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress" } fun onScroll(offset: Float) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index dd19fdc0e..3a996bfe9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -110,6 +110,12 @@ class PageView(context: Context, attrs: AttributeSet) : callBack.screenOffTimerStart() } + fun upTipStyle() { + curPage.upTipStyle() + prevPage.upTipStyle() + nextPage.upTipStyle() + } + fun upStyle() { ChapterProvider.upStyle() curPage.upStyle() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 2fcf2f7e2..e81af6986 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -44,12 +44,11 @@ data class TextPage( if (y < 0) y = 0f for (lineIndex in 0 until layout.lineCount) { val textLine = TextLine() - textLine.lineTop = (ChapterProvider.paddingTop + y - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))) - textLine.lineBase = (ChapterProvider.paddingTop + y - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))) + textLine.lineTop = ChapterProvider.paddingTop + y + layout.getLineTop(lineIndex) + textLine.lineBase = + ChapterProvider.paddingTop + y + layout.getLineBaseline(lineIndex) textLine.lineBottom = - textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent + ChapterProvider.paddingTop + y + layout.getLineBottom(lineIndex) var x = ChapterProvider.paddingLeft + (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 textLine.text = diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt index 8f0f46d94..5857fe8ef 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jetbrains.anko.toast import java.io.File -import java.net.URLEncoder class BookSourceViewModel(application: Application) : BaseViewModel(application) { @@ -228,7 +227,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } private fun importSourceUrl(url: String): Int { - HttpHelper.simpleGet(url)?.let { body -> + HttpHelper.simpleGet(url, "UTF-8")?.let { body -> val bookSources = mutableListOf() val items: List> = jsonPath.parse(body).read("$") for (item in items) { diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 0aa49ede7..181ec0330 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -15,6 +15,7 @@ import io.legado.app.base.VMBaseActivity import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig +import io.legado.app.help.storage.Backup import io.legado.app.lib.theme.ATH import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud @@ -77,7 +78,7 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) if (!BuildConfig.DEBUG) { 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(R.layout.activity_main), return super.onKeyUp(keyCode, event) } + override fun onPause() { + super.onPause() + if (!BuildConfig.DEBUG) { + Backup.autoBack(this) + } + } + override fun onDestroy() { super.onDestroy() ReadAloud.stop(this) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 55dca8d77..f51bdc0a6 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -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.view_tab_layout.* 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 @@ -51,8 +54,10 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b get() = getViewModel(BookshelfViewModel::class.java) private var bookGroupLiveData: LiveData>? = null + private var noGroupLiveData: LiveData? = null private val bookGroups = mutableListOf() private val fragmentMap = hashMapOf() + private var showGroupNone = false override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { setSupportToolbar(toolbar) @@ -99,22 +104,49 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b bookGroupLiveData = App.db.bookGroupDao().liveDataAll() bookGroupLiveData?.observe(viewLifecycleOwner, Observer { viewModel.checkGroup(it) - synchronized(this) { - tab_layout.removeOnTabSelectedListener(this) - bookGroups.clear() - if (AppConfig.bookGroupAllShow) { - bookGroups.add(AppConst.bookGroupAll) + launch { + synchronized(this) { + tab_layout.removeOnTabSelectedListener(this@BookshelfFragment) } - if (AppConfig.bookGroupLocalShow) { - bookGroups.add(AppConst.bookGroupLocal) + var noGroupSize = 0 + withContext(IO) { + if (AppConfig.bookGroupNoneShow) { + noGroupSize = App.db.bookDao().noGroupSize + } } - if (AppConfig.bookGroupAudioShow) { - bookGroups.add(AppConst.bookGroupAudio) + synchronized(this@BookshelfFragment) { + bookGroups.clear() + if (AppConfig.bookGroupAllShow) { + bookGroups.add(AppConst.bookGroupAll) + } + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(AppConst.bookGroupLocal) + } + if (AppConfig.bookGroupAudioShow) { + bookGroups.add(AppConst.bookGroupAudio) + } + showGroupNone = if (noGroupSize > 0 && it.isNotEmpty()) { + bookGroups.add(AppConst.bookGroupNone) + true + } else { + false + } + bookGroups.addAll(it) + view_pager_bookshelf.adapter?.notifyDataSetChanged() + tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() + tab_layout.addOnTabSelectedListener(this@BookshelfFragment) } - bookGroups.addAll(it) - view_pager_bookshelf.adapter?.notifyDataSetChanged() - tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() - tab_layout.addOnTabSelectedListener(this) + } + }) + 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,20 +161,36 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } override fun upGroup() { - synchronized(this) { - bookGroups.remove(AppConst.bookGroupAll) - bookGroups.remove(AppConst.bookGroupLocal) - bookGroups.remove(AppConst.bookGroupAudio) - if (AppConfig.bookGroupAudioShow) { - bookGroups.add(0, AppConst.bookGroupAudio) - } - if (AppConfig.bookGroupLocalShow) { - bookGroups.add(0, AppConst.bookGroupLocal) + launch { + var noGroupSize = 0 + withContext(IO) { + if (AppConfig.bookGroupNoneShow) { + noGroupSize = App.db.bookDao().noGroupSize + } } - if (AppConfig.bookGroupAllShow) { - bookGroups.add(0, AppConst.bookGroupAll) + synchronized(this@BookshelfFragment) { + bookGroups.remove(AppConst.bookGroupAll) + bookGroups.remove(AppConst.bookGroupLocal) + 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) { + bookGroups.add(0, AppConst.bookGroupAudio) + } + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(0, AppConst.bookGroupLocal) + } + if (AppConfig.bookGroupAllShow) { + bookGroups.add(0, AppConst.bookGroupAll) + } + view_pager_bookshelf.adapter?.notifyDataSetChanged() } - view_pager_bookshelf.adapter?.notifyDataSetChanged() } } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt index d82480ca2..ab8984752 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment +import io.legado.app.constant.AppConst import io.legado.app.constant.BookType import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey @@ -98,9 +99,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), private fun upRecyclerData() { bookshelfLiveData?.removeObservers(this) bookshelfLiveData = when (groupId) { - -1 -> App.db.bookDao().observeAll() - -2 -> App.db.bookDao().observeLocal() - -3 -> App.db.bookDao().observeAudio() + AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll() + AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal() + AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio() + AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } bookshelfLiveData?.observe(this, Observer { list -> diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt index b960041d8..5bb42f081 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt @@ -12,7 +12,7 @@ import io.legado.app.base.BaseFragment import io.legado.app.data.entities.RssSource import io.legado.app.lib.theme.ATH 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.source.manage.RssSourceActivity import io.legado.app.utils.getViewModelOfActivity @@ -60,6 +60,6 @@ class RssFragment : BaseFragment(R.layout.fragment_rss), } override fun openRss(rssSource: RssSource) { - startActivity(Pair("url", rssSource.sourceUrl)) + startActivity(Pair("url", rssSource.sourceUrl)) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt similarity index 51% rename from app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt rename to app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt index 31ac9cdda..f7a1b6bb6 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt @@ -1,83 +1,67 @@ package io.legado.app.ui.rss.article import android.os.Bundle -import android.view.Menu -import android.view.MenuItem +import android.view.View import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.App 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.lib.theme.ATH 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.VerticalDivider 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_refresh_recycler.* -import org.jetbrains.anko.startActivity -import org.jetbrains.anko.startActivityForResult -class RssArticlesActivity : VMBaseActivity(R.layout.activity_rss_artivles), - RssArticlesViewModel.CallBack, +class RssArticlesFragment : VMBaseFragment(R.layout.fragment_rss_articles), 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 get() = getViewModel(RssArticlesViewModel::class.java) - - override lateinit var adapter: RssArticlesAdapter - private val editSource = 12319 + lateinit var adapter: RssArticlesAdapter private lateinit var loadMoreView: LoadMoreView private var rssArticlesData: LiveData>? = null - override fun onActivityCreated(savedInstanceState: Bundle?) { - viewModel.callBack = this - viewModel.titleLiveData.observe(this, Observer { - title_bar.title = it - }) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + viewModel.init(arguments) initView() - viewModel.initData(intent) { - initData() - refresh_recycler_view.startLoading() - } - } - - 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(editSource, Pair("data", it)) - } - R.id.menu_clear -> { - viewModel.url?.let { - refresh_progress_bar.isAutoLoading = true - viewModel.clearArticles() - } - } - } - return super.onCompatOptionsItemSelected(item) + refresh_recycler_view.startLoading() + initView() + initData() } private fun initView() { ATH.applyEdgeEffectColor(recycler_view) - recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(VerticalDivider(this)) - adapter = RssArticlesAdapter(this, this) + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + adapter = RssArticlesAdapter(requireContext(), this) recycler_view.adapter = adapter - loadMoreView = LoadMoreView(this) + loadMoreView = LoadMoreView(requireContext()) adapter.addFooterView(loadMoreView) refresh_recycler_view.onRefreshStart = { - viewModel.url?.let { - viewModel.loadContent() + activityViewModel.rssSource?.let { + viewModel.loadContent(it) } } recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { @@ -91,10 +75,10 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi } private fun initData() { - viewModel.url?.let { + activityViewModel.url?.let { rssArticlesData?.removeObservers(this) - rssArticlesData = App.db.rssArticleDao().liveByOrigin(it) - rssArticlesData?.observe(this, Observer { list -> + rssArticlesData = App.db.rssArticleDao().liveByOriginSort(it, viewModel.sortName) + rssArticlesData?.observe(viewLifecycleOwner, Observer { list -> adapter.setItems(list) }) } @@ -104,21 +88,25 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi if (viewModel.isLoading) return if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) { loadMoreView.rotate_loading.show() - viewModel.loadMore() + activityViewModel.rssSource?.let { + viewModel.loadMore(it) + } } } - override fun loadFinally(hasMore: Boolean) { - refresh_recycler_view.stopLoading() - if (hasMore) { - loadMoreView.startLoad() - } else { - loadMoreView.noMore() - } + override fun observeLiveBus() { + viewModel.loadFinally.observe(viewLifecycleOwner, Observer { + refresh_recycler_view.stopLoading() + if (it) { + loadMoreView.startLoad() + } else { + loadMoreView.noMore() + } + }) } override fun readRss(rssArticle: RssArticle) { - viewModel.read(rssArticle) + activityViewModel.read(rssArticle) startActivity( Pair("title", rssArticle.title), Pair("origin", rssArticle.origin), diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt index 61a2b14be..3bcfc00ab 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt @@ -1,124 +1,85 @@ package io.legado.app.ui.rss.article import android.app.Application -import android.content.Intent +import android.os.Bundle 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 import io.legado.app.model.Rss -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext - class RssArticlesViewModel(application: Application) : BaseViewModel(application) { - var callBack: CallBack? = null - var url: String? = null - var rssSource: RssSource? = null - val titleLiveData = MutableLiveData() + val loadFinally = MutableLiveData() var isLoading = true var order = System.currentTimeMillis() private var nextPageUrl: String? = null + private val articles = arrayListOf() + var sortName: String = "" + var sortUrl: String = "" - 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 init(bundle: Bundle?) { + bundle?.let { + sortName = it.getString("sortName") ?: "" + sortUrl = it.getString("sortUrl") ?: "" } } - fun loadContent() { + + fun loadContent(rssSource: RssSource) { isLoading = true - rssSource?.let { rssSource -> - Rss.getArticles(rssSource, null) - .onSuccess(IO) { - nextPageUrl = it.nextPageUrl - it.articles.let { list -> - list.forEach { rssArticle -> - rssArticle.order = order-- - } - App.db.rssArticleDao().insert(*list.toTypedArray()) - if (!rssSource.ruleNextPage.isNullOrEmpty()) { - App.db.rssArticleDao().clearOld(url!!, order) - withContext(Main) { - callBack?.loadFinally(true) - } - } else { - withContext(Main) { - callBack?.loadFinally(false) - } + Rss.getArticles(sortName, sortUrl, rssSource, null) + .onSuccess(Dispatchers.IO) { + nextPageUrl = it.nextPageUrl + it.articles.let { list -> + list.forEach { rssArticle -> + rssArticle.order = order-- + } + App.db.rssArticleDao().insert(*list.toTypedArray()) + if (!rssSource.ruleNextPage.isNullOrEmpty()) { + App.db.rssArticleDao().clearOld(rssSource.sourceUrl, sortName, order) + loadFinally.postValue(true) + } else { + withContext(Dispatchers.Main) { + loadFinally.postValue(false) } - isLoading = false - } - }.onError { - toast(it.localizedMessage) + isLoading = false } - } + }.onError { + toast(it.localizedMessage) + } } - fun loadMore() { + fun loadMore(rssSource: RssSource) { isLoading = true - val source = rssSource val pageUrl = nextPageUrl - if (source != null && !pageUrl.isNullOrEmpty()) { - Rss.getArticles(source, pageUrl) - .onSuccess(IO) { + if (!pageUrl.isNullOrEmpty()) { + Rss.getArticles(sortName, pageUrl, rssSource, pageUrl) + .onSuccess(Dispatchers.IO) { nextPageUrl = it.nextPageUrl it.articles.let { list -> if (list.isEmpty()) { - callBack?.loadFinally(false) + loadFinally.postValue(true) return@let } - callBack?.adapter?.getItems()?.let { adapterItems -> - if (adapterItems.contains(list.first())) { - callBack?.loadFinally(false) - } else { - list.forEach { rssArticle -> - rssArticle.order = order-- - } - App.db.rssArticleDao().insert(*list.toTypedArray()) + if (articles.contains(list.first())) { + loadFinally.postValue(false) + } else { + list.forEach { rssArticle -> + rssArticle.order = order-- } + App.db.rssArticleDao().insert(*list.toTypedArray()) } } isLoading = false } } 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) - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt new file mode 100644 index 000000000..2d45bcd70 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt @@ -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(R.layout.activity_rss_artivles) { + + override val viewModel: RssSortViewModel + get() = getViewModel(RssSortViewModel::class.java) + private val editSource = 12319 + private val fragments = linkedMapOf() + 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(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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt new file mode 100644 index 000000000..4b1149f2e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt @@ -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() + 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 { + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 73c55961b..e11293cc4 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -24,7 +24,7 @@ import org.jetbrains.anko.toast import org.jsoup.Jsoup -class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), +class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read, false), FileChooserDialog.CallBack, ReadRssViewModel.CallBack { @@ -252,6 +252,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r web_view.settings.javaScriptEnabled = true web_view.evaluateJavascript("document.documentElement.outerHTML") { val html = StringEscapeUtils.unescapeJson(it) + .replace("^\"|\"$".toRegex(), "") Jsoup.parse(html).text() viewModel.readAloud(Jsoup.parse(html).textArray()) } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index 944135b45..88059b955 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -135,6 +135,7 @@ class RssSourceEditActivity : add(EditEntity("sourceUrl", rssSource?.sourceUrl, R.string.source_url)) add(EditEntity("sourceIcon", rssSource?.sourceIcon, R.string.source_icon)) 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("ruleNextPage", rssSource?.ruleNextPage, R.string.r_next)) add(EditEntity("ruleTitle", rssSource?.ruleTitle, R.string.r_title)) @@ -159,6 +160,7 @@ class RssSourceEditActivity : "sourceUrl" -> source.sourceUrl = it.value ?: "" "sourceIcon" -> source.sourceIcon = it.value ?: "" "sourceGroup" -> source.sourceGroup = it.value + "sortUrl" -> source.sortUrl = it.value "ruleArticles" -> source.ruleArticles = it.value "ruleNextPage" -> source.ruleNextPage = it.value "ruleTitle" -> source.ruleTitle = it.value diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt index b4f20e20a..42b354709 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt @@ -204,7 +204,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } private fun importSourceUrl(url: String): Int { - HttpHelper.simpleGet(url)?.let { body -> + HttpHelper.simpleGet(url, "UTF-8")?.let { body -> val sources = mutableListOf() val items: List> = jsonPath.parse(body).read("$") for (item in items) { diff --git a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt index fdf77bc85..63455fc9a 100644 --- a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt +++ b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt @@ -2,7 +2,7 @@ package io.legado.app.ui.welcome import android.content.Intent 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.R 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)) //初始化简繁转换引擎 when (AppConfig.chineseConverterType) { - 1 -> ZhConverterUtil.toSimple("初始化") - 2 -> ZhConverterUtil.toTraditional("初始化") + 1 -> HanLP.convertToSimplifiedChinese("初始化") + 2 -> HanLP.convertToTraditionalChinese("初始化") else -> null } } diff --git a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt index f11db64e8..eabfad602 100644 --- a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt @@ -14,6 +14,7 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co private val batteryPaint = Paint() private val outFrame = Rect() private val polar = Rect() + var isBattery = false init { setPadding(4.dp, 0, 6.dp, 0) @@ -35,6 +36,7 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co override fun onDraw(canvas: Canvas) { super.onDraw(canvas) + if (!isBattery) return outFrame.set( 1.dp, layout.getLineTop(0) + 2.dp, diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt index 9c1c75d5e..dc6ba868b 100644 --- a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt @@ -23,7 +23,8 @@ class TextDialog : BaseDialogFragment() { fragmentManager: FragmentManager, content: String?, mode: Int = 0, - time: Long = 0 + time: Long = 0, + autoClose: Boolean = false ) { TextDialog().apply { val bundle = Bundle() @@ -32,6 +33,7 @@ class TextDialog : BaseDialogFragment() { bundle.putLong("time", time) arguments = bundle isCancelable = false + this.autoClose = autoClose }.show(fragmentManager, "textDialog") } @@ -39,6 +41,8 @@ class TextDialog : BaseDialogFragment() { private var time = 0L + private var autoClose: Boolean = false + override fun onStart() { super.onStart() val dm = DisplayMetrics() @@ -79,6 +83,7 @@ class TextDialog : BaseDialogFragment() { if (time <= 0) { view.post { dialog?.setCancelable(true) + if (autoClose) dialog?.cancel() } } } @@ -86,6 +91,7 @@ class TextDialog : BaseDialogFragment() { } else { view.post { dialog?.setCancelable(true) + if (autoClose) dialog?.cancel() } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt index 9b1436ba8..9cf6c2bfd 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt @@ -8,6 +8,7 @@ import androidx.core.view.isVisible import androidx.preference.PreferenceCategory import androidx.preference.PreferenceViewHolder import io.legado.app.R +import io.legado.app.help.AppConfig import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.backgroundColor @@ -36,13 +37,18 @@ class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCate view.isVisible = title != null && title.isNotEmpty() 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) { - da.setBackgroundColor(ColorUtils.darkenColor(context.backgroundColor)) + da.setBackgroundColor(dividerColor) da.isVisible = it.isDividerAllowedAbove } val db = it.findViewById(R.id.preference_divider_below) if (db is View) { - db.setBackgroundColor(ColorUtils.darkenColor(context.backgroundColor)) + db.setBackgroundColor(dividerColor) db.isVisible = it.isDividerAllowedBelow } } diff --git a/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt new file mode 100644 index 000000000..154cce984 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt @@ -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 { + /** + * 清除关系

+ * 注意:这里不仅仅会清除关系,还会清除对应控件的宽高为 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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt index 13b105618..3f736abc5 100644 --- a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt @@ -15,8 +15,7 @@ val GSON: Gson by lazy { .create() } -inline fun genericType() = object : TypeToken() {}.type - +inline fun genericType(): Type = object : TypeToken() {}.type @Throws(JsonSyntaxException::class) inline fun Gson.fromJsonObject(json: String?): T? {//可转成任意类型 diff --git a/app/src/main/res/layout-land/activity_book_info.xml b/app/src/main/res/layout-land/activity_book_info.xml index 1dc7d789c..518501721 100644 --- a/app/src/main/res/layout-land/activity_book_info.xml +++ b/app/src/main/res/layout-land/activity_book_info.xml @@ -361,7 +361,8 @@ android:text="@string/book_intro" android:textColor="@color/tv_text_secondary" android:textSize="14sp" - android:visibility="visible" /> + android:visibility="visible" + tools:ignore="RtlHardcoded,RtlSymmetry" /> diff --git a/app/src/main/res/layout/activity_book_info.xml b/app/src/main/res/layout/activity_book_info.xml index ee0d050bb..6d05af889 100644 --- a/app/src/main/res/layout/activity_book_info.xml +++ b/app/src/main/res/layout/activity_book_info.xml @@ -155,7 +155,7 @@ android:text="@string/author" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> @@ -189,7 +189,7 @@ android:text="@string/origin_format" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> @@ -267,7 +267,7 @@ android:text="@string/group_s" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + android:visibility="visible" + tools:ignore="RtlHardcoded,RtlSymmetry" /> @@ -349,7 +350,7 @@ + android:background="@color/background" /> - + android:layout_height="wrap_content" /> - - - + android:layout_height="wrap_content" + app:tabMode="scrollable" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index ea4131764..6eb28b4f8 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -2,6 +2,7 @@ + android:layout_height="wrap_content" + app:fitStatusBar="false" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rss_articles.xml b/app/src/main/res/layout/fragment_rss_articles.xml new file mode 100644 index 000000000..5ca0c9574 --- /dev/null +++ b/app/src/main/res/layout/fragment_rss_articles.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_book_page.xml b/app/src/main/res/layout/view_book_page.xml index 3f09664a3..ec591bc6f 100644 --- a/app/src/main/res/layout/view_book_page.xml +++ b/app/src/main/res/layout/view_book_page.xml @@ -1,38 +1,61 @@ - - + + + android:gravity="center_vertical"> + + - + 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" /> - - + - + android:gravity="center_vertical"> - + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/book_group_manage.xml b/app/src/main/res/menu/book_group_manage.xml index 94ad26c3d..8c65f9f8d 100644 --- a/app/src/main/res/menu/book_group_manage.xml +++ b/app/src/main/res/menu/book_group_manage.xml @@ -10,25 +10,36 @@ app:showAsAction="always" tools:ignore="AlwaysShowAction" /> - + - + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/arrays.xml b/app/src/main/res/values-zh-rHK/arrays.xml new file mode 100644 index 000000000..ebe6c25dd --- /dev/null +++ b/app/src/main/res/values-zh-rHK/arrays.xml @@ -0,0 +1,52 @@ + + + + + 度小美 + 度小宇 + 度逍遙 + 度丫丫 + 度小嬌 + 度米朵 + 度博文 + 度小童 + 度小萌 + 百度騷男 + 百度評書 + 百度主持 + + + + 跟隨系統 + 亮色主題 + 暗色主題 + + + + 自動 + 黑色 + 白色 + 跟隨背景 + + + + 默認 + 1分鐘 + 2分鐘 + 3分鐘 + 常亮 + + + + 關閉 + 繁體轉簡體 + 簡體轉繁體 + + + + 系統默認字體 + 系統襯線字體 + 系統等寬字體 + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..7f07c94e0 --- /dev/null +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,688 @@ + + + 閲讀 + 閲讀·搜尋 + 閲讀需要訪問存儲卡權限,請前往「設定」—「應用程式權限」—開啟所需要的權限 + + + Home + 還原 + 導入閲讀數據 + 創建子文件夾 + 創建 legado 文件夾作爲備份路徑 + 備份路徑 + 導入舊版數據 + 導入 Github 數據 + 淨化替換 + Send + + 提示 + 取消 + 確認 + 去設定 + 無法轉跳至設定介面 + + 點擊重試 + 正在加載 + 提醒 + 編輯 + 刪除 + 替換 + 替換淨化 + 配置替換淨化規則 + 暫無 + 啟用 + 替換淨化-搜尋 + 書架 + 收藏夾 + 收藏 + 已收藏 + 未收藏 + 訂閲 + 全部 + 最近閲讀 + 最後閲讀 + 更新日誌 + 書架還空着,快去添加吧! + 搜尋 + 下載 + 列表 + 網格三列 + 網格四列 + 網格五列 + 網格六列 + 書架佈局 + 視圖 + 書城 + 添加本地 + 書源 + 書源管理 + 新建/導入/編輯/管理書源 + 設定 + 主題設定 + 同主題/顏色相關的一些設定 + 其它設定 + 與功能相關的一些設定 + 關於 + 捐贈 + 退出 + 尚未保存,是否繼續編輯 + 閲讀樣式設定 + 版本 + 本地 + 搜尋 + 來源: %s + 最近: %s + 書名 + 最新: %s + 是否將《%s》放入書架? + 共 %s 個 Text 文件 + 載入中… + 重試 + Web 服務 + 啟用 Web 服務 + web 編輯書源 + http://%1$s:%2$d + 離線下載 + 離線下載 + 下載已選擇的章節到本地 + 換源 + + \u3000\u3000這是一款使用 Kotlin 全新開發的開源的閲讀應用程式,歡迎你的加入。關注公眾號[开源阅读软件]! + + + 閲讀3.0下載地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app + + Version %s + 自動刷新 + 打開程式時自動更新書輯 + 自動下載最新章節 + 更新書輯時自動下載最新章節 + 備份與還原 + WebDav 設定 + WebDav 設定/還原舊版本數據 + 備份 + 還原 + 備份請給予存儲權限 + 還原請給予存儲權限 + 確認 + 取消 + 確認備份嗎? + 新備份會覆蓋原有備份。\n備份路徑YueDu + 確認還原嗎? + 還原成功會覆蓋原有書架。 + 備份成功 + 備份失敗 + 正在還原 + 還原成功 + 還原失敗 + 屏幕方向 + 跟隨傳感器 + 橫向 + 豎向 + 跟隨系統 + 免責聲明 + 共%d章 + 介面 + 亮度 + 目錄 + 下一章 + 上一章 + 隱藏狀態欄 + 閲讀介面隱藏狀態欄 + 朗讀 + 正在朗讀 + 點擊打開閲讀介面 + 播放 + 正在播放 + 點擊打開播放介面 + 播放暫停 + 返回 + 刷新 + 開始 + 停止 + 暫停 + 繼續 + 定時 + 朗讀暫停 + 正在朗讀(剩餘 %d 分鐘) + 閲讀介面隱藏導航欄 + 隱藏導航欄 + 導航欄顏色 + GitHub + 評分 + 發送電子郵件 + 無法打開 + 分享失敗 + 無章節 + 添加網址 + 添加書輯網址 + 背景 + 作者 + 作者: %s + 朗讀停止 + 清除緩存 + 成功清除緩存 + 保存 + 編輯源 + 編輯書源 + 禁用書源 + 新建書源 + 新建訂閲源 + 添加書輯 + 掃描 + 拷貝源 + 粘帖源 + 源規則説明 + 檢查更新 + 掃描 QR Code + 掃描本地圖片 + 規則説明 + 分享 + 應用程式分享 + 跟隨系統 + 添加 + 導入書源 + 本地導入 + 網絡導入 + 替換淨化 + 替換規則編輯 + 替換規則 + 替換為 + 封面 + + 音量鍵翻頁 + 點擊翻頁 + 點擊總是翻下一頁 + 翻頁動畫 + 屏幕超時 + 返回 + 菜單 + 調節 + 滾動條 + 清除緩存會刪除所有已保存的章節,確認是否清除? + 書源共享 + 規則替換名稱 + 替換規則為空或不滿足正則表達式要求 + 選擇操作 + 全選 + 全選 (%1$d/%2$d) + 取消 (%1$d/%2$d) + 深色模式 + 啟動頁 + 開始下載 + 取消下載 + 暫無任務 + 已下載 %1$d/%2$d + 導入選擇書輯 + 更新/搜尋線程數,太多會卡頓 + 切換圖標 + 刪除書輯 + 開始閲讀 + 數據載入中… + 載入失敗,點擊重試 + 內容簡介 + 簡介: %s + 打開外部書籍 + 來源: %s + 本地導入 + 導入在線規則 + 檢查更新間隔 + 按閲讀時間 + 按更新時間 + 按書名 + 手動排序 + 閲讀方式 + 排版 + 刪除所選 + 是否確認刪除? + 默認字體 + 發現 + 發現管理 + 沒有內容,去書源裏自定義吧! + 刪除所有 + 搜索歷史 + 清除 + 正文顯示標題 + 書源同步 + 無最新章節信息 + 顯示時間和電量 + 顯示分隔線 + 深色狀態欄圖標 + 內容 + 拷貝內容 + 一鍵緩存 + 這是一段測試文字\n\u3000\u3000只是讓你看看效果的 + 文字顏色和背景(長按自定義) + 沉浸式狀態欄 + 還剩 %d 章未下載 + 沒有選擇 + 長按輸入顏色值 + 加載中… + 追更區 + 養肥區 + 書籤 + 添加書籤 + 刪除 + 加載超時 + 關注: %s + 已拷貝 + 整理書架 + 這將會刪除所有書籍,請謹慎操作。 + 搜索書源 + 搜索訂閲源 + 搜索(共 %d 個書源) + 目錄(%d) + 加粗 + 字體 + 文字 + 軟件主頁 + + + + + 邊距 + 上邊距 + 下邊距 + 左邊距 + 右邊距 + 校驗書源 + 校驗所選 + 進度 %1$d/%2$d + 請安裝並選擇中文 TTS! + TTS 初始化失敗! + 簡繁轉換 + 關閉 + 簡轉繁 + 繁轉簡 + 翻頁模式 + %1$d 項 + 存儲卡: + 加入書架 + 加入書架 (%1$d) + 成功添加 %1$d 本書 + 請將字體文件放到 SD 根目錄 Fonts 文件夾下重新選擇 + 默認字體 + 選擇字體 + 字號 + 行距 + 段距 + 置頂 + 自動展開發現 + 默認展開第一組發現 + 當前線程數 %s + 朗讀語速 + 自動翻頁 + 停止自動翻頁 + 自動翻頁間隔 + 書籍信息 + 書籍信息編輯 + 默認打開書架 + 自動跳轉最近閲讀 + 替換範圍,選填書名或者源名 + 分組 + 內容緩存路徑 + 清理緩存 + 系統文件選擇器 + 新版本 + 下載更新 + 朗讀時音量鍵翻頁 + Tip 邊距跟隨邊距調整 + 允許更新 + 禁止更新 + 反選 + 搜索書名、作者 + 書名、作者、URL + 常見問題 + 顯示所有發現 + 關閉則只顯示勾選源的發現 + 更新目錄 + Txt目錄正則 + 設置編碼 + 倒序-順序 + 排序 + 智能排序 + 手動排序 + 拼音排序 + 滾動到頂部 + 滾動到底部 + 已讀: %s + 追更 + 養肥 + 完結 + 所有書籍 + 追更書籍 + 養肥書籍 + 完結書籍 + 本地書籍 + 狀態欄顏色透明 + 導航欄變色 + 導航欄根據夜間模式變化 + 放入書架 + 繼續閲讀 + 封面地址 + 覆蓋 + 滑動 + 仿真 + 滾動 + 無動畫 + 此書源使用了高級功能,請到捐贈裏點擊支付寶紅包搜索碼領取紅包開啟。 + 後台更新換源最新章節 + 開啟則會在軟件打開 1 分鐘後開始更新 + 書架 ToolBar 自動隱藏 + 滾動書架時 ToolBar 自動隱藏與顯示 + 登錄 + 登錄 %s + 成功 + 當前源沒有配置登陸地址 + + + 源名稱 (sourceName) + 源URL (sourceUrl) + 源分組 (sourceGroup) + 分類 Url + 登錄 URL(loginUrl) + 搜索地址 (url) + 發現地址規則 (url) + 書籍列表規則 (bookList) + 書名規則 (name) + 詳情頁 url 規則 (bookUrl) + 作者規則 (author) + 分類規則 (kind) + 簡介規則 (intro) + 封面規則 (coverUrl) + 最新章節規則 (lastChapter) + 字數規則 (wordCount) + 書籍 URL 正則 (bookUrlPattern) + 預處理規則 (bookInfoInit) + 目錄 URL 規則 (tocUrl) + 目錄下一頁規則 (nextTocUrl) + 目錄列表規則 (chapterList) + 章節名稱規則 (ChapterName) + 章節 URL 規則 (chapterUrl) + VIP 標識 (isVip) + 章節信息 (ChapterInfo) + 正文規則 (content) + 正文下一頁 URL 規則 (nextContentUrl) + webJs + 資源正則 (sourceRegex) + + 圖標 (sourceIcon) + 列表規則 (ruleArticles) + 列表下一頁規則 (ruleArticles) + 標題規則 (ruleTitle) + guid 規則 (ruleGuid) + 時間規則 (rulePubDate) + 類別規則 (ruleCategories) + 描述規則 (ruleDescription) + 圖片 url 規則 (ruleImage) + 內容規則 (ruleContent) + 鏈接規則 (ruleLink) + + + + 沒有書源 + 書籍信息獲取失敗 + 內容獲取失敗 + 目錄獲取失敗 + 訪問網站失敗: %s + 文件讀取失敗 + 加載目錄失敗 + 獲取數據失敗! + 加載失敗\n%s + 沒有網絡 + 網絡連接超時 + 數據解析失敗 + + + 請求頭 (header) + 調試源 + 二維碼導入 + 掃描二維碼 + 選中時點擊可彈出菜單 + 主題 + 主題模式 + 選擇主題模式 + 默認主題 + 恢復主題為默認配色 + 加入QQ羣 + 獲取背景圖片需存儲權限 + 輸入書源網址 + 刪除文件 + 刪除文件成功 + 確定刪除文件嗎? + 手機目錄 + 智能導入 + 發現 + 切換顯示樣式 + 導入本地書籍需存儲權限 + 夜間模式 + E-Ink 模式 + 電子墨水屏模式 + 本軟件需要存儲權限來存儲備份書籍信息 + 再按一次退出程式 + 導入本地書籍需存儲權限 + 網絡連接不可用 + + + 確認 + 是否確認刪除? + 是否刪除全部書籍? + 是否同時刪除已下載的書籍目錄? + 掃描二維碼需相機權限 + 朗讀正在運行,不能自動翻頁 + 輸入編碼 + TXT 目錄規則 + 打開外部書籍需獲取存儲權限 + 未獲取到書名 + 輸入替換規則網址 + 搜索列表獲取成功%d + 書源名稱和 URL 不能為空 + 圖庫 + 領支付寶紅包 + 沒有獲取到更新地址 + 正在打開首頁,成功自動返回主界面 + 登錄成功後請點擊右上角圖標進行首頁訪問測試 + + + 使用正則表達式 + 縮進 + 無縮進 + 一字符縮進 + 二字符縮進 + 三字符縮進 + 四字符縮進 + 選擇文件夾 + 選擇文件 + 沒有發現,可以在書源裏添加。 + 恢復默認 + 自定義緩存路徑需要存儲權限 + 黑色 + 文章內容為空 + 正在換源請等待… + 目錄列表為空 + 字距 + + 基本 + 搜索 + 發現 + 詳情 + 目錄 + 正文 + + E-Ink 模式 + 去除動畫,優化電紙書使用體驗 + Web 服務 + web 端口 + 當前端口 %s + 二維碼分享 + 字符串分享 + wifi 分享 + 請給於存儲權限 + 減速 + 加速 + 上一個 + 下一個 + 音樂 + 音頻 + 啟用 + 啟用 JS + 加載 BaseUrl + 全部書源 + 輸入不能為空 + 清空發現緩存 + 編輯發現 + 切換軟件顯示在桌面的圖標 + 幫助 + 我的 + 閲讀 + %d%% + %d 分鐘 + 自動亮度 %s + 按頁朗讀 + 在線朗讀 + 背景圖片 + 背景顏色 + 文字顏色 + 選擇圖片 + 分組管理 + 分組選擇 + 編輯分組 + 移入分組 + 添加分組 + 新建替換 + 分組 + 分組: %s + 目錄: %s + 啟用發現 + 禁用發現 + 啟用所選 + 禁用所選 + 導出所選 + 導出 + 加載目錄 + TTS + WebDav 密碼 + 輸入你的 WebDav 授權密碼 + 輸入你的服務器地址 + WebDav 服務器地址 + WebDav 賬號 + 輸入你的 WebDav 賬號 + 訂閲源 + 編輯訂閲源 + 篩選 + 篩選發現 + 當前位置: + 精準搜索 + 正在啟動服務 + + 文件選擇 + 文件夾選擇 + 我是有底線的 + Uri 轉 Path 失敗 + 刷新封面 + 封面換源 + 選擇本地圖片 + 類型: + 文本 + 音頻 + 後台 + 正在導入 + 正在導出 + 自定義翻頁按鍵 + 上一頁按鍵 + 下一頁按鍵 + 先將書籍加入書架 + 未分組 + 上一句 + 下一句 + 其它目錄 + 文字太多,生成二維碼失敗 + 分享RSS源 + 分享書源 + 自動切換夜間模式 + 夜間模式跟隨系統 + 上級 + 在線朗讀音色 + (%1$d/%2$d) + 顯示訂閲 + 服務已停止 + 正在啟動服務\n具體信息查看通知欄 + 默認路徑 + 系統文件夾選擇器 + 自帶選擇器\n(Android10 以上因權限限制可能無法使用) + Android10 以上因權限限制可能無法讀寫文件 + 長按文字在操作菜單中顯示閲讀·搜索 + 文字操作顯示搜索 + 記錄日誌 + 中文簡繁體轉換 + 圖標為矢量圖標,Android8.0 以前不支持 + 朗讀設置 + 主界面 + 長按選擇文本 + 頁眉 + 正文 + 頁腳 + 文本選擇結束位置 + 文本選擇開始位置 + 共用佈局 + 瀏覽器 + 導入默認規則 + 名稱 + 正則 + 更多菜單 + + + 系統內置字體樣式 + 刪除源文件 + 預設一 + 預設二 + 預設三 + 標題 + 靠左 + 居中 + 隱藏 + 加入分組 + 保存圖片 + 沒有默認路徑 + 設置分組 + 查看目錄 + 導航欄陰影 + 當前陰影大小 (elevation): %s + 默認 + 主菜單 + 點擊授予權限 + 閲讀需要訪問存儲卡權限,請點擊下方的"授予權限"按鈕,或前往「設定」—「應用程式權限」—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的「選擇文件夾」,使用系統文件夾選擇器。 + 全文朗讀中不能朗讀選中文字 + 擴展到劉海 + 更新目錄中 + 全程響應耳機按鍵 + 即使退出軟件也響應耳機按鍵 + 開發人員 + 聯繫我們 + 開源許可 + 關注公衆號 + WeChat + 你的支持是我更新的動力 + 公众号[开源阅读软件] + 正在自動換源 + 點擊加入 + + 信息 + + + 主色調 + 強調色 + 背景色 + 導航欄顏色 + 白天 + 白天,主色調 + 白天,強調色 + 白天,背景色 + 白天,導航欄顏色 + 夜間 + 夜間,主色調 + 夜間,強調色 + 夜間,背景色 + 夜間,導航欄顏色 + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 39c85cae3..b1046096f 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -96,4 +96,14 @@ 系统等宽字体 + + + 标题 + 时间 + 电量 + 页数 + 进度 + 页数及进度 + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4afe3398e..edf2698e6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,10 @@ Home 恢复 导入阅读数据 + 创建子文件夹 + 创建legado文件夹作为备份文件夹 + 备份路径 + 导入旧版数据 导入Github数据 净化替换 Send @@ -376,6 +380,7 @@ 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 分类Url 登录URL(loginUrl) 搜索地址(url) 发现地址规则(url) @@ -557,8 +562,11 @@ 导出 加载目录 TTS + WebDav 密码 输入你的WebDav授权密码 输入你的服务器地址 + WebDav 服务器地址 + WebDav 账号 输入你的WebDav账号 订阅源 编辑订阅源 @@ -604,7 +612,7 @@ 系统文件夹选择器 自带选择器\n(Android10以上因权限限制可能无法使用) Android10以上因权限限制可能无法读写文件 - 长按文字在操作菜单中显示欣悦·搜索 + 长按文字在操作菜单中显示阅读·搜索 文字操作显示搜索 记录日志 中文简繁体转换 @@ -647,4 +655,39 @@ 阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。 全文朗读中不能朗读选中文字 扩展到刘海 + 更新目录中 + 全程响应耳机按键 + 即使退出软件也响应耳机按键 + 开发人员 + 联系我们 + 开源许可 + 其它 + 开源阅读软件 + 关注公众号 + 微信 + 您的支持是我更新的动力 + 公众号[开源阅读软件] + 正在自动换源 + 点击加入 + + 信息 + 隐藏页眉 + 隐藏页脚 + + + 主色调 + 强调色 + 背景色 + 底部操作栏颜色 + 白天 + 白天,主色调 + 白天,强调色 + 白天,背景色 + 白天,底栏色 + 夜间 + 夜间,主色调 + 夜间,强调色 + 夜间,背景色 + 夜间,底栏色 + diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 2970ab12b..3c607cd9d 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -4,8 +4,8 @@ + android:key="gzGzh" + android:title="@string/follow_official_account" + android:summary="@string/official_account" + app:iconSpaceReserved="false" /> + android:summary="@string/click_to_apply" + app:iconSpaceReserved="false" /> + android:key="license" + android:title="@string/license" + app:iconSpaceReserved="false" /> + android:key="webDavCreateDir" + android:defaultValue="true" + android:title="@string/mkdirs" + android:summary="@string/mkdirs_description" + app:allowDividerAbove="false" + app:allowDividerBelow="false" + app:iconSpaceReserved="false" /> @@ -44,24 +44,24 @@ app:iconSpaceReserved="false"> + android:key="backupUri" + android:title="@string/backup_path" + app:iconSpaceReserved="false" /> + android:key="web_dav_backup" + android:title="@string/backup" + app:iconSpaceReserved="false" /> + android:key="web_dav_restore" + android:title="@string/restore" + app:iconSpaceReserved="false" /> + android:key="import_old" + android:title="@string/menu_import_old_version" + app:iconSpaceReserved="false" /> diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index d3422ec95..66f5bfd9f 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -45,6 +45,13 @@ android:title="@string/threads_num_title" app:iconSpaceReserved="false" /> + + + android:defaultValue="@color/md_light_blue_600" + android:key="colorPrimary" + android:summary="@string/day_color_primary" + android:title="@string/primary" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_pink_800" + android:key="colorAccent" + android:summary="@string/day_color_accent" + android:title="@string/accent" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_grey_100" + android:key="colorBackground" + android:summary="@string/day_background_color" + android:title="@string/background_color" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_grey_200" + android:key="colorBottomBackground" + android:summary="@string/day_navbar_color" + android:title="@string/navbar_color" + app:cpv_dialogType="preset" + app:allowDividerAbove="false" + app:allowDividerBelow="false" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_grey_900" + android:key="colorPrimaryNight" + android:summary="@string/night_primary" + android:title="@string/primary" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_deep_orange_800" + android:key="colorAccentNight" + android:summary="@string/night_accent" + android:title="@string/accent" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_black_1000" + android:key="colorBackgroundNight" + android:summary="@string/night_background_color" + android:title="@string/background_color" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> + android:defaultValue="@color/md_grey_800" + android:key="colorBottomBackgroundNight" + android:summary="@string/night_navbar_color" + android:title="@string/navbar_color" + app:cpv_dialogType="preset" + app:iconSpaceReserved="false" /> diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml index 2526b1ba2..7c63c98fe 100644 --- a/app/src/main/res/xml/pref_main.xml +++ b/app/src/main/res/xml/pref_main.xml @@ -86,15 +86,15 @@ diff --git a/build.gradle b/build.gradle index 12242697f..21c80e3a6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.71' + ext.kotlin_version = '1.3.72' repositories { google() - jcenter() + //jcenter() + maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } maven { url 'https://maven.fabric.io/public' } maven { url 'https://plugins.gradle.org/m2/' } } 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 'de.timfreiheit.resourceplaceholders:placeholders:0.3' classpath 'com.google.gms:google-services:4.3.3' @@ -20,7 +21,8 @@ buildscript { allprojects { repositories { google() - jcenter() + //jcenter() + maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } maven { url "https://jitpack.io" } maven { url 'https://maven.google.com/' } maven { url 'https://github.com/psiegman/mvn-repo/raw/master/releases' }