diff --git a/.gitignore b/.gitignore index 2543443bb..700c700fe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ /release /tmp node_modules/ +/app/app +/app/google +/app/gradle.properties package-lock.json diff --git a/app/build.gradle b/app/build.gradle index 844f12227..d54e75be2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec android { compileSdkVersion 29 - flavorDimensions ("version") + flavorDimensions("version") signingConfigs { if (project.hasProperty("RELEASE_STORE_FILE")) { myConfig { @@ -56,7 +56,7 @@ android { signingConfig signingConfigs.myConfig } applicationIdSuffix '.release' - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { @@ -66,7 +66,7 @@ android { applicationIdSuffix '.debug' versionNameSuffix 'debug' minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } android.applicationVariants.all { variant -> variant.outputs.all { @@ -87,7 +87,7 @@ android { } compileOptions { // Flag to enable support for the new language APIs - //coreLibraryDesugaringEnabled true + coreLibraryDesugaringEnabled true // Sets Java compatibility to Java 8 sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -109,6 +109,7 @@ kapt { } dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test:runner:1.2.0' @@ -117,16 +118,16 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" //fireBase - implementation 'com.google.firebase:firebase-core:17.3.0' + implementation 'com.google.firebase:firebase-core:17.4.3' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' //androidX - implementation 'androidx.core:core-ktx:1.2.0' + implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.media:media: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.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android:flexbox:1.1.0' @@ -154,7 +155,7 @@ dependencies { implementation 'com.jeremyliao:live-event-bus-x:1.5.7' //协程 - def coroutines_version = '1.3.3' + def coroutines_version = '1.3.7' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" @@ -193,6 +194,8 @@ dependencies { //转换繁体 implementation 'com.hankcs:hanlp:portable-1.7.7' + //E-Ink 有些手机会出现重影 + //implementation 'fadeapp.widgets:scrollless-recyclerView:1.0.2' } apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1e8ab731b..b22ef10c7 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -58,14 +58,13 @@ # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆 # 因为这些子类都有可能被外部调用 -keep public class * extends android.app.Activity --keep public class * extends android.app.Appliction +-keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View --keep public class com.android.vending.licensing.ILicensingService # 保留androidx下的所有类及其内部类 @@ -128,16 +127,11 @@ } # webView处理,项目中没有使用到webView忽略即可 --keepclassmembers class fqcn.of.javascript.interface.for.webview { - public *; -} --keepclassmembers class * extends android.webkit.webViewClient { +-keepclassmembers class * extends android.webkit.WebViewClient { + public void *(android.webkit.WebView, java.lang.String); public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } --keepclassmembers class * extends android.webkit.webViewClient { - public void *(android.webkit.webView, jav.lang.String); -} # 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用 # 记得proguard-android.txt中一定不要加-dontoptimize才起作用 @@ -170,20 +164,20 @@ -dontwarn org.conscrypt.** -dontwarn com.jeremyliao.liveeventbus.** +-keep class com.google.gson.** { *; } +-keep class com.ke.gson.** { *; } -keep class com.jeremyliao.liveeventbus.** { *; } -keep class okhttp3.**{*;} -keep class okio.**{*;} -keep class com.hwangjr.rxbus.**{*;} -keep class org.conscrypt.**{*;} --keep class com.kunfei.bookshelf.widget.**{*;} --keep class com.kunfei.bookshelf.bean.**{*;} -keep class android.support.**{*;} -keep class me.grantland.widget.**{*;} -keep class de.hdodenhof.circleimageview.**{*;} -keep class tyrant.explosionfield.**{*;} -keep class tyrantgit.explosionfield.**{*;} -keep class freemarker.**{*;} --keep class com.gyf.barlibrary.* {*;} +-keep class com.gyf.barlibrary.** {*;} ##JSOUP -keep class org.jsoup.**{*;} -keep class **.xpath.**{*;} @@ -197,7 +191,7 @@ -dontwarn com.jayway.** -keep class com.fasterxml.**{*;} --keep class javax.swing..**{*;} +-keep class javax.swing.**{*;} -dontwarn javax.swing.** -keep class java.awt.**{*;} -dontwarn java.awt.** @@ -215,10 +209,10 @@ -dontwarn nl.siegmann.epublib.** -dontwarn org.xmlpull.** -keep class nl.siegmann.epublib.**{*;} --keep class javax.xml.**{*;} --keep class org.xmlpull.**{*;} +-keep class javax.xml**{*;} +-keep class org.xmlpull**{*;} --keep class org.simpleframework.xml.**{*;} +-keep class org.simpleframework.xml**{*;} -dontwarn org.simpleframework.xml.** -keepclassmembers class * { diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index e4464c88d..f465472f0 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,4 +1,4 @@ - 阅读·D - 阅读·D·搜索 + 阅读·D + 阅读·D·搜索 \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b33892324..0bcfbc414 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ xmlns:tools="http://schemas.android.com/tools" package="io.legado.app"> + @@ -237,6 +240,10 @@ android:name="io.legado.app.ui.book.chapterlist.ChapterListActivity" android:screenOrientation="behind" android:launchMode="singleTop" /> + + - - - - - - - 阅读3.0书架 - - - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
- - - - \ No newline at end of file diff --git a/app/src/main/assets/web/bookshelf.html b/app/src/main/assets/web/bookshelf.html index 87ee3bf9b..86639426f 100644 --- a/app/src/main/assets/web/bookshelf.html +++ b/app/src/main/assets/web/bookshelf.html @@ -1,46 +1,39 @@ - - + + - - - - - yd-web-tool - - - - - - - - - - - - - - - - - - - - - - + + 阅读3.0书架 + + - - -
- - + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/app/src/main/assets/web/index.html b/app/src/main/assets/web/index.html index a0a00ef1c..4339538d9 100644 --- a/app/src/main/assets/web/index.html +++ b/app/src/main/assets/web/index.html @@ -359,8 +359,8 @@
(?i) 前缀表示忽略大小写 代码在线运行工具 - 阅读书架(经典) - 阅读书架(新潮) + 阅读书架(经典) + 阅读书架(新潮) diff --git a/app/src/main/assets/web/index.js b/app/src/main/assets/web/index.js index fb2298bbd..7b0ec52a3 100644 --- a/app/src/main/assets/web/index.js +++ b/app/src/main/assets/web/index.js @@ -36,23 +36,28 @@ const RuleJSON = (() => { // 搜索规则 $$('.rules .ruleSearch').forEach(item => searchJson[item.title] = ''); - ruleJson.ruleSearch = JSON.stringify(searchJson); + //ruleJson.ruleSearch = JSON.stringify(searchJson); + ruleJson.ruleSearch = searchJson; // 发现规则 $$('.rules .ruleExplore').forEach(item => exploreJson[item.title] = ''); - ruleJson.ruleExplore = JSON.stringify(exploreJson); + //ruleJson.ruleExplore = JSON.stringify(exploreJson); + ruleJson.ruleExplore = exploreJson; // 详情页规则 $$('.rules .ruleBookInfo').forEach(item => bookInfoJson[item.title] = ''); - ruleJson.ruleBookInfo = JSON.stringify(bookInfoJson); + //ruleJson.ruleBookInfo = JSON.stringify(bookInfoJson); + ruleJson.ruleBookInfo = bookInfoJson; // 目录规则 $$('.rules .ruleToc').forEach(item => tocJson[item.title] = ''); - ruleJson.ruleToc = JSON.stringify(tocJson); + //ruleJson.ruleToc = JSON.stringify(tocJson); + ruleJson.ruleToc = tocJson; // 正文规则 $$('.rules .ruleContent').forEach(item => contentJson[item.title] = ''); - ruleJson.ruleContent = JSON.stringify(contentJson); + //ruleJson.ruleContent = JSON.stringify(contentJson); + ruleJson.ruleContent = contentJson; return ruleJson; })(); @@ -110,38 +115,48 @@ function rule2json() { // 转换搜索规则 let searchJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + //Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + Object.keys(RuleJSON.ruleSearch).forEach(key => { searchJson[key] = $('#' + 'ruleSearch_' + key).value; }); - RuleJSON.ruleSearch = JSON.stringify(searchJson); + //RuleJSON.ruleSearch = JSON.stringify(searchJson); + RuleJSON.ruleSearch = searchJson; // 转换发现规则 let exploreJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + //Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + Object.keys(RuleJSON.ruleExplore).forEach(key => { exploreJson[key] = $('#' + 'ruleExplore_' + key).value; }); - RuleJSON.ruleExplore = JSON.stringify(exploreJson); + //RuleJSON.ruleExplore = JSON.stringify(exploreJson); + RuleJSON.ruleExplore = exploreJson; // 转换详情页规则 let bookInfoJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + //Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + Object.keys(RuleJSON.ruleBookInfo).forEach(key => { bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value; }); - RuleJSON.ruleBookInfo = JSON.stringify(bookInfoJson); + //RuleJSON.ruleBookInfo = JSON.stringify(bookInfoJson); + RuleJSON.ruleBookInfo = bookInfoJson; // 转换目录规则 let tocJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + //Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + Object.keys(RuleJSON.ruleToc).forEach(key => { tocJson[key] = $('#' + 'ruleToc_' + key).value; }); - RuleJSON.ruleToc = JSON.stringify(tocJson); + //RuleJSON.ruleToc = JSON.stringify(tocJson); + RuleJSON.ruleToc = tocJson; // 转换正文规则 let contentJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + //Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + Object.keys(RuleJSON.ruleContent).forEach(key => { contentJson[key] = $('#' + 'ruleContent_' + key).value; }); - RuleJSON.ruleContent = JSON.stringify(contentJson); + //RuleJSON.ruleContent = JSON.stringify(contentJson); + RuleJSON.ruleContent = contentJson; RuleJSON.lastUpdateTime = RuleJSON.lastUpdateTime == '' ? 0 : parseInt(RuleJSON.lastUpdateTime); RuleJSON.customOrder = RuleJSON.customOrder == '' ? 0 : parseInt(RuleJSON.customOrder); @@ -171,40 +186,50 @@ function json2rule(RuleEditor) { // 转换搜索规则 if (RuleEditor.ruleSearch) { - let searchJson = JSON.parse(RuleEditor.ruleSearch); - Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + //let searchJson = JSON.parse(RuleEditor.ruleSearch); + let searchJson = RuleEditor.ruleSearch; + //Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + Object.keys(RuleJSON.ruleSearch).forEach(key => { $('#' + 'ruleSearch_' + key).value = searchJson[key] ? searchJson[key] : ''; }); } // 转换发现规则 if (RuleEditor.ruleExplore) { - let exploreJson = JSON.parse(RuleEditor.ruleExplore); - Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + //let exploreJson = JSON.parse(RuleEditor.ruleExplore); + //Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + let exploreJson = RuleEditor.ruleExplore; + Object.keys(RuleJSON.ruleExplore).forEach(key => { $('#' + 'ruleExplore_' + key).value = exploreJson[key] ? exploreJson[key] : ''; }); } // 转换详情页规则 if (RuleEditor.ruleBookInfo) { - let bookInfoJson = JSON.parse(RuleEditor.ruleBookInfo); - Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + //let bookInfoJson = JSON.parse(RuleEditor.ruleBookInfo); + //Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + let bookInfoJson = RuleEditor.ruleBookInfo; + Object.keys(RuleJSON.ruleBookInfo).forEach(key => { $('#' + 'ruleBookInfo_' + key).value = bookInfoJson[key] ? bookInfoJson[key] : ''; }); } // 转换目录规则 if (RuleEditor.ruleToc) { - let tocJson = JSON.parse(RuleEditor.ruleToc); - Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + //let tocJson = JSON.parse(RuleEditor.ruleToc); + //Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + let tocJson = RuleEditor.ruleToc; + Object.keys(RuleJSON.ruleToc).forEach(key => { $('#' + 'ruleToc_' + key).value = tocJson[key] ? tocJson[key] : ''; }); } // 转换正文规则 if (RuleEditor.ruleContent) { - let contentJson = JSON.parse(RuleEditor.ruleContent); - Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + //let contentJson = JSON.parse(RuleEditor.ruleContent); + //Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + let contentJson = RuleEditor.ruleContent; + Object.keys(RuleJSON.ruleContent).forEach(key => { $('#' + 'ruleContent_' + key).value = contentJson[key] ? contentJson[key] : ''; }); } diff --git a/app/src/main/assets/web/book.css b/app/src/main/assets/web/new/bookshelf.css similarity index 100% rename from app/src/main/assets/web/book.css rename to app/src/main/assets/web/new/bookshelf.css diff --git a/app/src/main/assets/web/new/bookshelf.html b/app/src/main/assets/web/new/bookshelf.html new file mode 100644 index 000000000..87ee3bf9b --- /dev/null +++ b/app/src/main/assets/web/new/bookshelf.html @@ -0,0 +1,46 @@ + + + + + + + + + yd-web-tool + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/app/src/main/assets/web/book.js b/app/src/main/assets/web/new/bookshelf.js similarity index 100% rename from app/src/main/assets/web/book.js rename to app/src/main/assets/web/new/bookshelf.js diff --git a/app/src/main/assets/web/css/about.f23c15cb.css b/app/src/main/assets/web/new/css/about.f23c15cb.css similarity index 100% rename from app/src/main/assets/web/css/about.f23c15cb.css rename to app/src/main/assets/web/new/css/about.f23c15cb.css diff --git a/app/src/main/assets/web/css/app.e1c0d2e4.css b/app/src/main/assets/web/new/css/app.e1c0d2e4.css similarity index 100% rename from app/src/main/assets/web/css/app.e1c0d2e4.css rename to app/src/main/assets/web/new/css/app.e1c0d2e4.css diff --git a/app/src/main/assets/web/css/chunk-vendors.ad4ff18f.css b/app/src/main/assets/web/new/css/chunk-vendors.ad4ff18f.css similarity index 100% rename from app/src/main/assets/web/css/chunk-vendors.ad4ff18f.css rename to app/src/main/assets/web/new/css/chunk-vendors.ad4ff18f.css diff --git a/app/src/main/assets/web/css/detail.42c41bd6.css b/app/src/main/assets/web/new/css/detail.42c41bd6.css similarity index 100% rename from app/src/main/assets/web/css/detail.42c41bd6.css rename to app/src/main/assets/web/new/css/detail.42c41bd6.css diff --git a/app/src/main/assets/web/fonts/element-icons.535877f5.woff b/app/src/main/assets/web/new/fonts/element-icons.535877f5.woff similarity index 100% rename from app/src/main/assets/web/fonts/element-icons.535877f5.woff rename to app/src/main/assets/web/new/fonts/element-icons.535877f5.woff diff --git a/app/src/main/assets/web/fonts/element-icons.732389de.ttf b/app/src/main/assets/web/new/fonts/element-icons.732389de.ttf similarity index 100% rename from app/src/main/assets/web/fonts/element-icons.732389de.ttf rename to app/src/main/assets/web/new/fonts/element-icons.732389de.ttf diff --git a/app/src/main/assets/web/fonts/iconfont.f9a3fb0e.woff b/app/src/main/assets/web/new/fonts/iconfont.f9a3fb0e.woff similarity index 100% rename from app/src/main/assets/web/fonts/iconfont.f9a3fb0e.woff rename to app/src/main/assets/web/new/fonts/iconfont.f9a3fb0e.woff diff --git a/app/src/main/assets/web/fonts/popfont.f39ecc1a.ttf b/app/src/main/assets/web/new/fonts/popfont.f39ecc1a.ttf similarity index 100% rename from app/src/main/assets/web/fonts/popfont.f39ecc1a.ttf rename to app/src/main/assets/web/new/fonts/popfont.f39ecc1a.ttf diff --git a/app/src/main/assets/web/fonts/shelffont.6c094b6d.ttf b/app/src/main/assets/web/new/fonts/shelffont.6c094b6d.ttf similarity index 100% rename from app/src/main/assets/web/fonts/shelffont.6c094b6d.ttf rename to app/src/main/assets/web/new/fonts/shelffont.6c094b6d.ttf diff --git a/app/src/main/assets/web/img/icons/android-chrome-192x192.png b/app/src/main/assets/web/new/img/icons/android-chrome-192x192.png similarity index 100% rename from app/src/main/assets/web/img/icons/android-chrome-192x192.png rename to app/src/main/assets/web/new/img/icons/android-chrome-192x192.png diff --git a/app/src/main/assets/web/img/icons/android-chrome-512x512.png b/app/src/main/assets/web/new/img/icons/android-chrome-512x512.png similarity index 100% rename from app/src/main/assets/web/img/icons/android-chrome-512x512.png rename to app/src/main/assets/web/new/img/icons/android-chrome-512x512.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon-120x120.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon-120x120.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon-120x120.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon-120x120.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon-152x152.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon-152x152.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon-152x152.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon-152x152.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon-180x180.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon-180x180.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon-180x180.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon-180x180.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon-60x60.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon-60x60.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon-60x60.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon-60x60.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon-76x76.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon-76x76.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon-76x76.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon-76x76.png diff --git a/app/src/main/assets/web/img/icons/apple-touch-icon.png b/app/src/main/assets/web/new/img/icons/apple-touch-icon.png similarity index 100% rename from app/src/main/assets/web/img/icons/apple-touch-icon.png rename to app/src/main/assets/web/new/img/icons/apple-touch-icon.png diff --git a/app/src/main/assets/web/img/icons/favicon-16x16.png b/app/src/main/assets/web/new/img/icons/favicon-16x16.png similarity index 100% rename from app/src/main/assets/web/img/icons/favicon-16x16.png rename to app/src/main/assets/web/new/img/icons/favicon-16x16.png diff --git a/app/src/main/assets/web/img/icons/favicon-32x32.png b/app/src/main/assets/web/new/img/icons/favicon-32x32.png similarity index 100% rename from app/src/main/assets/web/img/icons/favicon-32x32.png rename to app/src/main/assets/web/new/img/icons/favicon-32x32.png diff --git a/app/src/main/assets/web/img/icons/msapplication-icon-144x144.png b/app/src/main/assets/web/new/img/icons/msapplication-icon-144x144.png similarity index 100% rename from app/src/main/assets/web/img/icons/msapplication-icon-144x144.png rename to app/src/main/assets/web/new/img/icons/msapplication-icon-144x144.png diff --git a/app/src/main/assets/web/img/icons/mstile-150x150.png b/app/src/main/assets/web/new/img/icons/mstile-150x150.png similarity index 100% rename from app/src/main/assets/web/img/icons/mstile-150x150.png rename to app/src/main/assets/web/new/img/icons/mstile-150x150.png diff --git a/app/src/main/assets/web/img/icons/safari-pinned-tab.svg b/app/src/main/assets/web/new/img/icons/safari-pinned-tab.svg similarity index 100% rename from app/src/main/assets/web/img/icons/safari-pinned-tab.svg rename to app/src/main/assets/web/new/img/icons/safari-pinned-tab.svg diff --git a/app/src/main/assets/web/img/noCover.b5c48bc1.jpeg b/app/src/main/assets/web/new/img/noCover.b5c48bc1.jpeg similarity index 100% rename from app/src/main/assets/web/img/noCover.b5c48bc1.jpeg rename to app/src/main/assets/web/new/img/noCover.b5c48bc1.jpeg diff --git a/app/src/main/assets/web/js/about.2589b5fe.js b/app/src/main/assets/web/new/js/about.2589b5fe.js similarity index 100% rename from app/src/main/assets/web/js/about.2589b5fe.js rename to app/src/main/assets/web/new/js/about.2589b5fe.js diff --git a/app/src/main/assets/web/js/about~detail.08c372e6.js b/app/src/main/assets/web/new/js/about~detail.08c372e6.js similarity index 100% rename from app/src/main/assets/web/js/about~detail.08c372e6.js rename to app/src/main/assets/web/new/js/about~detail.08c372e6.js diff --git a/app/src/main/assets/web/js/app.b25f3cec.js b/app/src/main/assets/web/new/js/app.b25f3cec.js similarity index 100% rename from app/src/main/assets/web/js/app.b25f3cec.js rename to app/src/main/assets/web/new/js/app.b25f3cec.js diff --git a/app/src/main/assets/web/js/chunk-vendors.b3838a2d.js b/app/src/main/assets/web/new/js/chunk-vendors.b3838a2d.js similarity index 100% rename from app/src/main/assets/web/js/chunk-vendors.b3838a2d.js rename to app/src/main/assets/web/new/js/chunk-vendors.b3838a2d.js diff --git a/app/src/main/assets/web/js/detail.043d6e39.js b/app/src/main/assets/web/new/js/detail.043d6e39.js similarity index 100% rename from app/src/main/assets/web/js/detail.043d6e39.js rename to app/src/main/assets/web/new/js/detail.043d6e39.js diff --git a/app/src/main/assets/web/manifest.json b/app/src/main/assets/web/new/manifest.json similarity index 100% rename from app/src/main/assets/web/manifest.json rename to app/src/main/assets/web/new/manifest.json diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 5c4c02964..43381a2c8 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -5,6 +5,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.res.Configuration +import android.graphics.Color import android.os.Build import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate @@ -12,14 +13,19 @@ import com.jeremyliao.liveeventbus.LiveEventBus import io.legado.app.constant.AppConst.channelIdDownload import io.legado.app.constant.AppConst.channelIdReadAloud import io.legado.app.constant.AppConst.channelIdWeb +import io.legado.app.constant.EventBus +import io.legado.app.constant.PreferKey import io.legado.app.data.AppDatabase import io.legado.app.help.ActivityHelp import io.legado.app.help.AppConfig import io.legado.app.help.CrashHandler import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ThemeStore import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefInt +import io.legado.app.utils.postEvent +import io.legado.app.utils.putPrefInt @Suppress("DEPRECATION") class App : Application() { @@ -68,28 +74,66 @@ class App : Application() { * 更新主题 */ fun applyTheme() { - if (AppConfig.isNightTheme) { - ThemeStore.editTheme(this) - .primaryColor( - getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600)) - ).accentColor( - getPrefInt("colorAccentNight", getCompatColor(R.color.md_deep_orange_800)) - ).backgroundColor( - getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) - ).bottomBackground( - getPrefInt("colorBottomBackgroundNight", getCompatColor(R.color.md_grey_850)) - ).apply() - } else { - ThemeStore.editTheme(this) - .primaryColor( - getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800)) - ).accentColor( - getPrefInt("colorAccent", getCompatColor(R.color.md_red_600)) - ).backgroundColor( - getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100)) - ).bottomBackground( - getPrefInt("colorBottomBackground", getCompatColor(R.color.md_grey_200)) - ).apply() + when { + AppConfig.isEInkMode -> { + ThemeStore.editTheme(this) + .coloredNavigationBar(true) + .primaryColor(Color.WHITE) + .accentColor(Color.BLACK) + .backgroundColor(Color.WHITE) + .bottomBackground(Color.WHITE) + .apply() + } + AppConfig.isNightTheme -> { + val primary = + getPrefInt(PreferKey.cNPrimary, getCompatColor(R.color.md_blue_grey_600)) + val accent = + getPrefInt(PreferKey.cNAccent, getCompatColor(R.color.md_deep_orange_800)) + var background = + getPrefInt(PreferKey.cNBackground, getCompatColor(R.color.md_grey_900)) + if (ColorUtils.isColorLight(background)) { + background = getCompatColor(R.color.md_grey_900) + putPrefInt(PreferKey.cNBackground, background) + } + var bBackground = + getPrefInt(PreferKey.cNBBackground, getCompatColor(R.color.md_grey_850)) + if (!ColorUtils.isColorLight(bBackground)) { + bBackground = getCompatColor(R.color.md_grey_850) + putPrefInt(PreferKey.cNBBackground, bBackground) + } + ThemeStore.editTheme(this) + .coloredNavigationBar(true) + .primaryColor(ColorUtils.withAlpha(primary, 1f)) + .accentColor(ColorUtils.withAlpha(accent, 1f)) + .backgroundColor(ColorUtils.withAlpha(background, 1f)) + .bottomBackground(ColorUtils.withAlpha(bBackground, 1f)) + .apply() + } + else -> { + val primary = + getPrefInt(PreferKey.cPrimary, getCompatColor(R.color.md_indigo_800)) + val accent = + getPrefInt(PreferKey.cAccent, getCompatColor(R.color.md_red_600)) + var background = + getPrefInt(PreferKey.cBackground, getCompatColor(R.color.md_grey_100)) + if (!ColorUtils.isColorLight(background)) { + background = getCompatColor(R.color.md_grey_100) + putPrefInt(PreferKey.cBackground, background) + } + var bBackground = + getPrefInt(PreferKey.cBBackground, getCompatColor(R.color.md_grey_200)) + if (!ColorUtils.isColorLight(bBackground)) { + bBackground = getCompatColor(R.color.md_grey_200) + putPrefInt(PreferKey.cBBackground, bBackground) + } + ThemeStore.editTheme(this) + .coloredNavigationBar(true) + .primaryColor(ColorUtils.withAlpha(primary, 1f)) + .accentColor(ColorUtils.withAlpha(accent, 1f)) + .backgroundColor(ColorUtils.withAlpha(background, 1f)) + .bottomBackground(ColorUtils.withAlpha(bBackground, 1f)) + .apply() + } } } @@ -97,6 +141,7 @@ class App : Application() { ReadBookConfig.upBg() applyTheme() initNightMode() + postEvent(EventBus.RECREATE, "") } private fun initNightMode() { diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index e0d4713d7..bfa46c4b0 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -107,6 +107,11 @@ abstract class BaseActivity( } else if (theme == Theme.Light) { ATH.setLightStatusBar(this, true) } + upNavigationBarColor() + } + + open fun upNavigationBarColor() { + ATH.setNavigationBarColorAuto(this) } open fun observeLiveBus() { 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 57724c62f..10ffccd1f 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -20,7 +20,7 @@ object AppConst { const val UA_NAME = "User-Agent" val userAgent: String by lazy { - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" } val SCRIPT_ENGINE: ScriptEngine by lazy { @@ -41,8 +41,9 @@ object AppConst { val keyboardToolChars: List by lazy { arrayListOf( - "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".", - "href", "src", "textNodes", "xpath", "json", "css", "id", "class", "tag" + "※", "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", + "$", "#", "!", ".", "href", "src", "textNodes", "xpath", "json", "css", + "id", "class", "tag" ) } @@ -55,4 +56,15 @@ object AppConst { const val notificationIdAudio = 1144772 const val notificationIdWeb = 1144773 const val notificationIdDownload = 1144774 + + val urlOption: String by lazy { + """ + ,{ + "charset": "", + "method": "POST", + "body": "", + "headers": {"User-Agent": ""} + } + """.trimIndent() + } } \ No newline at end of file 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 8913956d3..93ea963ca 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -43,4 +43,19 @@ object PreferKey { const val readStyleSelect = "readStyleSelect" const val systemTypefaces = "system_typefaces" const val readBodyToLh = "readBodyToLh" + const val textFullJustify = "textFullJustify" + const val autoReadSpeed = "autoReadSpeed" + const val barElevation = "barElevation" + const val transparentStatusBar = "transparentStatusBar" + + const val cPrimary = "colorPrimary" + const val cAccent = "colorAccent" + const val cBackground = "colorBackground" + const val cBBackground = "colorBottomBackground" + + const val cNPrimary = "colorPrimaryNight" + const val cNAccent = "colorAccentNight" + const val cNBackground = "colorBackgroundNight" + const val cNBBackground = "colorBottomBackgroundNight" + } \ 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 f7eabc4b9..16faf6cad 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import io.legado.app.data.dao.* import io.legado.app.data.entities.* @@ -18,7 +19,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 = 10, + version = 13, exportSchema = true ) abstract class AppDatabase : RoomDatabase() { @@ -30,13 +31,48 @@ abstract class AppDatabase : RoomDatabase() { fun createDatabase(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) .fallbackToDestructiveMigration() + .addMigrations(migration_10_11, migration_11_12, migration_12_13) .addCallback(object : Callback() { override fun onDestructiveMigration(db: SupportSQLiteDatabase) { GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) } } }) + .allowMainThreadQueries() .build() } + + private val migration_10_11 = object : Migration(10, 11) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("DROP TABLE txtTocRules") + database.execSQL( + """ + CREATE TABLE txtTocRules(id INTEGER NOT NULL, + name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL, + enable INTEGER NOT NULL, PRIMARY KEY (id)) + """ + ) + } + } + + private val migration_11_12 = object : Migration(11, 12) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + ALTER TABLE rssSources ADD style TEXT + """ + ) + } + } + + private val migration_12_13 = object : Migration(12, 13) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0 + """ + ) + } + } } abstract fun bookDao(): BookDao 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 c91e01648..3dfaf21fa 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 @@ -21,9 +21,6 @@ interface BookDao { @Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}'") fun observeLocalUri(): LiveData> - @Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0") - fun observeDownload(): LiveData> - @Query("SELECT * FROM books WHERE (`group` & :group) > 0") fun observeByGroup(group: Int): LiveData> diff --git a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt index 55d5ada4c..0c0b8e49a 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt @@ -14,6 +14,12 @@ interface BookSourceDao { @Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey order by customOrder asc") fun liveDataSearch(searchKey: String = ""): LiveData> + @Query("select * from book_sources where enabled = 1 order by customOrder asc") + fun liveDataEnabled(): LiveData> + + @Query("select * from book_sources where enabled = 0 order by customOrder asc") + fun liveDataDisabled(): LiveData> + @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc") fun liveExplore(): LiveData> diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt index c02450091..e2586e3e0 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -19,7 +19,7 @@ interface SearchBookDao { @Query("select * from searchBooks where bookUrl = :bookUrl") fun getSearchBook(bookUrl: String): SearchBook? - @Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder limit 1") + @Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources) order by originOrder limit 1") fun getFirstByNameAuthor(name: String, author: String): SearchBook? @Query( diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt index 1bd502a09..d1b023896 100644 --- a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -28,4 +28,6 @@ interface TxtTocRuleDao { @Delete fun delete(vararg rule: TxtTocRule) + @Query("delete from txtTocRules where id < 0") + fun deleteDefault() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/BaseBook.kt b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt index 62d322b6c..31346a50c 100644 --- a/app/src/main/java/io/legado/app/data/entities/BaseBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/BaseBook.kt @@ -4,7 +4,7 @@ import io.legado.app.utils.splitNotBlank interface BaseBook { var bookUrl: String - var variableMap: HashMap? + val variableMap: HashMap var kind: String? var wordCount: String? 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 fd96d137b..104520ff3 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 @@ -56,10 +56,14 @@ data class Book( return origin == BookType.local } - fun isTxt(): Boolean { + fun isLocalTxt(): Boolean { return isLocalBook() && originName.endsWith(".txt", true) } + fun isOnLineTxt(): Boolean { + return !isLocalBook() && type == 0 + } + override fun equals(other: Any?): Boolean { if (other is Book) { return other.bookUrl == bookUrl @@ -71,15 +75,17 @@ data class Book( return bookUrl.hashCode() } - @Ignore + @delegate:Transient + @delegate:Ignore @IgnoredOnParcel - override var variableMap: HashMap? = null - get() { - if (field == null) { - field = GSON.fromJsonObject>(variable) ?: HashMap() - } - return field - } + override val variableMap by lazy { + GSON.fromJsonObject>(variable) ?: HashMap() + } + + override fun putVariable(key: String, value: String) { + variableMap[key] = value + variable = GSON.toJson(variableMap) + } @Ignore @IgnoredOnParcel @@ -97,11 +103,6 @@ data class Book( fun getDisplayIntro() = if (customIntro.isNullOrEmpty()) intro else customIntro - override fun putVariable(key: String, value: String) { - variableMap?.put(key, value) - variable = GSON.toJson(variableMap) - } - fun fileCharset(): Charset { return charset(charset ?: "UTF-8") } 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 ad16bf99f..48e436262 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 @@ -1,26 +1,21 @@ package io.legado.app.data.entities import android.os.Parcelable -import androidx.room.Entity -import androidx.room.Ignore -import androidx.room.Index -import androidx.room.PrimaryKey +import android.text.TextUtils +import androidx.room.* import io.legado.app.App import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst.userAgent import io.legado.app.constant.BookType import io.legado.app.data.entities.rule.* import io.legado.app.help.JsExtensions -import io.legado.app.utils.ACache -import io.legado.app.utils.GSON -import io.legado.app.utils.fromJsonObject -import io.legado.app.utils.getPrefString -import kotlinx.android.parcel.IgnoredOnParcel +import io.legado.app.utils.* import kotlinx.android.parcel.Parcelize import java.util.* import javax.script.SimpleBindings @Parcelize +@TypeConverters(BookSource.Converters::class) @Entity( tableName = "book_sources", indices = [(Index(value = ["bookSourceUrl"], unique = false))] @@ -40,12 +35,12 @@ data class BookSource( var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 var weight: Int = 0, // 智能排序的权重 var exploreUrl: String? = null, // 发现url - var ruleExplore: String? = null, // 发现规则 + var ruleExplore: ExploreRule? = null, // 发现规则 var searchUrl: String? = null, // 搜索url - var ruleSearch: String? = null, // 搜索规则 - var ruleBookInfo: String? = null, // 书籍信息页规则 - var ruleToc: String? = null, // 目录页规则 - var ruleContent: String? = null // 正文页规则 + var ruleSearch: SearchRule? = null, // 搜索规则 + var ruleBookInfo: BookInfoRule? = null, // 书籍信息页规则 + var ruleToc: TocRule? = null, // 目录页规则 + var ruleContent: ContentRule? = null // 正文页规则 ) : Parcelable, JsExtensions { override fun hashCode(): Int { @@ -59,26 +54,6 @@ data class BookSource( return false } - @Ignore - @IgnoredOnParcel - private var searchRuleV: SearchRule? = null - - @Ignore - @IgnoredOnParcel - private var exploreRuleV: ExploreRule? = null - - @Ignore - @IgnoredOnParcel - private var bookInfoRuleV: BookInfoRule? = null - - @Ignore - @IgnoredOnParcel - private var tocRuleV: TocRule? = null - - @Ignore - @IgnoredOnParcel - private var contentRuleV: ContentRule? = null - @Throws(Exception::class) fun getHeaderMap(): Map { val headerMap = HashMap() @@ -99,55 +74,42 @@ data class BookSource( } fun getSearchRule(): SearchRule { - searchRuleV ?: let { - searchRuleV = GSON.fromJsonObject(ruleSearch) - searchRuleV ?: let { searchRuleV = SearchRule() } - } - return searchRuleV!! + return ruleSearch ?: SearchRule() } fun getExploreRule(): ExploreRule { - exploreRuleV ?: let { - exploreRuleV = GSON.fromJsonObject(ruleExplore) - exploreRuleV ?: let { exploreRuleV = ExploreRule() } - } - return exploreRuleV!! + return ruleExplore ?: ExploreRule() } fun getBookInfoRule(): BookInfoRule { - bookInfoRuleV ?: let { - bookInfoRuleV = GSON.fromJsonObject(ruleBookInfo) - bookInfoRuleV ?: let { bookInfoRuleV = BookInfoRule() } - } - return bookInfoRuleV!! + return ruleBookInfo ?: BookInfoRule() } fun getTocRule(): TocRule { - tocRuleV ?: let { - tocRuleV = GSON.fromJsonObject(ruleToc) - tocRuleV ?: let { tocRuleV = TocRule() } - } - return tocRuleV!! + return ruleToc ?: TocRule() } fun getContentRule(): ContentRule { - contentRuleV ?: let { - contentRuleV = GSON.fromJsonObject(ruleContent) - contentRuleV ?: let { contentRuleV = ContentRule() } - } - return contentRuleV!! + return ruleContent ?: ContentRule() } fun addGroup(group: String) { bookSourceGroup?.let { if (!it.contains(group)) { - bookSourceGroup = "$it;$group" + bookSourceGroup = "$it,$group" } } ?: let { bookSourceGroup = group } } + fun removeGroup(group: String) { + bookSourceGroup?.splitNotBlank("[,;]".toRegex())?.toHashSet()?.let { + it.remove(group) + bookSourceGroup = TextUtils.join(",", it) + } + } + fun getExploreKinds(): ArrayList? { val exploreKinds = arrayListOf() exploreUrl?.let { @@ -219,4 +181,57 @@ data class BookSource( var title: String, var url: String? = null ) + + class Converters { + @TypeConverter + fun exploreRuleToString(exploreRule: ExploreRule?): String? { + return GSON.toJson(exploreRule) + } + + @TypeConverter + fun stringToExploreRule(json: String?): ExploreRule? { + return GSON.fromJsonObject(json) + } + + @TypeConverter + fun searchRuleToString(searchRule: SearchRule?): String? { + return GSON.toJson(searchRule) + } + + @TypeConverter + fun stringToSearchRule(json: String?): SearchRule? { + return GSON.fromJsonObject(json) + } + + @TypeConverter + fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String? { + return GSON.toJson(bookInfoRule) + } + + @TypeConverter + fun stringToBookInfoRule(json: String?): BookInfoRule? { + return GSON.fromJsonObject(json) + } + + @TypeConverter + fun tocRuleToString(tocRule: TocRule?): String? { + return GSON.toJson(tocRule) + } + + @TypeConverter + fun stringToTocRule(json: String?): TocRule? { + return GSON.fromJsonObject(json) + } + + @TypeConverter + fun contentRuleToString(contentRule: ContentRule?): String? { + return GSON.toJson(contentRule) + } + + @TypeConverter + fun stringToContentRule(json: String?): ContentRule? { + return GSON.fromJsonObject(json) + } + + } } \ No newline at end of file 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 15dafa3ab..d9ae5531b 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 @@ -21,7 +21,7 @@ data class RssArticle( ) { override fun hashCode(): Int { - return super.hashCode() + return link.hashCode() } override fun equals(other: Any?): Boolean { 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 e9a58ee98..e30707446 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 @@ -24,6 +24,7 @@ data class RssSource( var sourceGroup: String? = null, var enabled: Boolean = true, var sortUrl: String? = null, + var articleStyle: Int = 0, //列表规则 var ruleArticles: String? = null, var ruleNextPage: String? = null, @@ -34,9 +35,11 @@ data class RssSource( var ruleImage: String? = null, var ruleLink: String? = null, var ruleContent: String? = null, + var style: String? = null, var header: String? = null, var enableJs: Boolean = false, var loadWithBaseUrl: Boolean = false, + var customOrder: Int = 0 ) : Parcelable, JsExtensions { diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt index 27f666547..eb7ea0c10 100644 --- a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt @@ -63,21 +63,19 @@ data class SearchBook( return other.originOrder - this.originOrder } - @Ignore + @delegate:Transient + @delegate:Ignore @IgnoredOnParcel - override var variableMap: HashMap? = null - get() { - if (field == null) { - field = GSON.fromJsonObject>(variable) ?: HashMap() - } - return field - } + override val variableMap by lazy { + GSON.fromJsonObject>(variable) ?: HashMap() + } override fun putVariable(key: String, value: String) { - variableMap?.put(key, value) + variableMap[key] = value variable = GSON.toJson(variableMap) } + @delegate:Transient @delegate:Ignore @IgnoredOnParcel val origins: LinkedHashSet by lazy { linkedSetOf(origin) } diff --git a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt index cdfca7567..52669dab7 100644 --- a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt @@ -7,6 +7,7 @@ import androidx.room.PrimaryKey @Entity(tableName = "txtTocRules") data class TxtTocRule( @PrimaryKey + var id: Long = System.currentTimeMillis(), var name: String = "", var rule: String = "", var serialNumber: Int = -1, diff --git a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt index a28f3c6e6..57fa3c495 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt @@ -1,5 +1,8 @@ package io.legado.app.data.entities.rule +import android.os.Parcel +import android.os.Parcelable + data class BookInfoRule( var init: String? = null, var name: String? = null, @@ -11,4 +14,45 @@ data class BookInfoRule( var coverUrl: String? = null, var tocUrl: String? = null, var wordCount: String? = null -) \ No newline at end of file +) : Parcelable { + + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() + ) + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(init) + dest.writeString(name) + dest.writeString(author) + dest.writeString(intro) + dest.writeString(kind) + dest.writeString(lastChapter) + dest.writeString(updateTime) + dest.writeString(coverUrl) + dest.writeString(tocUrl) + dest.writeString(wordCount) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BookInfoRule { + return BookInfoRule(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt index b0fe78614..306e7e659 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt @@ -1,8 +1,39 @@ package io.legado.app.data.entities.rule +import android.os.Parcel +import android.os.Parcelable + data class ContentRule( var content: String? = null, var nextContentUrl: String? = null, var webJs: String? = null, var sourceRegex: String? = null -) \ No newline at end of file +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() + ) + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(content) + dest.writeString(nextContentUrl) + dest.writeString(webJs) + dest.writeString(sourceRegex) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ContentRule { + return ContentRule(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt index 22b267ed3..8ccbe8aba 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt @@ -1,5 +1,8 @@ package io.legado.app.data.entities.rule +import android.os.Parcel +import android.os.Parcelable + data class ExploreRule( override var bookList: String? = null, override var name: String? = null, @@ -11,4 +14,46 @@ data class ExploreRule( override var bookUrl: String? = null, override var coverUrl: String? = null, override var wordCount: String? = null -) : BookListRule \ No newline at end of file +) : BookListRule, Parcelable { + + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() + ) + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(bookList) + dest.writeString(name) + dest.writeString(author) + dest.writeString(intro) + dest.writeString(kind) + dest.writeString(lastChapter) + dest.writeString(updateTime) + dest.writeString(bookUrl) + dest.writeString(coverUrl) + dest.writeString(wordCount) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ExploreRule { + return ExploreRule(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + diff --git a/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt index 83921e56d..3968226e2 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt @@ -1,5 +1,8 @@ package io.legado.app.data.entities.rule +import android.os.Parcel +import android.os.Parcelable + data class SearchRule( override var bookList: String? = null, override var name: String? = null, @@ -11,4 +14,46 @@ data class SearchRule( override var bookUrl: String? = null, override var coverUrl: String? = null, override var wordCount: String? = null -) : BookListRule \ No newline at end of file +) : BookListRule, Parcelable { + + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() + ) + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(bookList) + dest.writeString(name) + dest.writeString(author) + dest.writeString(intro) + dest.writeString(kind) + dest.writeString(lastChapter) + dest.writeString(updateTime) + dest.writeString(bookUrl) + dest.writeString(coverUrl) + dest.writeString(wordCount) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SearchRule { + return SearchRule(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + diff --git a/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt index c484e7226..d49a08e33 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt @@ -1,5 +1,8 @@ package io.legado.app.data.entities.rule +import android.os.Parcel +import android.os.Parcelable + data class TocRule( var chapterList: String? = null, var chapterName: String? = null, @@ -7,4 +10,36 @@ data class TocRule( var isVip: String? = null, var updateTime: String? = null, var nextTocUrl: String? = null -) \ No newline at end of file +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString(), + parcel.readString() + ) + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(chapterList) + dest.writeString(chapterName) + dest.writeString(chapterUrl) + dest.writeString(isVip) + dest.writeString(updateTime) + dest.writeString(nextTocUrl) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): TocRule { + return TocRule(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt b/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt index 24f0c0f2b..1aeddeb95 100644 --- a/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt +++ b/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt @@ -3,8 +3,8 @@ package io.legado.app.help import androidx.recyclerview.widget.RecyclerView internal class AdapterDataObserverHeader( - var adapterDataObserver: RecyclerView.AdapterDataObserver, - var headerCount: Int + private var adapterDataObserver: RecyclerView.AdapterDataObserver, + private var headerCount: Int ) : RecyclerView.AdapterDataObserver() { override fun onChanged() { adapterDataObserver.onChanged() 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 9d2a3f679..43d4e8ac3 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -1,7 +1,7 @@ package io.legado.app.help +import android.annotation.SuppressLint import android.content.Context -import android.content.pm.PackageManager import io.legado.app.App import io.legado.app.R import io.legado.app.constant.PreferKey @@ -13,6 +13,7 @@ object AppConfig { return when (context.getPrefString(PreferKey.themeMode, "0")) { "1" -> false "2" -> true + "3" -> false else -> context.sysIsDarkMode() } } @@ -27,10 +28,13 @@ object AppConfig { } } + val isEInkMode: Boolean + get() = App.INSTANCE.getPrefString(PreferKey.themeMode) == "3" + var isTransparentStatusBar: Boolean - get() = App.INSTANCE.getPrefBoolean("transparentStatusBar") + get() = App.INSTANCE.getPrefBoolean(PreferKey.transparentStatusBar) set(value) { - App.INSTANCE.putPrefBoolean("transparentStatusBar", value) + App.INSTANCE.putPrefBoolean(PreferKey.transparentStatusBar, value) } val requestedDirection: String? @@ -80,9 +84,6 @@ object AppConfig { val ttsSpeechPer: String get() = App.INSTANCE.getPrefString(PreferKey.ttsSpeechPer) ?: "0" - val isEInkMode: Boolean - get() = App.INSTANCE.getPrefBoolean("isEInkMode") - val clickAllNext: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.clickAllNext, false) var chineseConverterType: Int @@ -122,25 +123,19 @@ object AppConfig { } var elevation: Int - get() = App.INSTANCE.getPrefInt("elevation", -1) + @SuppressLint("PrivateResource") + get() = App.INSTANCE.getPrefInt( + PreferKey.barElevation, + App.INSTANCE.resources.getDimension(R.dimen.design_appbar_elevation).toInt() + ) set(value) { - App.INSTANCE.putPrefInt("elevation", value) + App.INSTANCE.putPrefInt(PreferKey.barElevation, value) } + val autoChangeSource: Boolean get() = App.INSTANCE.getPrefBoolean("autoChangeSource", true) + val readBodyToLh: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.readBodyToLh, true) val isGooglePlay: Boolean get() = App.INSTANCE.channel == "google" } -val Context.channel: String - get() { - try { - val pm = packageManager - val appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - return appInfo.metaData.getString("channel") ?: "" - } catch (e: Exception) { - e.printStackTrace(); - } - return "" - } - 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 514c5327c..3c0fa644e 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -196,7 +196,7 @@ object BookHelp { origin: String?, content: String, enableReplace: Boolean - ): String { + ): List { var c = content if (enableReplace) { synchronized(this) { @@ -228,9 +228,6 @@ object BookHelp { } } } - if (!c.substringBefore("\n").contains(title)) { - c = "$title\n$c" - } try { when (AppConfig.chineseConverterType) { 1 -> c = HanLP.convertToSimplifiedChinese(c) @@ -241,7 +238,19 @@ object BookHelp { App.INSTANCE.toast("简繁转换出错") } } - return c - .replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") + val contents = arrayListOf() + c.split("\n").forEach { + val str = it.replace("^\\s+".toRegex(), "") + .replace("\r", "") + if (contents.isEmpty()) { + contents.add(title) + if (str != title && it.isNotEmpty()) { + contents.add("${ReadBookConfig.bodyIndent}$str") + } + } else if (str.isNotEmpty()) { + contents.add("${ReadBookConfig.bodyIndent}$str") + } + } + return contents } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/ImageLoader.kt b/app/src/main/java/io/legado/app/help/ImageLoader.kt index 3d4272e9e..11f8dff21 100644 --- a/app/src/main/java/io/legado/app/help/ImageLoader.kt +++ b/app/src/main/java/io/legado/app/help/ImageLoader.kt @@ -7,14 +7,20 @@ import android.net.Uri import androidx.annotation.DrawableRes import com.bumptech.glide.Glide import com.bumptech.glide.RequestBuilder +import io.legado.app.utils.isAbsUrl +import io.legado.app.utils.isContentPath import java.io.File object ImageLoader { + /** + * 自动判断path类型 + */ fun load(context: Context, path: String?): RequestBuilder { return when { path.isNullOrEmpty() -> Glide.with(context).load(path) - path.startsWith("http", true) -> Glide.with(context).load(path) + path.isAbsUrl() -> Glide.with(context).load(path) + path.isContentPath() -> Glide.with(context).load(Uri.parse(path)) else -> try { Glide.with(context).load(File(path)) } catch (e: Exception) { 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 ff4180081..823ddee55 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -27,8 +27,16 @@ object ReadBookConfig { GSON.fromJsonArray(json)!! } val durConfig get() = getConfig(styleSelect) + var bg: Drawable? = null var bgMeanColor: Int = 0 + val textColor: Int + get() = + if (AppConfig.isEInkMode && !AppConfig.isNightTheme) { + Color.BLACK + } else { + durConfig.textColor() + } init { upConfig() @@ -59,6 +67,7 @@ object ReadBookConfig { val json = configFile.readText() return GSON.fromJsonArray(json) } catch (e: Exception) { + e.printStackTrace() } } return null @@ -69,11 +78,16 @@ object ReadBookConfig { val dm = resources.displayMetrics val width = dm.widthPixels val height = dm.heightPixels - bg = durConfig.bgDrawable(width, height).apply { - if (this is BitmapDrawable) { - bgMeanColor = BitmapUtils.getMeanColor(bitmap) - } else if (this is ColorDrawable) { - bgMeanColor = color + if (AppConfig.isEInkMode && !AppConfig.isNightTheme) { + bg = ColorDrawable(Color.WHITE) + bgMeanColor = Color.WHITE + } else { + bg = durConfig.bgDrawable(width, height).apply { + if (this is BitmapDrawable) { + bgMeanColor = BitmapUtils.getMeanColor(bitmap) + } else if (this is ColorDrawable) { + bgMeanColor = color + } } } } @@ -103,6 +117,11 @@ object ReadBookConfig { } //配置写入读取 + var autoReadSpeed = App.INSTANCE.getPrefInt(PreferKey.autoReadSpeed, 46) + set(value) { + field = value + App.INSTANCE.putPrefInt(PreferKey.autoReadSpeed, value) + } var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) set(value) { field = value @@ -117,16 +136,14 @@ object ReadBookConfig { App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value) } } - var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim) + var pageAnim: Int + get() = if (AppConfig.isEInkMode) -1 else App.INSTANCE.getPrefInt(PreferKey.pageAnim) set(value) { - field = value - isScroll = value == 3 - if (App.INSTANCE.getPrefInt(PreferKey.pageAnim) != value) { - App.INSTANCE.putPrefInt(PreferKey.pageAnim, value) - } + App.INSTANCE.putPrefInt(PreferKey.pageAnim, value) } var isScroll = pageAnim == 3 val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true) + val textFullJustify get() = App.INSTANCE.getPrefBoolean(PreferKey.textFullJustify, true) var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) set(value) { field = value @@ -141,7 +158,7 @@ object ReadBookConfig { private val config get() = if (shareLayout) getConfig(5) else durConfig - var textBold: Boolean + var textBold: Int get() = config.textBold set(value) { config.textBold = value @@ -288,11 +305,11 @@ object ReadBookConfig { private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏 private var textColor: String = "#3E3D3B",//白天文字颜色 private var textColorNight: String = "#ADADAD",//夜间文字颜色 - var textBold: Boolean = false,//是否粗体字 + var textBold: Int = 0,//是否粗体字 0:正常, 1:粗体, 2:细体 var textSize: Int = 20,//文字大小 - var letterSpacing: Float = 0.5f,//字间距 + var letterSpacing: Float = 0.1f,//字间距 var lineSpacingExtra: Int = 12,//行间距 - var paragraphSpacing: Int = 12,//段距 + var paragraphSpacing: Int = 4,//段距 var titleMode: Int = 0,//标题居中 var titleSize: Int = 0, var titleTopSpacing: Int = 0, diff --git a/app/src/main/java/io/legado/app/help/SourceHelp.kt b/app/src/main/java/io/legado/app/help/SourceHelp.kt new file mode 100644 index 000000000..bdb883b10 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/SourceHelp.kt @@ -0,0 +1,54 @@ +package io.legado.app.help + +import android.os.Handler +import android.os.Looper +import io.legado.app.App +import io.legado.app.data.entities.RssSource +import io.legado.app.utils.EncoderUtils +import io.legado.app.utils.NetworkUtils +import io.legado.app.utils.splitNotBlank +import org.jetbrains.anko.toast + +object SourceHelp { + + private val handler = Handler(Looper.getMainLooper()) + private val list18Plus by lazy { + try { + return@lazy String(App.INSTANCE.assets.open("18PlusList.txt").readBytes()) + .splitNotBlank("\n") + } catch (e: Exception) { + return@lazy arrayOf() + } + } + + fun insertRssSource(vararg rssSources: RssSource) { + rssSources.forEach { rssSource -> + if (is18Plus(rssSource.sourceUrl)) { + handler.post { + App.INSTANCE.toast("${rssSource.sourceName}是18+网址,禁止导入.") + } + } else { + App.db.rssSourceDao().insert(rssSource) + } + } + } + + private fun is18Plus(url: String?): Boolean { + url ?: return false + if (AppConfig.isGooglePlay) return false + val baseUrl = NetworkUtils.getBaseUrl(url) + baseUrl ?: return false + try { + val host = baseUrl.split("//", ".") + val base64Url = EncoderUtils.base64Encode("${host[host.lastIndex - 1]}.${host.last()}") + list18Plus.forEach { + if (base64Url == it) { + return true + } + } + } catch (e: Exception) { + } + return false + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/CookieStore.kt b/app/src/main/java/io/legado/app/help/http/CookieStore.kt index d82a4b175..42634ede1 100644 --- a/app/src/main/java/io/legado/app/help/http/CookieStore.kt +++ b/app/src/main/java/io/legado/app/help/http/CookieStore.kt @@ -39,10 +39,6 @@ object CookieStore { App.db.cookieDao().delete(url) } - fun clearCookies() { - - } - private fun cookieToMap(cookie: String): MutableMap { val cookieMap = mutableMapOf() if (cookie.isBlank()) { diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index 31064e268..a65423e78 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -1,5 +1,6 @@ package io.legado.app.help.http +import io.legado.app.constant.AppConst import io.legado.app.help.http.api.HttpGetApi import io.legado.app.utils.NetworkUtils import kotlinx.coroutines.suspendCancellableCoroutine @@ -41,7 +42,7 @@ object HttpHelper { fun simpleGet(url: String, encode: String? = null): String? { NetworkUtils.getBaseUrl(url)?.let { baseUrl -> val response = getApiService(baseUrl, encode) - .get(url, mapOf()) + .get(url, mapOf(Pair(AppConst.UA_NAME, AppConst.userAgent))) .execute() return response.body() } @@ -51,7 +52,7 @@ object HttpHelper { suspend fun simpleGetAsync(url: String, encode: String? = null): String? { NetworkUtils.getBaseUrl(url)?.let { baseUrl -> val response = getApiService(baseUrl, encode) - .getAsync(url, mapOf()) + .getAsync(url, mapOf(Pair(AppConst.UA_NAME, AppConst.userAgent))) return response.body() } return null @@ -61,7 +62,7 @@ object HttpHelper { NetworkUtils.getBaseUrl(url)?.let { baseUrl -> return getByteRetrofit(baseUrl) .create(HttpGetApi::class.java) - .getMapByteAsync(url, mapOf(), mapOf()) + .getMapByteAsync(url, mapOf(), mapOf(Pair(AppConst.UA_NAME, AppConst.userAgent))) .body() } return null diff --git a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt index 4af39cbb2..da769dbe2 100644 --- a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt +++ b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt @@ -6,113 +6,78 @@ import androidx.documentfile.provider.DocumentFile import io.legado.app.App import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.ReplaceRule -import io.legado.app.help.coroutine.Coroutine import io.legado.app.utils.DocumentUtils import io.legado.app.utils.FileUtils -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.jetbrains.anko.toast import java.io.File object ImportOldData { fun import(context: Context, file: File) { - GlobalScope.launch(Dispatchers.IO) { - try {// 导入书架 - val shelfFile = - FileUtils.createFileIfNotExist(file, "myBookShelf.json") - val json = shelfFile.readText() - val importCount = importOldBookshelf(json) - withContext(Dispatchers.Main) { - context.toast("成功导入书籍${importCount}") - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - context.toast("导入书籍失败\n${e.localizedMessage}") - } - } + try {// 导入书架 + val shelfFile = + FileUtils.createFileIfNotExist(file, "myBookShelf.json") + val json = shelfFile.readText() + val importCount = importOldBookshelf(json) + context.toast("成功导入书籍${importCount}") + } catch (e: Exception) { + context.toast("导入书籍失败\n${e.localizedMessage}") + } - try {// Book source - val sourceFile = - FileUtils.getFile(file, "myBookSource.json") - val json = sourceFile.readText() - val importCount = importOldSource(json) - withContext(Dispatchers.Main) { - context.toast("成功导入书源${importCount}") - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - context.toast("导入源失败\n${e.localizedMessage}") - } - } + try {// Book source + val sourceFile = + FileUtils.getFile(file, "myBookSource.json") + val json = sourceFile.readText() + val importCount = importOldSource(json) + context.toast("成功导入书源${importCount}") + } catch (e: Exception) { + context.toast("导入源失败\n${e.localizedMessage}") + } - try {// Replace rules - val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json") - if (ruleFile.exists()) { - val json = ruleFile.readText() - val importCount = importOldReplaceRule(json) - withContext(Dispatchers.Main) { - context.toast("成功导入替换规则${importCount}") - } - } else { - withContext(Dispatchers.Main) { - context.toast("未找到替换规则") - } - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - context.toast("导入替换规则失败\n${e.localizedMessage}") - } + try {// Replace rules + val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json") + if (ruleFile.exists()) { + val json = ruleFile.readText() + val importCount = importOldReplaceRule(json) + context.toast("成功导入替换规则${importCount}") + } else { + context.toast("未找到替换规则") } + } catch (e: Exception) { + context.toast("导入替换规则失败\n${e.localizedMessage}") } } fun importUri(uri: Uri) { - Coroutine.async { - DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach { - when (it.name) { - "myBookShelf.json" -> - try { - DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> - val importCount = importOldBookshelf(json) - withContext(Dispatchers.Main) { - App.INSTANCE.toast("成功导入书籍${importCount}") - } - } - } catch (e: java.lang.Exception) { - withContext(Dispatchers.Main) { - App.INSTANCE.toast("导入书籍失败\n${e.localizedMessage}") - } + DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach { + when (it.name) { + "myBookShelf.json" -> + try { + DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> + val importCount = importOldBookshelf(json) + App.INSTANCE.toast("成功导入书籍${importCount}") } - "myBookSource.json" -> - try { - DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> - val importCount = importOldSource(json) - withContext(Dispatchers.Main) { - App.INSTANCE.toast("成功导入书源${importCount}") - } - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - App.INSTANCE.toast("导入源失败\n${e.localizedMessage}") - } + } catch (e: java.lang.Exception) { + App.INSTANCE.toast("导入书籍失败\n${e.localizedMessage}") + } + "myBookSource.json" -> + try { + DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> + val importCount = importOldSource(json) + App.INSTANCE.toast("成功导入书源${importCount}") } - "myBookReplaceRule.json" -> - try { - DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> - val importCount = importOldReplaceRule(json) - withContext(Dispatchers.Main) { - App.INSTANCE.toast("成功导入替换规则${importCount}") - } - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - App.INSTANCE.toast("导入替换规则失败\n${e.localizedMessage}") - } + } catch (e: Exception) { + App.INSTANCE.toast("导入源失败\n${e.localizedMessage}") + } + "myBookReplaceRule.json" -> + try { + DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json -> + val importCount = importOldReplaceRule(json) + App.INSTANCE.toast("成功导入替换规则${importCount}") } - } + } catch (e: Exception) { + App.INSTANCE.toast("导入替换规则失败\n${e.localizedMessage}") + } } } } @@ -136,14 +101,12 @@ object ImportOldData { return bookSources.size } - - fun importOldReplaceRule(json: String): Int { val replaceRules = mutableListOf() val items: List> = Restore.jsonPath.parse(json).read("$") for (item in items) { val jsonItem = Restore.jsonPath.parse(item) - OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let { + OldReplace.jsonToReplaceRule(jsonItem.jsonString())?.let { if (it.isValid()){ replaceRules.add(it) } diff --git a/app/src/main/java/io/legado/app/help/storage/OldReplace.kt b/app/src/main/java/io/legado/app/help/storage/OldReplace.kt new file mode 100644 index 000000000..d658106dc --- /dev/null +++ b/app/src/main/java/io/legado/app/help/storage/OldReplace.kt @@ -0,0 +1,32 @@ +package io.legado.app.help.storage + +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.utils.* + +object OldReplace { + + fun jsonToReplaceRule(json: String): ReplaceRule? { + var replaceRule: ReplaceRule? = null + runCatching { + replaceRule = GSON.fromJsonObject(json.trim()) + } + runCatching { + if (replaceRule == null || replaceRule?.pattern.isNullOrBlank()) { + val jsonItem = Restore.jsonPath.parse(json.trim()) + val rule = ReplaceRule() + rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis() + rule.pattern = jsonItem.readString("$.regex") ?: "" + if (rule.pattern.isEmpty()) return null + rule.name = jsonItem.readString("$.replaceSummary") ?: "" + rule.replacement = jsonItem.readString("$.replacement") ?: "" + rule.isRegex = jsonItem.readBool("$.isRegex") == true + rule.scope = jsonItem.readString("$.useTo") + rule.isEnabled = jsonItem.readBool("$.enable") == true + rule.order = jsonItem.readInt("$.serialNumber") ?: 0 + return rule + } + } + return replaceRule + } + +} \ No newline at end of file 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 d605b02fe..b69b35526 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 @@ -1,9 +1,9 @@ package io.legado.app.help.storage +import androidx.annotation.Keep import io.legado.app.constant.AppConst import io.legado.app.constant.BookType import io.legado.app.data.entities.BookSource -import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.rule.* import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* @@ -14,13 +14,15 @@ object OldRule { private val jsPattern = Pattern.compile("\\{\\{.+?\\}\\}", Pattern.CASE_INSENSITIVE) fun jsonToBookSource(json: String): BookSource? { - var source: BookSource? = null - runCatching { - source = GSON.fromJsonObject(json.trim()) + val source = BookSource() + val sourceAny = try { + GSON.fromJsonObject(json.trim()) + } catch (e: Exception) { + null } - runCatching { - if (source == null || source?.ruleToc.isNullOrBlank()) { - source = BookSource().apply { + try { + if (sourceAny?.ruleToc == null) { + source.apply { val jsonItem = jsonPath.parse(json.trim()) bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: "" bookSourceName = jsonItem.readString("bookSourceName") ?: "" @@ -37,7 +39,7 @@ object OldRule { if (exploreUrl.isNullOrBlank()) { enabledExplore = false } - val searchRule = SearchRule( + ruleSearch = SearchRule( bookList = toNewRule(jsonItem.readString("ruleSearchList")), name = toNewRule(jsonItem.readString("ruleSearchName")), author = toNewRule(jsonItem.readString("ruleSearchAuthor")), @@ -47,8 +49,7 @@ object OldRule { coverUrl = toNewRule(jsonItem.readString("ruleSearchCoverUrl")), lastChapter = toNewRule(jsonItem.readString("ruleSearchLastChapter")) ) - ruleSearch = GSON.toJson(searchRule) - val exploreRule = ExploreRule( + ruleExplore = ExploreRule( bookList = toNewRule(jsonItem.readString("ruleFindList")), name = toNewRule(jsonItem.readString("ruleFindName")), author = toNewRule(jsonItem.readString("ruleFindAuthor")), @@ -58,8 +59,7 @@ object OldRule { coverUrl = toNewRule(jsonItem.readString("ruleFindCoverUrl")), lastChapter = toNewRule(jsonItem.readString("ruleFindLastChapter")) ) - ruleExplore = GSON.toJson(exploreRule) - val bookInfoRule = BookInfoRule( + ruleBookInfo = BookInfoRule( init = toNewRule(jsonItem.readString("ruleBookInfoInit")), name = toNewRule(jsonItem.readString("ruleBookName")), author = toNewRule(jsonItem.readString("ruleBookAuthor")), @@ -69,29 +69,91 @@ object OldRule { lastChapter = toNewRule(jsonItem.readString("ruleBookLastChapter")), tocUrl = toNewRule(jsonItem.readString("ruleChapterUrl")) ) - ruleBookInfo = GSON.toJson(bookInfoRule) - val chapterRule = TocRule( + ruleToc = TocRule( chapterList = toNewRule(jsonItem.readString("ruleChapterList")), chapterName = toNewRule(jsonItem.readString("ruleChapterName")), chapterUrl = toNewRule(jsonItem.readString("ruleContentUrl")), nextTocUrl = toNewRule(jsonItem.readString("ruleChapterUrlNext")) ) - ruleToc = GSON.toJson(chapterRule) var content = toNewRule(jsonItem.readString("ruleBookContent")) ?: "" if (content.startsWith("$") && !content.startsWith("$.")) { content = content.substring(1) } - val contentRule = ContentRule( + ruleContent = ContentRule( content = content, nextContentUrl = toNewRule(jsonItem.readString("ruleContentUrlNext")) ) - ruleContent = GSON.toJson(contentRule) + } + } else { + source.bookSourceUrl = sourceAny.bookSourceUrl + source.bookSourceName = sourceAny.bookSourceName + source.bookSourceGroup = sourceAny.bookSourceGroup + source.bookSourceType = sourceAny.bookSourceType + source.bookUrlPattern = sourceAny.bookUrlPattern + source.customOrder = sourceAny.customOrder + source.enabled = sourceAny.enabled + source.enabledExplore = sourceAny.enabledExplore + source.header = sourceAny.header + source.loginUrl = sourceAny.loginUrl + source.lastUpdateTime = sourceAny.lastUpdateTime + source.weight = sourceAny.weight + source.exploreUrl = sourceAny.exploreUrl + source.ruleExplore = if (sourceAny.ruleExplore is String) { + GSON.fromJsonObject(sourceAny.ruleExplore as? String) + } else { + GSON.fromJsonObject(GSON.toJson(sourceAny.ruleExplore)) + } + source.searchUrl = sourceAny.searchUrl + source.ruleSearch = if (sourceAny.ruleSearch is String) { + GSON.fromJsonObject(sourceAny.ruleSearch as? String) + } else { + GSON.fromJsonObject(GSON.toJson(sourceAny.ruleSearch)) + } + source.ruleBookInfo = if (sourceAny.ruleBookInfo is String) { + GSON.fromJsonObject(sourceAny.ruleBookInfo as? String) + } else { + GSON.fromJsonObject(GSON.toJson(sourceAny.ruleBookInfo)) + } + source.ruleToc = if (sourceAny.ruleToc is String) { + GSON.fromJsonObject(sourceAny.ruleToc as? String) + } else { + GSON.fromJsonObject(GSON.toJson(sourceAny.ruleToc)) + } + source.ruleContent = if (sourceAny.ruleContent is String) { + GSON.fromJsonObject(sourceAny.ruleContent as? String) + } else { + GSON.fromJsonObject(GSON.toJson(sourceAny.ruleContent)) } } + } catch (e: Exception) { + e.printStackTrace() } return source } + @Keep + data class BookSourceAny( + var bookSourceName: String = "", // 名称 + var bookSourceGroup: String? = null, // 分组 + var bookSourceUrl: String = "", // 地址,包括 http/https + var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频 + var bookUrlPattern: String? = null, // 详情页url正则 + var customOrder: Int = 0, // 手动排序编号 + var enabled: Boolean = true, // 是否启用 + var enabledExplore: Boolean = true, // 启用发现 + var header: String? = null, // 请求头 + var loginUrl: String? = null, // 登录地址 + var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 + var weight: Int = 0, // 智能排序的权重 + var exploreUrl: String? = null, // 发现url + var ruleExplore: Any? = null, // 发现规则 + var searchUrl: String? = null, // 搜索url + var ruleSearch: Any? = null, // 搜索规则 + var ruleBookInfo: Any? = null, // 书籍信息页规则 + var ruleToc: Any? = null, // 目录页规则 + var ruleContent: Any? = null // 正文页规则 + ) + // default规则适配 // #正则#替换内容 替换成 ##正则##替换内容 // | 替换成 || @@ -114,8 +176,8 @@ object OldRule { !newRule.startsWith("//") && !newRule.startsWith("##") && !newRule.startsWith(":") && - !newRule.contains("@js:",true) && - !newRule.contains("",true) + !newRule.contains("@js:", true) && + !newRule.contains("", true) ) { if (newRule.contains("#") && !newRule.contains("##")) { newRule = oldRule.replace("#", "##") @@ -142,10 +204,10 @@ object OldRule { } } if (allinone) { - newRule = "+" + newRule + newRule = "+$newRule" } if (reverse) { - newRule = "-" + newRule + newRule = "-$newRule" } return newRule } @@ -216,27 +278,4 @@ object OldRule { return GSON.toJson(map) } - fun jsonToReplaceRule(json: String): ReplaceRule? { - var replaceRule: ReplaceRule? = null - runCatching { - replaceRule = GSON.fromJsonObject(json.trim()) - } - runCatching { - if (replaceRule == null || replaceRule?.pattern.isNullOrBlank()) { - val jsonItem = jsonPath.parse(json.trim()) - val rule = ReplaceRule() - rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis() - rule.pattern = jsonItem.readString("$.regex") ?: "" - if (rule.pattern.isEmpty()) return null - rule.name = jsonItem.readString("$.replaceSummary") ?: "" - rule.replacement = jsonItem.readString("$.replacement") ?: "" - rule.isRegex = jsonItem.readBool("$.isRegex") == true - rule.scope = jsonItem.readString("$.useTo") - rule.isEnabled = jsonItem.readBool("$.enable") == true - rule.order = jsonItem.readInt("$.serialNumber") ?: 0 - return rule - } - } - return replaceRule - } } diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index f174a6f5e..c92f408bd 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -126,6 +126,7 @@ object Restore { hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) + autoReadSpeed = App.INSTANCE.getPrefInt(PreferKey.autoReadSpeed, 46) } ChapterProvider.upStyle() ReadBook.loadContent(resetPageOffset = false) diff --git a/app/src/main/java/io/legado/app/lib/theme/ATH.kt b/app/src/main/java/io/legado/app/lib/theme/ATH.kt index 19304862c..1b0bf723d 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ATH.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ATH.kt @@ -93,18 +93,13 @@ object ATH { } } - fun setLightNavigationBarAuto(activity: Activity, bgColor: Int) { - setLightNavigationBar(activity, ColorUtils.isColorLight(bgColor)) - } - - fun setNavigationBarColorAuto(activity: Activity) { - setNavigationBarColor(activity, ThemeStore.navigationBarColor(activity)) - } - - fun setNavigationBarColor(activity: Activity, color: Int) { + fun setNavigationBarColorAuto( + activity: Activity, + color: Int = ThemeStore.navigationBarColor(activity) + ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.window.navigationBarColor = color - setLightNavigationBarAuto(activity, color) + setLightNavigationBar(activity, ColorUtils.isColorLight(color)) } } diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt index 5b3997081..4259b6a7c 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt @@ -9,6 +9,7 @@ import androidx.annotation.CheckResult import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.core.content.ContextCompat +import io.legado.app.App import io.legado.app.R /** @@ -194,7 +195,7 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { @CheckResult @ColorInt - fun primaryColor(context: Context): Int { + fun primaryColor(context: Context = App.INSTANCE): Int { return prefs(context).getInt( ThemeStorePrefKeys.KEY_PRIMARY_COLOR, ATHUtils.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64")) @@ -212,7 +213,7 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { @CheckResult @ColorInt - fun accentColor(context: Context): Int { + fun accentColor(context: Context = App.INSTANCE): Int { return prefs(context).getInt( ThemeStorePrefKeys.KEY_ACCENT_COLOR, ATHUtils.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238")) @@ -236,7 +237,10 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { fun navigationBarColor(context: Context): Int { return if (!coloredNavigationBar(context)) { Color.BLACK - } else prefs(context).getInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, primaryColor(context)) + } else prefs(context).getInt( + ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, + bottomBackground(context) + ) } @CheckResult @@ -277,7 +281,7 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { @CheckResult @ColorInt - fun backgroundColor(context: Context): Int { + fun backgroundColor(context: Context = App.INSTANCE): Int { return prefs(context).getInt( ThemeStorePrefKeys.KEY_BACKGROUND_COLOR, ATHUtils.resolveColor(context, android.R.attr.colorBackground) @@ -294,7 +298,7 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { @CheckResult @ColorInt - fun bottomBackground(context: Context): Int { + fun bottomBackground(context: Context = App.INSTANCE): Int { return prefs(context).getInt( ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, ATHUtils.resolveColor(context, android.R.attr.colorBackground) diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt index 52f3b7560..0439713e8 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt @@ -169,7 +169,7 @@ class AnalyzeByXPath { if (rules.size == 1) { val jxNodes = jxNode?.sel(rule) ?: jxDocument?.selN(rule) jxNodes?.let { - return TextUtils.join(",", jxNodes) + return TextUtils.join("\n", jxNodes) } return null } else { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index ad96e781c..0e6031755 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -51,8 +51,8 @@ class AnalyzeUrl( private var queryStr: String? = null private val fieldMap = LinkedHashMap() private var charset: String? = null - private var bodyTxt: String? = null - private var body: RequestBody? = null + private var body: String? = null + private var requestBody: RequestBody? = null private var method = RequestMethod.GET init { @@ -157,15 +157,30 @@ class AnalyzeUrl( baseUrl = it } if (urlArray.size > 1) { - val options = GSON.fromJsonObject>(urlArray[1]) - options?.let { _ -> - options["method"]?.let { if (it.equals("POST", true)) method = RequestMethod.POST } - options["headers"]?.let { headers -> - GSON.fromJsonObject>(headers)?.let { headerMap.putAll(it) } + val option = GSON.fromJsonObject(urlArray[1]) + option?.let { _ -> + option.method?.let { if (it.equals("POST", true)) method = RequestMethod.POST } + option.headers?.let { headers -> + if (headers is Map<*, *>) { + @Suppress("unchecked_cast") + headerMap.putAll(headers as Map) + } + if (headers is String) { + GSON.fromJsonObject>(headers) + ?.let { headerMap.putAll(it) } + } + } + charset = option.charset + body = if (option.body is String) { + option.body + } else { + GSON.toJson(option.body) + } + option.webView?.let { + if (it.toString().isNotEmpty()) { + useWebView = true + } } - options["body"]?.let { bodyTxt = it } - options["charset"]?.let { charset = it } - options["webView"]?.let { if (it.isNotEmpty()) useWebView = true } } } when (method) { @@ -179,20 +194,19 @@ class AnalyzeUrl( } } RequestMethod.POST -> { - bodyTxt?.let { + body?.let { if (it.isJson()) { - body = RequestBody.create(jsonType, it) + requestBody = RequestBody.create(jsonType, it) } else { analyzeFields(it) } } ?: let { - body = FormBody.Builder().build() + requestBody = FormBody.Builder().build() } } } } - /** * 解析QueryMap */ @@ -253,7 +267,7 @@ class AnalyzeUrl( } else { HttpHelper .getApiService(baseUrl, charset) - .postBody(url, body!!, headerMap) + .postBody(url, requestBody!!, headerMap) } } fieldMap.isEmpty() -> HttpHelper @@ -277,7 +291,7 @@ class AnalyzeUrl( params.requestMethod = method params.javaScript = jsStr params.sourceRegex = sourceRegex - params.postData = bodyTxt?.toByteArray() + params.postData = body?.toByteArray() params.tag = tag return HttpHelper.ajax(params) } @@ -294,7 +308,7 @@ class AnalyzeUrl( } else { HttpHelper .getApiService(baseUrl, charset) - .postBodyAsync(url, body!!, headerMap) + .postBodyAsync(url, requestBody!!, headerMap) } } fieldMap.isEmpty() -> HttpHelper @@ -307,4 +321,12 @@ class AnalyzeUrl( return Res(NetworkUtils.getUrl(res), res.body()) } + data class UrlOption( + val method: String?, + val charset: String?, + val webView: Any?, + val headers: Any?, + val body: Any? + ) + } 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 494437d92..f9cfb846c 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 @@ -40,6 +40,7 @@ class AnalyzeTxtFile { book: Book, pattern: Pattern? ): ArrayList { + bookStream.seek(0) val toc = arrayListOf() var tocRule: TxtTocRule? = null val rulePattern = pattern ?: let { @@ -83,7 +84,8 @@ class AnalyzeTxtFile { //获取章节内容 val chapterContent = blockContent.substring(seekPos, chapterStart) val chapterLength = chapterContent.toByteArray(charset).size - if (chapterLength > 30000 && pattern == null) { + val lastStart = toc.lastOrNull()?.start ?: 0 + if (curOffset + chapterLength - lastStart > 50000 && pattern == null) { //移除不匹配的规则 tocRules.remove(tocRule) return analyze(bookStream, book, null) @@ -139,6 +141,11 @@ class AnalyzeTxtFile { //设置指针偏移 seekPos += chapterContent.length } + if (seekPos == 0 && length > 50000 && pattern == null) { + //移除不匹配的规则 + tocRules.remove(tocRule) + return analyze(bookStream, book, null) + } } else { //进行本地虚拟分章 //章节在buffer的偏移量 var chapterOffset = 0 @@ -271,18 +278,20 @@ class AnalyzeTxtFile { } private fun getTocRules(): List { - val rules = App.db.txtTocRule().all + val rules = App.db.txtTocRule().enabled if (rules.isEmpty()) { - return getDefaultRules() + return getDefaultEnabledRules() } return rules } - fun getDefaultRules(): List { + fun getDefaultEnabledRules(): List { App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> - GSON.fromJsonArray(String(byteArray))?.let { - App.db.txtTocRule().insert(*it.toTypedArray()) - return it + GSON.fromJsonArray(String(byteArray))?.let { txtTocRules -> + App.db.txtTocRule().insert(*txtTocRules.toTypedArray()) + return txtTocRules.filter { + it.enable + } } } return emptyList() diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index c5e7ffab5..9195c87a5 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -47,7 +47,7 @@ object LocalBook { fun deleteBook(book: Book, deleteOriginal: Boolean) { kotlin.runCatching { - if (book.isTxt()) { + if (book.isLocalTxt()) { val bookFile = FileUtils.getFile(AnalyzeTxtFile.cacheFolder, book.originName) bookFile.delete() } diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index 372a4eec6..162db6dd0 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -4,6 +4,7 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook +import io.legado.app.data.entities.rule.BookListRule import io.legado.app.help.BookHelp import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule @@ -47,7 +48,7 @@ object BookList { } val collections: List var reverse = false - val bookListRule = when { + val bookListRule: BookListRule = when { isSearch -> bookSource.getSearchRule() bookSource.getExploreRule().bookList.isNullOrBlank() -> bookSource.getSearchRule() else -> bookSource.getExploreRule() diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt index 986e7c4ed..fa56e85d0 100644 --- a/app/src/main/java/io/legado/app/service/AudioPlayService.kt +++ b/app/src/main/java/io/legado/app/service/AudioPlayService.kt @@ -63,6 +63,7 @@ class AudioPlayService : BaseService(), private val dsRunnable: Runnable = Runnable { doDs() } private var mpRunnable: Runnable = Runnable { upPlayProgress() } private var bookChapter: BookChapter? = null + private var playSpeed: Float = 1f override fun onCreate() { super.onCreate() @@ -95,7 +96,12 @@ class AudioPlayService : BaseService(), IntentAction.adjustSpeed -> upSpeed(intent.getFloatExtra("adjust", 1f)) IntentAction.addTimer -> addTimer() IntentAction.setTimer -> setTimer(intent.getIntExtra("minute", 0)) - IntentAction.adjustProgress -> adjustProgress(intent.getIntExtra("position", position)) + IntentAction.adjustProgress -> adjustProgress( + intent.getIntExtra( + "position", + position + ) + ) else -> stopSelf() } } @@ -140,14 +146,18 @@ class AudioPlayService : BaseService(), if (url.contains(".m3u8", false)) { stopSelf() } else { - AudioPlayService.pause = pause - handler.removeCallbacks(mpRunnable) - position = mediaPlayer.currentPosition - mediaPlayer.pause() - upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) - AudioPlay.status = Status.PAUSE - postEvent(EventBus.AUDIO_STATE, Status.PAUSE) - upNotification() + try { + AudioPlayService.pause = pause + handler.removeCallbacks(mpRunnable) + position = mediaPlayer.currentPosition + mediaPlayer.pause() + upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) + AudioPlay.status = Status.PAUSE + postEvent(EventBus.AUDIO_STATE, Status.PAUSE) + upNotification() + } catch (e: Exception) { + e.printStackTrace() + } } } @@ -174,12 +184,12 @@ class AudioPlayService : BaseService(), private fun upSpeed(adjust: Float) { kotlin.runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - with(mediaPlayer) { - if (isPlaying) { - playbackParams = playbackParams.apply { speed += adjust } - } - postEvent(EventBus.AUDIO_SPEED, playbackParams.speed) + playSpeed += adjust + if (mediaPlayer.isPlaying) { + mediaPlayer.playbackParams = + mediaPlayer.playbackParams.apply { speed = playSpeed } } + postEvent(EventBus.AUDIO_SPEED, playSpeed) } } } @@ -189,7 +199,11 @@ class AudioPlayService : BaseService(), */ override fun onPrepared(mp: MediaPlayer?) { if (pause) return - mediaPlayer.start() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mediaPlayer.playbackParams = mediaPlayer.playbackParams.apply { speed = playSpeed } + } else { + mediaPlayer.start() + } mediaPlayer.seekTo(position) postEvent(EventBus.AUDIO_SIZE, mediaPlayer.duration) bookChapter?.let { @@ -506,7 +520,6 @@ class AudioPlayService : BaseService(), ) builder.setStyle( androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(mediaSessionCompat?.sessionToken) .setShowActionsInCompactView(0, 1, 2) ) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index 1a4de9e6f..7531b21bb 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -44,7 +44,7 @@ abstract class BaseReadAloudService : BaseService(), private lateinit var audioManager: AudioManager private var mFocusRequest: AudioFocusRequest? = null private var broadcastReceiver: BroadcastReceiver? = null - private var mediaSessionCompat: MediaSessionCompat? = null + private lateinit var mediaSessionCompat: MediaSessionCompat private var title: String = "" private var subtitle: String = "" internal val contentList = arrayListOf() @@ -59,6 +59,7 @@ abstract class BaseReadAloudService : BaseService(), isRun = true audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager mFocusRequest = MediaHelp.getFocusRequest(this) + mediaSessionCompat = MediaSessionCompat(this, "readAloud") initMediaSession() initBroadcastReceiver() upNotification() @@ -72,7 +73,7 @@ abstract class BaseReadAloudService : BaseService(), unregisterReceiver(broadcastReceiver) postEvent(EventBus.ALOUD_STATE, Status.STOP) upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED) - mediaSessionCompat?.release() + mediaSessionCompat.release() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -109,11 +110,9 @@ abstract class BaseReadAloudService : BaseService(), readAloudNumber = textChapter.getReadLength(pageIndex) contentList.clear() if (getPrefBoolean(PreferKey.readAloudByPage)) { - for (index in pageIndex..textChapter.lastIndex()) { + for (index in pageIndex..textChapter.lastIndex) { textChapter.page(index)?.text?.split("\n")?.let { - if (it.isNotEmpty()) { - contentList.addAll(it) - } + contentList.addAll(it) } } } else { @@ -204,7 +203,7 @@ abstract class BaseReadAloudService : BaseService(), * 更新媒体状态 */ private fun upMediaSessionPlaybackState(state: Int) { - mediaSessionCompat?.setPlaybackState( + mediaSessionCompat.setPlaybackState( PlaybackStateCompat.Builder() .setActions(MediaHelp.MEDIA_SESSION_ACTIONS) .setState(state, nowSpeak.toLong(), 1f) @@ -216,13 +215,12 @@ abstract class BaseReadAloudService : BaseService(), * 初始化MediaSession, 注册多媒体按钮 */ private fun initMediaSession() { - mediaSessionCompat = MediaSessionCompat(this, "readAloud") - mediaSessionCompat?.setCallback(object : MediaSessionCompat.Callback() { + mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() { override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { return MediaButtonReceiver.handleIntent(this@BaseReadAloudService, mediaButtonEvent) } }) - mediaSessionCompat?.setMediaButtonReceiver( + mediaSessionCompat.setMediaButtonReceiver( PendingIntent.getBroadcast( this, 0, @@ -235,7 +233,7 @@ abstract class BaseReadAloudService : BaseService(), PendingIntent.FLAG_CANCEL_CURRENT ) ) - mediaSessionCompat?.isActive = true + mediaSessionCompat.isActive = true } /** @@ -325,7 +323,6 @@ abstract class BaseReadAloudService : BaseService(), ) builder.setStyle( androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(mediaSessionCompat?.sessionToken) .setShowActionsInCompactView(0, 1, 2) ) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) 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 8cbd80afd..e6cbb5966 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,18 @@ 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.CompositeCoroutine -import io.legado.app.model.WebBook +import io.legado.app.service.help.CheckSource 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 threadCount = AppConfig.threadCount private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() private var tasks = CompositeCoroutine() private val allIds = ArrayList() @@ -58,42 +56,37 @@ class CheckSourceService : BaseService() { checkedIds.clear() allIds.addAll(ids) processIndex = 0 + threadCount = min(allIds.size, threadCount) updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) - for (i in 0 until min(threadCount, allIds.size)) { + for (i in 0 until threadCount) { check() } } - + /** + * 检测 + */ private fun check() { + val index = processIndex synchronized(this) { processIndex++ } execute { - if (processIndex < allIds.size) { - val sourceUrl = allIds[processIndex] + if (index < allIds.size) { + val sourceUrl = allIds[index] App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> if (source.searchUrl.isNullOrEmpty()) { onNext(sourceUrl) } else { - check(source) + CheckSource(source).check(this, searchPool) { + onNext(it) + } } } ?: 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() @@ -102,7 +95,7 @@ class CheckSourceService : BaseService() { checkedIds.size, getString(R.string.progress_show, checkedIds.size, allIds.size) ) - if (processIndex >= allIds.size + min(threadCount, allIds.size) - 1) { + if (processIndex >= allIds.size + threadCount - 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 1cbfe44f4..bf6b90629 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -30,7 +30,7 @@ class DownloadService : BaseService() { private val handler = Handler() private var runnable: Runnable = Runnable { upDownload() } private val downloadMap = hashMapOf>() - private val downloadCount = hashMapOf(); + private val downloadCount = hashMapOf() private val finalMap = hashMapOf>() private var notificationContent = "正在启动下载" @@ -199,11 +199,11 @@ class DownloadService : BaseService() { var successCount = 0 //下载成功的条目数量 fun increaseSuccess() { - ++successCount; + ++successCount } fun increaseFinished() { - ++downloadFinishedCount; + ++downloadFinishedCount } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 7796ffce0..4c29f84ae 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -76,7 +76,9 @@ class HttpReadAloudService : BaseReadAloudService(), val file = getSpeakFile(index) file.writeBytes(bytes) if (index == nowSpeak) { - playAudio(FileInputStream(file).fd) + @Suppress("BlockingMethodInNonBlockingContext") + val fis = FileInputStream(file) + playAudio(fis.fd) } } } else { @@ -204,7 +206,7 @@ class HttpReadAloudService : BaseReadAloudService(), } else { nextChapter() } - }, 1000) + }, 100) return true } diff --git a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt index 1f63dac89..2d4b8bbde 100644 --- a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt @@ -1,7 +1,6 @@ package io.legado.app.service import android.app.PendingIntent -import android.os.Build import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import io.legado.app.R @@ -20,35 +19,45 @@ import java.util.* class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener { companion object { - var textToSpeech: TextToSpeech? = null + private var textToSpeech: TextToSpeech? = null + private var ttsInitFinish = false fun clearTTS() { - textToSpeech?.stop() - textToSpeech?.shutdown() + textToSpeech?.let { + it.stop() + it.shutdown() + } textToSpeech = null + ttsInitFinish = false } } - private var ttsInitFinish = false + private val ttsUtteranceListener = TTSUtteranceListener() override fun onCreate() { super.onCreate() - textToSpeech = TextToSpeech(this, this) + initTts() upSpeechRate() } + private fun initTts() { + ttsInitFinish = false + textToSpeech = TextToSpeech(this, this) + } + override fun onDestroy() { super.onDestroy() clearTTS() } - @Synchronized override fun onInit(status: Int) { if (status == TextToSpeech.SUCCESS) { - textToSpeech?.language = Locale.CHINA - textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener()) - ttsInitFinish = true - play() + textToSpeech?.let { + it.setOnUtteranceProgressListener(ttsUtteranceListener) + it.language = Locale.CHINA + ttsInitFinish = true + play() + } } else { launch { toast(R.string.tts_init_failed) @@ -58,35 +67,25 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener @Synchronized override fun play() { - if (contentList.isEmpty() || !ttsInitFinish) { - return - } - if (requestFocus()) { - MediaHelp.playSilentSound(this) + if (contentList.isNotEmpty() && ttsInitFinish && requestFocus()) { super.play() - for (i in nowSpeak until contentList.size) { - if (i == 0) { - speak(contentList[i], TextToSpeech.QUEUE_FLUSH, AppConst.APP_TAG + i) - } else { - speak(contentList[i], TextToSpeech.QUEUE_ADD, AppConst.APP_TAG + i) + execute { + MediaHelp.playSilentSound(this@TTSReadAloudService) + textToSpeech?.let { + it.speak("", TextToSpeech.QUEUE_FLUSH, null, null) + for (i in nowSpeak until contentList.size) { + it.speak( + contentList[i], + TextToSpeech.QUEUE_ADD, + null, + AppConst.APP_TAG + i + ) + } } } } } - private fun speak(content: String, queueMode: Int, utteranceId: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - textToSpeech?.speak(content, queueMode, null, utteranceId) - } else { - @Suppress("DEPRECATION") - textToSpeech?.speak( - content, - queueMode, - hashMapOf(Pair(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId)) - ) - } - } - /** * 更新朗读速度 */ @@ -94,7 +93,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener if (this.getPrefBoolean("ttsFollowSys", true)) { if (reset) { clearTTS() - textToSpeech = TextToSpeech(this, this) + initTts() } } else { textToSpeech?.setSpeechRate((AppConfig.ttsSpeechRate + 5) / 10f) @@ -152,8 +151,8 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener pageIndex++ ReadBook.moveToNextPage() } + postEvent(EventBus.TTS_PROGRESS, readAloudNumber + 1) } - postEvent(EventBus.TTS_PROGRESS, readAloudNumber + 1) } override fun onDone(s: String) { diff --git a/app/src/main/java/io/legado/app/service/help/CheckSource.kt b/app/src/main/java/io/legado/app/service/help/CheckSource.kt index 1f13723a2..5d8c6876b 100644 --- a/app/src/main/java/io/legado/app/service/help/CheckSource.kt +++ b/app/src/main/java/io/legado/app/service/help/CheckSource.kt @@ -2,34 +2,62 @@ package io.legado.app.service.help import android.content.Context import android.content.Intent +import io.legado.app.App import io.legado.app.R import io.legado.app.constant.IntentAction import io.legado.app.data.entities.BookSource +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.WebBook import io.legado.app.service.CheckSourceService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import org.jetbrains.anko.toast +import kotlin.coroutines.CoroutineContext -object CheckSource { +class CheckSource(val source: BookSource) { - fun start(context: Context, sources: LinkedHashSet) { - if (sources.isEmpty()) { - context.toast(R.string.non_select) - return - } - val selectedIds: ArrayList = arrayListOf() - sources.map { - selectedIds.add(it.bookSourceUrl) + companion object { + var keyword = "我的" + + fun start(context: Context, sources: List) { + if (sources.isEmpty()) { + context.toast(R.string.non_select) + return + } + val selectedIds: ArrayList = arrayListOf() + sources.map { + selectedIds.add(it.bookSourceUrl) + } + Intent(context, CheckSourceService::class.java).let { + it.action = IntentAction.start + it.putExtra("selectIds", selectedIds) + context.startService(it) + } } - Intent(context, CheckSourceService::class.java).let { - it.action = IntentAction.start - it.putExtra("selectIds", selectedIds) - context.startService(it) + + fun stop(context: Context) { + Intent(context, CheckSourceService::class.java).let { + it.action = IntentAction.stop + context.startService(it) + } } } - fun stop(context: Context) { - Intent(context, CheckSourceService::class.java).let { - it.action = IntentAction.stop - context.startService(it) - } + fun check( + scope: CoroutineScope, + context: CoroutineContext, + onNext: (sourceUrl: String) -> Unit + ): Coroutine<*> { + val webBook = WebBook(source) + return webBook.searchBook(keyword, scope = scope, context = context) + .onError(Dispatchers.IO) { + source.addGroup("失效") + App.db.bookSourceDao().update(source) + }.onSuccess(Dispatchers.IO) { + source.removeGroup("失效") + App.db.bookSourceDao().update(source) + }.onFinally(Dispatchers.IO) { + onNext(source.bookSourceUrl) + } } } \ 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 89f730ae5..ef15098b0 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 @@ -1,11 +1,13 @@ package io.legado.app.service.help import androidx.lifecycle.MutableLiveData +import com.hankcs.hanlp.HanLP import io.legado.app.App import io.legado.app.R import io.legado.app.constant.BookType import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.IntentDataHelp import io.legado.app.help.coroutine.Coroutine @@ -36,9 +38,8 @@ object ReadBook { var msg: String? = null private val loadingChapters = arrayListOf() - fun resetData(book: Book, noSource: (name: String, author: String) -> Unit) { + fun resetData(book: Book) { this.book = book - titleDate.postValue(book.name) durChapterIndex = book.durChapterIndex durPageIndex = book.durChapterPos isLocalBook = book.origin == BookType.local @@ -46,20 +47,19 @@ object ReadBook { prevTextChapter = null curTextChapter = null nextTextChapter = null - upWebBook(book, noSource) + titleDate.postValue(book.name) + upWebBook(book) } - fun upWebBook(book: Book?, noSource: (name: String, author: String) -> Unit) { - book ?: return - if (book.origin == BookType.local) { - webBook = null + fun upWebBook(book: Book) { + webBook = if (book.origin == BookType.local) { + null } else { val bookSource = App.db.bookSourceDao().getBookSource(book.origin) if (bookSource != null) { - webBook = WebBook(bookSource) + WebBook(bookSource) } else { - webBook = null - noSource.invoke(book.name, book.author) + null } } } @@ -107,7 +107,7 @@ object ReadBook { fun moveToPrevChapter(upContent: Boolean, toLast: Boolean = true): Boolean { if (durChapterIndex > 0) { - durPageIndex = if (toLast) prevTextChapter?.lastIndex() ?: 0 else 0 + durPageIndex = if (toLast) prevTextChapter?.lastIndex ?: 0 else 0 durChapterIndex-- nextTextChapter = curTextChapter curTextChapter = prevTextChapter @@ -149,7 +149,7 @@ object ReadBook { } private fun curPageChanged() { - callBack?.upPageProgress() + callBack?.pageChanged() if (BaseReadAloudService.isRun) { readAloud(!BaseReadAloudService.pause) } @@ -176,10 +176,10 @@ object ReadBook { fun durChapterPos(): Int { curTextChapter?.let { - if (durPageIndex < it.pageSize()) { + if (durPageIndex < it.pageSize) { return durPageIndex } - return it.pageSize() - 1 + return it.pageSize - 1 } return durPageIndex } @@ -293,7 +293,12 @@ object ReadBook { ) { Coroutine.async { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { - val c = BookHelp.disposeContent( + chapter.title = when (AppConfig.chineseConverterType) { + 1 -> HanLP.convertToSimplifiedChinese(chapter.title) + 2 -> HanLP.convertToTraditionalChinese(chapter.title) + else -> chapter.title + } + val contents = BookHelp.disposeContent( chapter.title, book!!.name, webBook?.bookSource?.bookSourceUrl, @@ -302,18 +307,21 @@ object ReadBook { ) when (chapter.index) { durChapterIndex -> { - curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + curTextChapter = + ChapterProvider.getTextChapter(chapter, contents, chapterSize) if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset) callBack?.upView() curPageChanged() callBack?.contentLoadFinish() } durChapterIndex - 1 -> { - prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + prevTextChapter = + ChapterProvider.getTextChapter(chapter, contents, chapterSize) if (upContent) callBack?.upContent(-1, resetPageOffset) } durChapterIndex + 1 -> { - nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + nextTextChapter = + ChapterProvider.getTextChapter(chapter, contents, chapterSize) if (upContent) callBack?.upContent(1, resetPageOffset) } } @@ -342,7 +350,7 @@ object ReadBook { interface CallBack { fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) fun upView() - fun upPageProgress() + fun pageChanged() fun contentLoadFinish() } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index d86cb39a1..ca71714f1 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -24,14 +24,19 @@ class AboutActivity : BaseActivity(R.layout.activity_about) { .replace(R.id.fl_fragment, aboutFragment, fTag) .commit() tv_app_summary.post { - val span = ForegroundColorSpan(accentColor) - val spannableString = SpannableString(tv_app_summary.text) - val start = spannableString.indexOf("开源阅读软件") - spannableString.setSpan( - span, start, start + 6, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - tv_app_summary.text = spannableString + try { + val span = ForegroundColorSpan(accentColor) + val spannableString = SpannableString(tv_app_summary.text) + val gzh = getString(R.string.legado_gzh) + val start = spannableString.indexOf(gzh) + spannableString.setSpan( + span, start, start + gzh.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + tv_app_summary.text = spannableString + } catch (e: Exception) { + e.printStackTrace() + } } } 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 3b56737cf..81ed18e32 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 @@ -47,12 +47,13 @@ class AboutFragment : PreferenceFragmentCompat() { "update_log" -> showUpdateLog() "check_update" -> openUrl(R.string.latest_release_url) "mail" -> sendMail() + "sourceRuleSummary" -> openUrl(R.string.source_rule_url) "git" -> openUrl(R.string.this_github_url) "home_page" -> openUrl(R.string.home_page_url) "license" -> requireContext().openUrl(licenseUrl) "disclaimer" -> requireContext().openUrl(disclaimerUrl) "qq" -> showQqGroups() - "gzGzh" -> requireContext().sendToClip("开源阅读软件") + "gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh)) } return super.onPreferenceTreeClick(preference) } diff --git a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt index f82fa1b64..b716c8f05 100644 --- a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt @@ -35,7 +35,7 @@ class DonateFragment : PreferenceFragmentCompat() { "zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl) "qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl) "zfbHbSsm" -> getZfbHb(requireContext()) - "gzGzh" -> requireContext().sendToClip("开源阅读软件") + "gzGzh" -> requireContext().sendToClip("开源阅读") } return super.onPreferenceTreeClick(preference) } diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt index 132858e0f..12e63a907 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt @@ -2,6 +2,8 @@ package io.legado.app.ui.book.changesource import android.content.Context import android.os.Bundle +import android.view.View +import androidx.appcompat.widget.PopupMenu import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter @@ -10,6 +12,7 @@ import io.legado.app.utils.invisible import io.legado.app.utils.visible import kotlinx.android.synthetic.main.item_change_source.view.* import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.sdk27.listeners.onLongClick class ChangeSourceAdapter(context: Context, val callBack: CallBack) : @@ -43,10 +46,30 @@ class ChangeSourceAdapter(context: Context, val callBack: CallBack) : callBack.changeTo(it) } } + holder.itemView.onLongClick { + showMenu(holder.itemView, getItem(holder.layoutPosition)) + true + } + } + + private fun showMenu(view: View, searchBook: SearchBook?) { + searchBook ?: return + val popupMenu = PopupMenu(context, view) + popupMenu.inflate(R.menu.change_source_item) + popupMenu.setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_disable_book_source -> { + callBack.disableSource(searchBook) + } + } + true + } + popupMenu.show() } interface CallBack { val bookUrl: String? fun changeTo(searchBook: SearchBook) + fun disableSource(searchBook: SearchBook) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt index bb60c19d4..09fa066ec 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt @@ -185,6 +185,10 @@ class ChangeSourceDialog : BaseDialogFragment(), override val bookUrl: String? get() = callBack?.oldBook?.bookUrl + override fun disableSource(searchBook: SearchBook) { + viewModel.disableSource(searchBook) + } + interface CallBack { val oldBook: Book? fun changeTo(book: Book) diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index b375aed11..da58200c5 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.book.changesource import android.app.Application import android.os.Bundle +import android.os.Handler import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.R @@ -22,6 +23,7 @@ import java.util.concurrent.Executors class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { private var searchPool = Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + val handler = Handler() val searchStateData = MutableLiveData() val searchBooksLiveData = MutableLiveData>() var name: String = "" @@ -29,6 +31,9 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio private var task: Coroutine<*>? = null private var screenKey: String = "" private val searchBooks = hashSetOf() + private var postTime = 0L + private val sendRunnable = Runnable { upAdapter() } + fun initData(arguments: Bundle?) { arguments?.let { bundle -> @@ -55,9 +60,17 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } + @Synchronized private fun upAdapter() { - val books = searchBooks.toList() - searchBooksLiveData.postValue(books.sortedBy { it.originOrder }) + if (System.currentTimeMillis() >= postTime + 500) { + handler.removeCallbacks(sendRunnable) + postTime = System.currentTimeMillis() + val books = searchBooks.toList() + searchBooksLiveData.postValue(books.sortedBy { it.originOrder }) + } else { + handler.removeCallbacks(sendRunnable) + handler.postDelayed(sendRunnable, 500 - System.currentTimeMillis() + postTime) + } } private fun searchFinish(searchBook: SearchBook) { @@ -165,4 +178,15 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio searchPool.close() } + fun disableSource(searchBook: SearchBook) { + execute { + App.db.bookSourceDao().getBookSource(searchBook.origin)?.let { source -> + source.enabled = false + App.db.bookSourceDao().update(source) + } + searchBooks.remove(searchBook) + upAdapter() + } + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt index b0a8f19ef..142ab24c2 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt @@ -24,13 +24,11 @@ class DiffCallBack(private val oldItems: List, private val newItems: override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - if (oldItem.originName != newItem.originName) { - return false - } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { - return false + return when { + oldItem.originName != newItem.originName -> false + oldItem.latestChapterTitle != newItem.latestChapterTitle -> false + else -> true } - return true } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { diff --git a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt index b0f860527..0097c33ac 100644 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt @@ -12,10 +12,12 @@ import com.google.android.material.snackbar.Snackbar 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.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter +import io.legado.app.data.entities.BookGroup import io.legado.app.help.BookHelp import io.legado.app.service.help.Download import io.legado.app.ui.filechooser.FileChooserDialog @@ -35,29 +37,49 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow private val exportRequestCode = 32 private val exportBookPathKey = "exportBookPath" lateinit var adapter: DownloadAdapter - private var bookshelfLiveData: LiveData>? = null + private var groupLiveData: LiveData>? = null + private var booksLiveData: LiveData>? = null private var menu: Menu? = null private var exportPosition = -1 + private val groupList: ArrayList = arrayListOf() + private var groupId: Int = -1 override val viewModel: DownloadViewModel get() = getViewModel(DownloadViewModel::class.java) override fun onActivityCreated(savedInstanceState: Bundle?) { + groupId = intent.getIntExtra("groupId", -1) + title_bar.subtitle = intent.getStringExtra("groupName") ?: getString(R.string.all) initRecyclerView() - initLiveData() + initGroupData() + initBookData() } override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.download, menu) - this.menu = menu return super.onCompatCreateOptionsMenu(menu) } + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + this.menu = menu + upMenu() + return super.onPrepareOptionsMenu(menu) + } + + private fun upMenu() { + menu?.findItem(R.id.menu_book_group)?.subMenu?.let { subMenu -> + subMenu.removeGroup(R.id.menu_group) + groupList.forEach { bookGroup -> + subMenu.add(R.id.menu_group, bookGroup.groupId, Menu.NONE, bookGroup.groupName) + } + } + } + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_download -> launch(IO) { if (adapter.downloadMap.isNullOrEmpty()) { - App.db.bookDao().webBooks.forEach { book -> + adapter.getItems().forEach { book -> Download.start( this@DownloadActivity, book.bookUrl, @@ -69,6 +91,21 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow Download.stop(this@DownloadActivity) } } + R.id.menu_no_group -> { + title_bar.subtitle = getString(R.string.no_group) + groupId = AppConst.bookGroupNone.groupId + initBookData() + } + R.id.menu_all -> { + title_bar.subtitle = item.title + groupId = AppConst.bookGroupAll.groupId + initBookData() + } + else -> if (item.groupId == R.id.menu_group) { + title_bar.subtitle = item.title + groupId = item.itemId + initBookData() + } } return super.onCompatOptionsItemSelected(item) } @@ -79,21 +116,39 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow recycler_view.adapter = adapter } - private fun initLiveData() { - bookshelfLiveData?.removeObservers(this) - bookshelfLiveData = App.db.bookDao().observeDownload() - bookshelfLiveData?.observe(this, Observer { list -> + private fun initBookData() { + booksLiveData?.removeObservers(this) + booksLiveData = when (groupId) { + AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll() + AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() + else -> App.db.bookDao().observeByGroup(groupId) + } + booksLiveData?.observe(this, Observer { list -> + val booksDownload = list.filter { + it.isOnLineTxt() + } val books = when (getPrefInt(PreferKey.bookshelfSort)) { - 1 -> list.sortedByDescending { it.latestChapterTime } - 2 -> list.sortedBy { it.name } - 3 -> list.sortedBy { it.order } - else -> list.sortedByDescending { it.durChapterTime } + 1 -> booksDownload.sortedByDescending { it.latestChapterTime } + 2 -> booksDownload.sortedBy { it.name } + 3 -> booksDownload.sortedBy { it.order } + else -> booksDownload.sortedByDescending { it.durChapterTime } } adapter.setItems(books) initCacheSize(books) }) } + private fun initGroupData() { + groupLiveData?.removeObservers(this) + groupLiveData = App.db.bookGroupDao().liveDataAll() + groupLiveData?.observe(this, Observer { + groupList.clear() + groupList.addAll(it) + adapter.notifyDataSetChanged() + upMenu() + }) + } + private fun initCacheSize(books: List) { launch(IO) { books.forEach { book -> diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt index 04bf86188..dbba6b63e 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt @@ -72,7 +72,8 @@ class ExploreShowActivity : VMBaseActivity(R.layout.activi override fun showBookInfo(book: Book) { startActivity( - Pair("bookUrl", book.bookUrl) + Pair("name", book.name), + Pair("author", book.author) ) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index e0b6f275a..0e887fad4 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -86,6 +86,9 @@ class BookInfoActivity : R.id.menu_refresh -> { upLoading(true) viewModel.bookData.value?.let { + if (it.isLocalBook()) { + it.tocUrl = "" + } viewModel.loadBookInfo(it) } } 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 47dc6c33f..53b7366b9 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 @@ -12,6 +12,7 @@ import io.legado.app.help.BookHelp import io.legado.app.model.WebBook import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.model.localBook.LocalBook +import io.legado.app.service.help.ReadBook import kotlinx.coroutines.Dispatchers.IO class BookInfoViewModel(application: Application) : BaseViewModel(application) { @@ -22,13 +23,13 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { fun initData(intent: Intent) { execute { - intent.getStringExtra("bookUrl")?.let { - App.db.bookDao().getBook(it)?.let { book -> - inBookshelf = true - setBook(book) - } ?: App.db.searchBookDao().getSearchBook(it)?.toBook()?.let { book -> - setBook(book) - } + val name = intent.getStringExtra("name") ?: "" + val author = intent.getStringExtra("author") ?: "" + App.db.bookDao().getBook(name, author)?.let { book -> + inBookshelf = true + setBook(book) + } ?: App.db.searchBookDao().getFirstByNameAuthor(name, author)?.toBook()?.let { book -> + setBook(book) } } } @@ -178,6 +179,9 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { book.durChapterTitle = it.durChapterTitle } App.db.bookDao().insert(book) + if (ReadBook.book?.name == book.name && ReadBook.book?.author == book.author) { + ReadBook.book = book + } } }.onSuccess { success?.invoke() diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt index 969b1ee45..70b945efe 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.book.info.edit import android.app.Activity +import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -16,6 +17,9 @@ import org.jetbrains.anko.sdk27.listeners.onClick class BookInfoEditActivity : VMBaseActivity(R.layout.activity_book_info_edit), ChangeCoverDialog.CallBack { + + private val resultSelectCover = 132 + override val viewModel: BookInfoEditViewModel get() = getViewModel(BookInfoEditViewModel::class.java) @@ -47,6 +51,13 @@ class BookInfoEditActivity : ChangeCoverDialog.show(supportFragmentManager, it.name, it.author) } } + tv_select_cover.onClick { + selectImage() + } + tv_refresh_cover.onClick { + viewModel.book?.customCoverUrl = tie_cover_url.text?.toString() + upCover() + } } private fun upView(book: Book) { @@ -77,9 +88,29 @@ class BookInfoEditActivity : } } + private fun selectImage() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" + startActivityForResult(intent, resultSelectCover) + } + override fun coverChangeTo(coverUrl: String) { viewModel.book?.customCoverUrl = coverUrl tie_cover_url.setText(coverUrl) upCover() } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + resultSelectCover -> { + if (resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + coverChangeTo(uri.toString()) + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/Help.kt b/app/src/main/java/io/legado/app/ui/book/read/Help.kt index 8d87e89ed..853a070da 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/Help.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/Help.kt @@ -63,7 +63,9 @@ object Help { } } - + /** + * 屏幕方向 + */ @SuppressLint("SourceLockedOrientationActivity") fun setOrientation(activity: Activity) = activity.apply { when (AppConfig.requestedDirection) { 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 43d03c332..d778b999c 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 @@ -4,6 +4,8 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.res.Configuration +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.Handler @@ -30,6 +32,7 @@ import io.legado.app.help.storage.SyncBookProgress import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton +import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.receiver.TimeBatteryReceiver import io.legado.app.service.BaseReadAloudService @@ -69,6 +72,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo ReadAloudDialog.CallBack, ChangeSourceDialog.CallBack, ReadBook.CallBack, + AutoReadDialog.CallBack, TocRegexDialog.CallBack, ReplaceEditDialog.CallBack, ColorPickerDialogListener { @@ -85,7 +89,9 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo private val mHandler = Handler() private val keepScreenRunnable: Runnable = Runnable { Help.keepScreenOn(window, false) } - + private val autoPageRunnable: Runnable = Runnable { autoPagePlus() } + override var autoPageProgress = 0 + override var isAutoPage = false private var screenTimeOut: Long = 0 private var timeBatteryReceiver: TimeBatteryReceiver? = null override val pageFactory: TextPageFactory get() = page_view.pageFactory @@ -129,6 +135,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onPause() { super.onPause() + ReadBook.saveRead() timeBatteryReceiver?.let { unregisterReceiver(it) timeBatteryReceiver = null @@ -140,6 +147,21 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } + override fun upNavigationBarColor() { + when { + read_menu == null -> return + read_menu.isVisible -> { + ATH.setNavigationBarColorAuto(this) + } + ReadBookConfig.bg is ColorDrawable -> { + ATH.setNavigationBarColorAuto(this, ReadBookConfig.bgMeanColor) + } + else -> { + ATH.setNavigationBarColorAuto(this, Color.BLACK) + } + } + } + /** * 初始化View */ @@ -194,7 +216,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo when (item.groupId) { R.id.menu_group_on_line -> item.isVisible = onLine R.id.menu_group_local -> item.isVisible = !onLine - R.id.menu_group_text -> item.isVisible = book.isTxt() + R.id.menu_group_text -> item.isVisible = book.isLocalTxt() R.id.menu_group_login -> item.isVisible = !ReadBook.webBook?.bookSource?.loginUrl.isNullOrEmpty() else -> if (item.itemId == R.id.menu_enable_replace) { @@ -238,7 +260,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo onReplaceRuleSave() } R.id.menu_book_info -> ReadBook.book?.let { - startActivity(Pair("bookUrl", it.bookUrl)) + startActivity( + Pair("name", it.name), + Pair("author", it.author) + ) } R.id.menu_toc_regex -> TocRegexDialog.show( supportFragmentManager, @@ -280,6 +305,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo */ override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { when (keyCode) { + getPrefInt(PreferKey.prevKey) -> { + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV) + return true + } + } + getPrefInt(PreferKey.nextKey) -> { + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT) + return true + } + } KeyEvent.KEYCODE_VOLUME_UP -> { if (volumeKeyPage(PageDelegate.Direction.PREV)) { return true @@ -294,18 +331,6 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT) return true } - getPrefInt(PreferKey.prevKey) -> { - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV) - return true - } - } - getPrefInt(PreferKey.nextKey) -> { - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT) - return true - } - } } return super.onKeyDown(keyCode, event) } @@ -344,6 +369,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo toast(R.string.read_aloud_pause) return true } + if (isAutoPage) { + autoPageStop() + return true + } } } } @@ -466,6 +495,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo if (getPrefBoolean("volumeKeyPageOnPlay") || BaseReadAloudService.pause ) { + page_view.pageDelegate?.isCancel = false page_view.pageDelegate?.keyTurnPage(direction) return true } @@ -507,18 +537,22 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } else { tv_chapter_url.gone() } - seek_read_page.max = it.pageSize().minus(1) + seek_read_page.max = it.pageSize.minus(1) seek_read_page.progress = ReadBook.durPageIndex tv_pre.isEnabled = ReadBook.durChapterIndex != 0 tv_next.isEnabled = ReadBook.durChapterIndex != ReadBook.chapterSize - 1 + } ?: let { + tv_chapter_name.gone() + tv_chapter_url.gone() } } } /** - * 更新进度条 + * 页面改变 */ - override fun upPageProgress() { + override fun pageChanged() { + autoPageProgress = 0 launch { seek_read_page.progress = ReadBook.durPageIndex } @@ -539,10 +573,16 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } override fun clickCenter() { - if (BaseReadAloudService.isRun) { - showReadAloudDialog() - } else { - read_menu.runMenuIn() + when { + BaseReadAloudService.isRun -> { + showReadAloudDialog() + } + isAutoPage -> { + AutoReadDialog().show(supportFragmentManager, "autoRead") + } + else -> { + read_menu.runMenuIn() + } } } @@ -557,7 +597,33 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo * 自动翻页 */ override fun autoPage() { + if (isAutoPage) { + autoPageStop() + } else { + isAutoPage = true + page_view.upContent() + page_view.upContent(1) + autoPagePlus() + } + read_menu.setAutoPage(isAutoPage) + } + + override fun autoPageStop() { + isAutoPage = false + mHandler.removeCallbacks(autoPageRunnable) + page_view.upContent() + } + private fun autoPagePlus() { + mHandler.removeCallbacks(autoPageRunnable) + autoPageProgress++ + if (autoPageProgress >= ReadBookConfig.autoReadSpeed * 10) { + autoPageProgress = 0 + page_view.fillPage(PageDelegate.Direction.NEXT) + } else { + page_view.invalidate() + } + mHandler.postDelayed(autoPageRunnable, 100) } /** @@ -608,6 +674,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo */ override fun upSystemUiVisibility() { Help.upSystemUiVisibility(this, !read_menu.isVisible) + upNavigationBarColor() } /** 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 cc129056a..e6d6b57e3 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 @@ -7,6 +7,7 @@ import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.IntentDataHelp import io.legado.app.model.WebBook @@ -34,13 +35,19 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } ?: App.db.bookDao().lastReadBook?.let { initBook(it) } + }.onFinally { + if (ReadBook.inBookshelf) { + ReadBook.saveRead() + } } } private fun initBook(book: Book) { if (ReadBook.book?.bookUrl != book.bookUrl) { - ReadBook.resetData(book) { name, author -> - autoChangeSource(name, author) + ReadBook.resetData(book) + if (!book.isLocalBook() && ReadBook.webBook == null) { + autoChangeSource(book.name, book.author) + return } isInitFinish = true ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) @@ -56,14 +63,14 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } ReadBook.loadContent(resetPageOffset = true) } - if (ReadBook.inBookshelf) { - ReadBook.saveRead() - } } else { isInitFinish = true + ReadBook.book!!.group = book.group ReadBook.titleDate.postValue(book.name) - ReadBook.upWebBook(book) { name, author -> - autoChangeSource(name, author) + ReadBook.upWebBook(book) + if (!book.isLocalBook() && ReadBook.webBook == null) { + autoChangeSource(book.name, book.author) + return } ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) if (ReadBook.chapterSize == 0) { @@ -163,6 +170,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } private fun autoChangeSource(name: String, author: String) { + if (!AppConfig.autoChangeSource) return execute { App.db.bookSourceDao().allTextEnabled.forEach { source -> try { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index 254d037ad..65befde89 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -4,12 +4,12 @@ import android.content.Context import android.util.AttributeSet import android.view.WindowManager import android.view.animation.Animation -import android.view.animation.AnimationUtils import android.widget.FrameLayout import android.widget.SeekBar import androidx.core.view.isVisible import io.legado.app.App import io.legado.app.R +import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.accentColor @@ -140,7 +140,11 @@ class ReadMenu : FrameLayout { }) //自动翻页 - fabAutoPage.onClick { callBack?.autoPage() } + fabAutoPage.onClick { + runMenuOut { + callBack?.autoPage() + } + } //替换 fabReplaceRule.onClick { callBack?.openReplaceRule() } @@ -149,6 +153,7 @@ class ReadMenu : FrameLayout { fabNightTheme.onClick { AppConfig.isNightTheme = !AppConfig.isNightTheme App.INSTANCE.applyDayNight() + postEvent(EventBus.RECREATE, "") } //上一章 @@ -190,8 +195,8 @@ class ReadMenu : FrameLayout { } private fun initAnimation() { - menuTopIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_in) - menuBottomIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_in) + menuTopIn = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_top_in) + menuBottomIn = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_bottom_in) menuTopIn.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationStart(animation: Animation) { callBack?.upSystemUiVisibility() @@ -213,8 +218,8 @@ class ReadMenu : FrameLayout { }) //隐藏菜单 - menuTopOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_out) - menuBottomOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_out) + menuTopOut = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_top_out) + menuBottomOut = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_bottom_out) menuTopOut.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationStart(animation: Animation) { vw_menu_bg.setOnClickListener(null) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt new file mode 100644 index 000000000..d465c7b7f --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt @@ -0,0 +1,100 @@ +package io.legado.app.ui.book.read.config + +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.SeekBar +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.service.BaseReadAloudService +import io.legado.app.service.help.ReadAloud +import io.legado.app.ui.book.read.Help +import kotlinx.android.synthetic.main.dialog_auto_read.* +import org.jetbrains.anko.sdk27.listeners.onClick + +class AutoReadDialog : BaseDialogFragment() { + var callBack: CallBack? = null + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.let { + Help.upSystemUiVisibility(it) + it.windowManager?.defaultDisplay?.getMetrics(dm) + } + dialog?.window?.let { + it.setBackgroundDrawableResource(R.color.background) + it.decorView.setPadding(0, 0, 0, 0) + val attr = it.attributes + attr.dimAmount = 0.0f + attr.gravity = Gravity.BOTTOM + it.attributes = attr + it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + callBack = activity as? CallBack + return inflater.inflate(R.layout.dialog_auto_read, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + root_view.setBackgroundColor(requireContext().bottomBackground) + initOnChange() + initData() + initEvent() + } + + private fun initData() { + seek_auto_read.progress = ReadBookConfig.autoReadSpeed + } + + private fun initOnChange() { + seek_auto_read.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + ReadBookConfig.autoReadSpeed = seek_auto_read.progress + upTtsSpeechRate() + } + }) + } + + private fun initEvent() { + ll_main_menu.onClick { callBack?.showMenuBar(); dismiss() } + ll_setting.onClick { + ReadAloudConfigDialog().show(childFragmentManager, "readAloudConfigDialog") + } + ll_catalog.onClick { callBack?.openChapterList() } + ll_auto_page_stop.onClick { + callBack?.autoPageStop() + dismiss() + } + } + + private fun upTtsSpeechRate() { + ReadAloud.upTtsSpeechRate(requireContext()) + if (!BaseReadAloudService.pause) { + ReadAloud.pause(requireContext()) + ReadAloud.resume(requireContext()) + } + } + + interface CallBack { + fun showMenuBar() + fun openChapterList() + fun autoPageStop() + } +} \ No newline at end of file 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 8672d04bb..7033b82d0 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 @@ -18,6 +18,7 @@ import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.bottomBackground import io.legado.app.ui.book.read.Help +import io.legado.app.utils.dp import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.postEvent @@ -38,7 +39,7 @@ class MoreConfigDialog : DialogFragment() { attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM it.attributes = attr - it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 360.dp) } } @@ -110,6 +111,9 @@ class MoreConfigDialog : DialogFragment() { Help.setOrientation(it) } } + PreferKey.textFullJustify -> { + postEvent(EventBus.UP_CONFIG, true) + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt index 28a46b7f2..d85aae294 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt @@ -8,6 +8,7 @@ import io.legado.app.constant.PreferKey import io.legado.app.utils.getPrefInt import io.legado.app.utils.hideSoftInput import io.legado.app.utils.putPrefInt +import io.legado.app.utils.removePref import kotlinx.android.synthetic.main.dialog_page_key.* import org.jetbrains.anko.sdk27.listeners.onClick @@ -19,11 +20,17 @@ class PageKeyDialog(context: Context) : Dialog(context, R.style.AppTheme_AlertDi et_prev.setText(context.getPrefInt(PreferKey.prevKey).toString()) et_next.setText(context.getPrefInt(PreferKey.nextKey).toString()) tv_ok.onClick { - et_prev.text?.let { - context.putPrefInt(PreferKey.prevKey, it.toString().toInt()) + val prevKey = et_prev.text?.toString() + if (prevKey.isNullOrEmpty()) { + context.removePref(PreferKey.prevKey) + } else { + context.putPrefInt(PreferKey.prevKey, prevKey.toInt()) } - et_next.text?.let { - context.putPrefInt(PreferKey.nextKey, it.toString().toInt()) + val nextKey = et_next.text?.toString() + if (nextKey.isNullOrEmpty()) { + context.removePref(PreferKey.nextKey) + } else { + context.putPrefInt(PreferKey.nextKey, nextKey.toInt()) } dismiss() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt index 294a1c127..cdb10159e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt @@ -16,6 +16,7 @@ import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.backgroundColor import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.ui.book.read.Help @@ -42,8 +43,8 @@ class ReadAloudConfigDialog : DialogFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val view = LinearLayout(context) - view.setBackgroundResource(R.color.background) + val view = LinearLayout(requireContext()) + view.setBackgroundColor(requireContext().backgroundColor) view.id = R.id.tag1 container?.addView(view) return view 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 19cad6738..38e96af50 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 @@ -103,9 +103,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { tv_title_mode.onClick { showTitleConfig() } - tv_text_bold.onClick { - ReadBookConfig.textBold = !ReadBookConfig.textBold - tv_text_bold.isSelected = ReadBookConfig.textBold + text_font_weight_converter.onChanged { postEvent(EventBus.UP_CONFIG, true) } tv_text_font.onClick { @@ -215,7 +213,6 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { private fun upStyle() { ReadBookConfig.let { - tv_text_bold.isSelected = it.textBold dsb_text_size.progress = it.textSize - 5 dsb_text_letter_spacing.progress = (it.letterSpacing * 100).toInt() + 50 dsb_line_size.progress = it.lineSpacingExtra diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TextFontWeightConverter.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TextFontWeightConverter.kt new file mode 100644 index 000000000..12f5243fa --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TextFontWeightConverter.kt @@ -0,0 +1,55 @@ +package io.legado.app.ui.book.read.config + +import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.util.AttributeSet +import io.legado.app.R +import io.legado.app.help.AppConfig +import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.theme.accentColor +import io.legado.app.ui.widget.text.StrokeTextView +import org.jetbrains.anko.sdk27.listeners.onClick + +class TextFontWeightConverter(context: Context, attrs: AttributeSet?) : StrokeTextView(context, attrs) { + + private val spannableString = SpannableString("中/粗/细") + private var enabledSpan: ForegroundColorSpan = ForegroundColorSpan(context.accentColor) + private var onChanged: (() -> Unit)? = null + + init { + text = spannableString + if (!isInEditMode) { + upUi(ReadBookConfig.textBold) + } + onClick { + selectType() + } + } + + private fun upUi(type: Int) { + spannableString.removeSpan(enabledSpan) + when (type) { + 0 -> spannableString.setSpan(enabledSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + 1 -> spannableString.setSpan(enabledSpan, 2, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + 2 -> spannableString.setSpan(enabledSpan, 4, 5, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + } + text = spannableString + } + + private fun selectType() { + context.alert(titleResource = R.string.text_font_weight_converter) { + items(context.resources.getStringArray(R.array.text_font_weight).toList()) { _, i -> + ReadBookConfig.textBold = i + upUi(i) + onChanged?.invoke() + } + }.show() + } + + fun onChanged(unit: () -> Unit) { + onChanged = unit + } +} \ No newline at end of file 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 index cb9cfa594..65bcfc7b6 100644 --- 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 @@ -25,12 +25,10 @@ class TipConfigDialog : BaseDialogFragment() { 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) - } + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) } override fun onCreateView( @@ -60,43 +58,187 @@ class TipConfigDialog : BaseDialogFragment() { private fun initEvent() { tv_header_left.onClick { selector(items = ReadTipConfig.tipArray.toList()) { _, i -> - ReadTipConfig.tipHeaderLeft = i - tv_header_left.text = ReadTipConfig.tipArray[i] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderMiddle == i) { + tipHeaderMiddle = none + tv_header_middle.text = tipArray[none] + } + if (tipHeaderRight == i) { + tipHeaderRight = none + tv_header_right.text = tipArray[none] + } + if (tipFooterLeft == i) { + tipFooterLeft = none + tv_footer_left.text = tipArray[none] + } + if (tipFooterMiddle == i) { + tipFooterMiddle = none + tv_footer_middle.text = tipArray[none] + } + if (tipFooterRight == i) { + tipFooterRight = none + tv_footer_right.text = tipArray[none] + } + } + tipHeaderLeft = i + tv_header_left.text = 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] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderLeft == i) { + tipHeaderLeft = none + tv_header_left.text = tipArray[none] + } + if (tipHeaderRight == i) { + tipHeaderRight = none + tv_header_right.text = tipArray[none] + } + if (tipFooterLeft == i) { + tipFooterLeft = none + tv_footer_left.text = tipArray[none] + } + if (tipFooterMiddle == i) { + tipFooterMiddle = none + tv_footer_middle.text = tipArray[none] + } + if (tipFooterRight == i) { + tipFooterRight = none + tv_footer_right.text = tipArray[none] + } + } + tipHeaderMiddle = i + tv_header_middle.text = 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] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderLeft == i) { + tipHeaderLeft = none + tv_header_left.text = tipArray[none] + } + if (tipHeaderMiddle == i) { + tipHeaderMiddle = none + tv_header_middle.text = tipArray[none] + } + if (tipFooterLeft == i) { + tipFooterLeft = none + tv_footer_left.text = tipArray[none] + } + if (tipFooterMiddle == i) { + tipFooterMiddle = none + tv_footer_middle.text = tipArray[none] + } + if (tipFooterRight == i) { + tipFooterRight = none + tv_footer_right.text = tipArray[none] + } + } + tipHeaderRight = i + tv_header_right.text = 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] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderLeft == i) { + tipHeaderLeft = none + tv_header_left.text = tipArray[none] + } + if (tipHeaderMiddle == i) { + tipHeaderMiddle = none + tv_header_middle.text = tipArray[none] + } + if (tipHeaderRight == i) { + tipHeaderRight = none + tv_header_right.text = tipArray[none] + } + if (tipFooterMiddle == i) { + tipFooterMiddle = none + tv_footer_middle.text = tipArray[none] + } + if (tipFooterRight == i) { + tipFooterRight = none + tv_footer_right.text = tipArray[none] + } + } + tipFooterLeft = i + tv_footer_left.text = 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] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderLeft == i) { + tipHeaderLeft = none + tv_header_left.text = tipArray[none] + } + if (tipHeaderMiddle == i) { + tipHeaderMiddle = none + tv_header_middle.text = tipArray[none] + } + if (tipHeaderRight == i) { + tipHeaderRight = none + tv_header_right.text = tipArray[none] + } + if (tipFooterLeft == i) { + tipFooterLeft = none + tv_footer_left.text = tipArray[none] + } + if (tipFooterRight == i) { + tipFooterRight = none + tv_footer_right.text = tipArray[none] + } + } + tipFooterMiddle = i + tv_footer_middle.text = 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] + ReadTipConfig.apply { + if (i != none) { + if (tipHeaderLeft == i) { + tipHeaderLeft = none + tv_header_left.text = tipArray[none] + } + if (tipHeaderMiddle == i) { + tipHeaderMiddle = none + tv_header_middle.text = tipArray[none] + } + if (tipHeaderRight == i) { + tipHeaderRight = none + tv_header_right.text = tipArray[none] + } + if (tipFooterLeft == i) { + tipFooterLeft = none + tv_footer_left.text = tipArray[none] + } + if (tipFooterMiddle == i) { + tipFooterMiddle = none + tv_footer_middle.text = tipArray[none] + } + } + tipFooterRight = i + tv_footer_right.text = tipArray[i] + } postEvent(EventBus.UP_CONFIG, true) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt index d4a6711bc..300a507ea 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt @@ -189,7 +189,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { rootView?.apply { tocRule.name = tv_rule_name.text.toString() tocRule.rule = tv_rule_regex.text.toString() - viewModel.saveRule(tocRule, rule) + viewModel.saveRule(tocRule) } } cancelButton() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt index 4a63120c8..140a781e4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt @@ -11,23 +11,19 @@ import io.legado.app.utils.fromJsonArray class TocRegexViewModel(application: Application) : BaseViewModel(application) { - fun saveRule(rule: TxtTocRule, oldRule: TxtTocRule? = null) { + fun saveRule(rule: TxtTocRule) { execute { if (rule.serialNumber < 0) { rule.serialNumber = App.db.txtTocRule().lastOrderNum + 1 } - oldRule?.let { - App.db.txtTocRule().delete(oldRule) - } App.db.txtTocRule().insert(rule) } } fun importDefault() { execute { - AnalyzeTxtFile.getDefaultRules().let { - App.db.txtTocRule().insert(*it.toTypedArray()) - } + App.db.txtTocRule().deleteDefault() + AnalyzeTxtFile.getDefaultEnabledRules() } } 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 ea0a1bd37..5f52bd12f 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 @@ -44,23 +44,23 @@ object ChapterProvider { */ fun getTextChapter( bookChapter: BookChapter, - content: String, + contents: List, chapterSize: Int ): TextChapter { val textPages = arrayListOf() val pageLines = arrayListOf() val pageLengths = arrayListOf() val stringBuilder = StringBuilder() - val contents = content.split("\n") var durY = 0f textPages.add(TextPage()) - for ((index, text) in contents.withIndex()) { + contents.forEachIndexed { index, text -> val isTitle = index == 0 - if (isTitle && ReadBookConfig.titleMode == 2) { - continue + if (!(isTitle && ReadBookConfig.titleMode == 2)) { + durY = setTypeText( + text, durY, textPages, pageLines, + pageLengths, stringBuilder, isTitle + ) } - durY = - setTypeText(text, durY, textPages, pageLines, pageLengths, stringBuilder, isTitle) } textPages.last().height = durY + 20.dp textPages.last().text = stringBuilder.toString() @@ -70,7 +70,7 @@ object ChapterProvider { if (pageLengths.size < textPages.size) { pageLengths.add(textPages.last().text.length) } - for ((index, item) in textPages.withIndex()) { + textPages.forEachIndexed { index, item -> item.index = index item.pageSize = textPages.size item.chapterIndex = bookChapter.index @@ -78,6 +78,7 @@ object ChapterProvider { item.title = bookChapter.title item.upLinesPosition() } + return TextChapter( bookChapter.index, bookChapter.title, @@ -161,6 +162,10 @@ object ChapterProvider { desiredWidth: Float ) { var x = 0f + if (!ReadBookConfig.textFullJustify) { + addCharsToLineLast(textLine, words, textPaint, x) + return + } val bodyIndent = ReadBookConfig.bodyIndent val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length bodyIndent.toStringArray().forEach { @@ -186,14 +191,18 @@ object ChapterProvider { desiredWidth: Float, startX: Float ) { + if (!ReadBookConfig.textFullJustify) { + addCharsToLineLast(textLine, words, textPaint, startX) + return + } val gapCount: Int = words.length - 1 val d = (visibleWidth - desiredWidth) / gapCount var x = startX - for ((i, char) in words.toStringArray().withIndex()) { - val cw = StaticLayout.getDesiredWidth(char, textPaint) - val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) + words.toStringArray().forEachIndexed { index, s -> + val cw = StaticLayout.getDesiredWidth(s, textPaint) + val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw) textLine.addTextChar( - charData = char, + charData = s, start = paddingLeft + x, end = paddingLeft + x1 ) @@ -262,23 +271,37 @@ object ChapterProvider { App.INSTANCE.removePref(PreferKey.readBookFont) Typeface.SANS_SERIF } + // 字体统一处理 + val bold = Typeface.create(typeface, Typeface.BOLD) + val normal = Typeface.create(typeface, Typeface.NORMAL) + val (titleFont, textFont) = when (ReadBookConfig.textBold) { + 1 -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + Pair(Typeface.create(typeface, 900, false), bold) + else + Pair(bold, bold) + } + 2 -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + Pair(normal, Typeface.create(typeface, 300, false)) + else + Pair(normal, normal) + } + else -> Pair(bold, normal) + } + //标题 titlePaint = TextPaint() - titlePaint.color = ReadBookConfig.durConfig.textColor() + titlePaint.color = ReadBookConfig.textColor titlePaint.letterSpacing = ReadBookConfig.letterSpacing - 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.typeface = titleFont titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat() titlePaint.isAntiAlias = true //正文 contentPaint = TextPaint() - contentPaint.color = ReadBookConfig.durConfig.textColor() + contentPaint.color = ReadBookConfig.textColor contentPaint.letterSpacing = ReadBookConfig.letterSpacing - val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL - contentPaint.typeface = Typeface.create(typeface, style) + contentPaint.typeface = textFont contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() contentPaint.isAntiAlias = true //间距 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 62f240303..fb996cc29 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 @@ -144,7 +144,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at ) { val textPaint = if (isTitle) ChapterProvider.titlePaint else ChapterProvider.contentPaint textPaint.color = - if (isReadAloud) context.accentColor else ReadBookConfig.durConfig.textColor() + if (isReadAloud) context.accentColor else ReadBookConfig.textColor textChars.forEach { canvas.drawText(it.charData, it.start, lineBase, textPaint) if (it.selected) { 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 67e40bf0e..37a3acd77 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 @@ -7,10 +7,8 @@ import android.view.MotionEvent import android.widget.FrameLayout 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 @@ -49,18 +47,22 @@ class ContentView(context: Context) : FrameLayout(context) { fun upStyle() { ReadBookConfig.apply { + bv_header_left.typeface = ChapterProvider.typeface tv_header_left.typeface = ChapterProvider.typeface tv_header_middle.typeface = ChapterProvider.typeface tv_header_right.typeface = ChapterProvider.typeface + bv_footer_left.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()) + bv_header_left.setColor(textColor) + tv_header_left.setColor(textColor) + tv_header_middle.setColor(textColor) + tv_header_right.setColor(textColor) + bv_footer_left.setColor(textColor) + tv_footer_left.setColor(textColor) + tv_footer_middle.setColor(textColor) + tv_footer_right.setColor(textColor) //显示状态栏时隐藏header vw_status_bar.setPadding(0, context.statusBarHeight, 0, 0) vw_status_bar.isGone = hideStatusBar @@ -86,18 +88,14 @@ class ContentView(context: Context) : FrameLayout(context) { 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 + tv_header_left.isInvisible = tipHeaderLeft != chapterTitle + bv_header_left.isInvisible = tipHeaderLeft == none || !tv_header_left.isInvisible + tv_header_right.isGone = tipHeaderRight == none + tv_header_middle.isGone = tipHeaderMiddle == none + tv_footer_left.isInvisible = tipFooterLeft != chapterTitle + bv_footer_left.isInvisible = tipFooterLeft == none || !tv_footer_left.isInvisible + tv_footer_right.isGone = tipFooterRight == none + tv_footer_middle.isGone = tipFooterMiddle == none ll_header.isGone = hideHeader ll_footer.isGone = hideFooter } @@ -115,10 +113,10 @@ class ContentView(context: Context) : FrameLayout(context) { textSize = 12f } tvTime = when (ReadTipConfig.time) { - ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderLeft -> bv_header_left ReadTipConfig.tipHeaderMiddle -> tv_header_middle ReadTipConfig.tipHeaderRight -> tv_header_right - ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterLeft -> bv_footer_left ReadTipConfig.tipFooterMiddle -> tv_footer_middle ReadTipConfig.tipFooterRight -> tv_footer_right else -> null @@ -128,10 +126,10 @@ class ContentView(context: Context) : FrameLayout(context) { textSize = 12f } tvBattery = when (ReadTipConfig.battery) { - ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderLeft -> bv_header_left ReadTipConfig.tipHeaderMiddle -> tv_header_middle ReadTipConfig.tipHeaderRight -> tv_header_right - ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterLeft -> bv_footer_left ReadTipConfig.tipFooterMiddle -> tv_footer_middle ReadTipConfig.tipFooterRight -> tv_footer_right else -> null @@ -141,10 +139,10 @@ class ContentView(context: Context) : FrameLayout(context) { textSize = 10f } tvPage = when (ReadTipConfig.page) { - ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderLeft -> bv_header_left ReadTipConfig.tipHeaderMiddle -> tv_header_middle ReadTipConfig.tipHeaderRight -> tv_header_right - ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterLeft -> bv_footer_left ReadTipConfig.tipFooterMiddle -> tv_footer_middle ReadTipConfig.tipFooterRight -> tv_footer_right else -> null @@ -154,10 +152,10 @@ class ContentView(context: Context) : FrameLayout(context) { textSize = 12f } tvTotalProgress = when (ReadTipConfig.totalProgress) { - ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderLeft -> bv_header_left ReadTipConfig.tipHeaderMiddle -> tv_header_middle ReadTipConfig.tipHeaderRight -> tv_header_right - ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterLeft -> bv_footer_left ReadTipConfig.tipFooterMiddle -> tv_footer_middle ReadTipConfig.tipFooterRight -> tv_footer_right else -> null @@ -167,10 +165,10 @@ class ContentView(context: Context) : FrameLayout(context) { textSize = 12f } tvPageAndTotal = when (ReadTipConfig.pageAndTotal) { - ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderLeft -> bv_header_left ReadTipConfig.tipHeaderMiddle -> tv_header_middle ReadTipConfig.tipHeaderRight -> tv_header_right - ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterLeft -> bv_footer_left ReadTipConfig.tipFooterMiddle -> tv_footer_middle ReadTipConfig.tipFooterRight -> tv_footer_right else -> null @@ -207,11 +205,7 @@ class ContentView(context: Context) : FrameLayout(context) { @SuppressLint("SetTextI18n") fun setProgress(textPage: TextPage) = textPage.apply { - val title = when (AppConfig.chineseConverterType) { - 1 -> HanLP.convertToSimplifiedChinese(textPage.title) - 2 -> HanLP.convertToTraditionalChinese(textPage.title) - else -> textPage.title - } + val title = textPage.title tvTitle?.text = title tvPage?.text = "${index.plus(1)}/$pageSize" tvTotalProgress?.text = readProgress 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 3a996bfe9..7b881006a 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 @@ -3,14 +3,19 @@ package io.legado.app.ui.book.read.page import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect import android.util.AttributeSet import android.view.MotionEvent import android.widget.FrameLayout import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.theme.accentColor import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.delegate.* import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.utils.activity +import io.legado.app.utils.screenshot +import kotlinx.android.synthetic.main.activity_book_read.view.* class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs), @@ -23,6 +28,12 @@ class PageView(context: Context, attrs: AttributeSet) : var prevPage: ContentView = ContentView(context) var curPage: ContentView = ContentView(context) var nextPage: ContentView = ContentView(context) + private val autoPageRect by lazy { Rect() } + private val autoPagePint by lazy { + Paint().apply { + color = context.accentColor + } + } init { addView(nextPage) @@ -44,8 +55,22 @@ class PageView(context: Context, attrs: AttributeSet) : override fun dispatchDraw(canvas: Canvas) { super.dispatchDraw(canvas) - pageDelegate?.onDraw(canvas) + if (callBack.isAutoPage) { + nextPage.screenshot()?.let { + val bottom = + page_view.height * callBack.autoPageProgress / (ReadBookConfig.autoReadSpeed * 10) + autoPageRect.set(0, 0, page_view.width, bottom) + canvas.drawBitmap(it, autoPageRect, autoPageRect, null) + canvas.drawRect( + 0f, + bottom.toFloat() - 1, + page_view.width.toFloat(), + bottom.toFloat(), + autoPagePint + ) + } + } } override fun computeScroll() { @@ -94,9 +119,10 @@ class PageView(context: Context, attrs: AttributeSet) : } override fun upContent(relativePosition: Int, resetPageOffset: Boolean) { - if (ReadBookConfig.isScroll) { + if (ReadBookConfig.isScroll && !callBack.isAutoPage) { curPage.setContent(pageFactory.currentPage, resetPageOffset) } else { + curPage.resetPageOffset() when (relativePosition) { -1 -> prevPage.setContent(pageFactory.prevPage) 1 -> nextPage.setContent(pageFactory.nextPage) @@ -146,18 +172,18 @@ class PageView(context: Context, attrs: AttributeSet) : override val currentChapter: TextChapter? get() { - return if (callBack.isInitFinish) ReadBook.textChapter(0) else null - } + return if (callBack.isInitFinish) ReadBook.textChapter(0) else null + } override val nextChapter: TextChapter? get() { - return if (callBack.isInitFinish) ReadBook.textChapter(1) else null - } + return if (callBack.isInitFinish) ReadBook.textChapter(1) else null + } override val prevChapter: TextChapter? get() { - return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null - } + return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null + } override fun hasNextChapter(): Boolean { return ReadBook.durChapterIndex < ReadBook.chapterSize - 1 @@ -169,6 +195,8 @@ class PageView(context: Context, attrs: AttributeSet) : interface CallBack { val isInitFinish: Boolean + val isAutoPage: Boolean + val autoPageProgress: Int fun clickCenter() fun screenOffTimerStart() fun showTextActionMenu() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt index a38ed6e51..95917ae11 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt @@ -14,7 +14,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } override fun hasNextPlus(): Boolean = with(dataSource) { - return hasNextChapter() || pageIndex < (currentChapter?.pageSize() ?: 1) - 2 + return hasNextChapter() || pageIndex < (currentChapter?.pageSize ?: 1) - 2 } override fun moveToFirst() { @@ -23,10 +23,10 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override fun moveToLast() = with(dataSource) { currentChapter?.let { - if (it.pageSize() == 0) { + if (it.pageSize == 0) { ReadBook.setPageIndex(0) } else { - ReadBook.setPageIndex(it.pageSize().minus(1)) + ReadBook.setPageIndex(it.pageSize.minus(1)) } } ?: ReadBook.setPageIndex(0) } @@ -75,7 +75,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource return@with TextPage(text = it).format() } currentChapter?.let { - if (pageIndex < it.pageSize() - 1) { + if (pageIndex < it.pageSize - 1) { return@with it.page(pageIndex + 1)?.removePageAloudSpan() ?: TextPage(title = it.title).format() } @@ -102,7 +102,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } } prevChapter?.let { - return@with it.lastPage()?.removePageAloudSpan() + return@with it.lastPage?.removePageAloudSpan() ?: TextPage(title = it.title).format() } return TextPage().format() @@ -111,12 +111,12 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override val nextPagePlus: TextPage get() = with(dataSource) { currentChapter?.let { - if (pageIndex < it.pageSize() - 2) { + if (pageIndex < it.pageSize - 2) { return@with it.page(pageIndex + 2)?.removePageAloudSpan() ?: TextPage(title = it.title).format() } nextChapter?.let { nc -> - if (pageIndex < it.pageSize() - 1) { + if (pageIndex < it.pageSize - 1) { return@with nc.page(0)?.removePageAloudSpan() ?: TextPage(title = nc.title).format() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt index f8f9bec74..774f522be 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt @@ -104,7 +104,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie } override fun nextPageByAnim() { - super.nextPageByAnim() + abort() if (!hasNext()) return setDirection(Direction.NEXT) setTouchPoint(viewWidth.toFloat(), 0f) @@ -112,7 +112,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie } override fun prevPageByAnim() { - super.prevPageByAnim() + abort() if (!hasPrev()) return setDirection(Direction.PREV) setTouchPoint(0f, 0f) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index cd455b641..1f46e1dfa 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -15,6 +15,7 @@ import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.PageView import kotlin.math.abs +import io.legado.app.R abstract class PageDelegate(protected val pageView: PageView) : GestureDetector.SimpleOnGestureListener() { @@ -170,13 +171,9 @@ abstract class PageDelegate(protected val pageView: PageView) : open fun onScroll() {}//移动contentView, slidePage - open fun nextPageByAnim() { - abort() - } + abstract fun nextPageByAnim() - open fun prevPageByAnim() { - abort() - } + abstract fun prevPageByAnim() open fun keyTurnPage(direction: Direction) { if (isRunning) return @@ -320,7 +317,7 @@ abstract class PageDelegate(protected val pageView: PageView) : val hasPrev = pageView.pageFactory.hasPrev() if (!hasPrev) { if (!snackBar.isShown) { - snackBar.setText("没有上一页") + snackBar.setText(R.string.no_prev_page) snackBar.show() } } @@ -334,7 +331,7 @@ abstract class PageDelegate(protected val pageView: PageView) : val hasNext = pageView.pageFactory.hasNext() if (!hasNext) { if (!snackBar.isShown) { - snackBar.setText("没有下一页") + snackBar.setText(R.string.no_next_page) snackBar.show() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index 3fa333cbf..b00578ec2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -79,12 +79,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { } override fun nextPageByAnim() { - super.nextPageByAnim() + abort() startScroll(0, 0, 0, -ChapterProvider.visibleHeight) } override fun prevPageByAnim() { - super.prevPageByAnim() + abort() startScroll(0, 0, 0, ChapterProvider.visibleHeight) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt index e05b7746b..3f183830d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt @@ -15,25 +15,16 @@ data class TextChapter( return pages.getOrNull(index) } - fun lastPage(): TextPage? { - if (pages.isNotEmpty()) { - return pages[pages.lastIndex] - } - return null - } + val lastPage: TextPage? get() = pages.lastOrNull() - fun lastIndex(): Int { - return pages.size - 1 - } + val lastIndex: Int get() = pages.lastIndex + + val pageSize: Int get() = pages.size fun isLastIndex(index: Int): Boolean { return index >= pages.size - 1 } - fun pageSize(): Int { - return pages.size - } - fun getReadLength(pageIndex: Int): Int { var length = 0 val maxIndex = min(pageIndex, pages.size) @@ -45,8 +36,8 @@ data class TextChapter( fun getUnRead(pageIndex: Int): String { val stringBuilder = StringBuilder() - if (pageIndex < pages.size && pages.isNotEmpty()) { - for (index in pageIndex..lastIndex()) { + if (pages.isNotEmpty()) { + for (index in pageIndex..pages.lastIndex) { stringBuilder.append(pages[index].text) } } @@ -60,5 +51,4 @@ data class TextChapter( } return stringBuilder.toString() } -} - +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index b03e54fb7..4125489b8 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -18,64 +18,40 @@ class DiffCallBack(private val oldItems: List, private val newItems: override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - if (oldItem.name != newItem.name) { - return false + return when { + oldItem.name != newItem.name -> false + oldItem.author != newItem.author -> false + else -> true } - if (oldItem.author != newItem.author) { - return false - } - return true } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - if (oldItem.origins.size != newItem.origins.size) { - return false - } - if (oldItem.coverUrl != newItem.coverUrl) { - return false - } - if (oldItem.kind != newItem.kind) { - return false - } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { - return false + return when { + oldItem.origins.size != newItem.origins.size -> false + oldItem.coverUrl != newItem.coverUrl -> false + oldItem.kind != newItem.kind -> false + oldItem.latestChapterTitle != newItem.latestChapterTitle -> false + oldItem.intro != newItem.intro -> false + else -> true } - if (oldItem.intro != newItem.intro) { - return false - } - return true } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val payload = Bundle() val newItem = newItems[newItemPosition] val oldItem = oldItems[oldItemPosition] - if (oldItem.name != newItem.name) { - payload.putString("name", newItem.name) - } - if (oldItem.author != newItem.author) { - payload.putString("author", newItem.author) - } - if (oldItem.origins.size != newItem.origins.size) { + if (oldItem.name != newItem.name) payload.putString("name", newItem.name) + if (oldItem.author != newItem.author) payload.putString("author", newItem.author) + if (oldItem.origins.size != newItem.origins.size) payload.putInt("origins", newItem.origins.size) - } - if (oldItem.coverUrl != newItem.coverUrl) { - payload.putString("cover", newItem.coverUrl) - } - if (oldItem.kind != newItem.kind) { - payload.putString("kind", newItem.kind) - } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem.coverUrl != newItem.coverUrl) payload.putString("cover", newItem.coverUrl) + if (oldItem.kind != newItem.kind) payload.putString("kind", newItem.kind) + if (oldItem.latestChapterTitle != newItem.latestChapterTitle) payload.putString("last", newItem.latestChapterTitle) - } - if (oldItem.intro != newItem.intro) { - payload.putString("intro", newItem.intro) - } - if (payload.isEmpty) { - return null - } + if (oldItem.intro != newItem.intro) payload.putString("intro", newItem.intro) + if (payload.isEmpty) return null return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index e81545adc..3d12157d5 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -8,7 +8,6 @@ import android.view.View.VISIBLE import androidx.appcompat.widget.SearchView import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.FlexboxLayoutManager @@ -52,7 +51,6 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se private var menu: Menu? = null private var precisionSearchMenuItem: MenuItem? = null private var groups = linkedSetOf() - private var refreshTime = System.currentTimeMillis() override fun onActivityCreated(savedInstanceState: Bundle?) { initRecyclerView() @@ -181,14 +179,13 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se upGroupMenu() }) viewModel.searchBookLiveData.observe(this, Observer { - upSearchItems(it, false) + upSearchItems(it) }) viewModel.isSearchLiveData.observe(this, Observer { if (it) { startSearch() } else { searchFinally() - upSearchItems(viewModel.searchBooks, true) } }) } @@ -284,14 +281,8 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se * 更新搜索结果 */ @Synchronized - private fun upSearchItems(items: List, isMandatoryUpdate: Boolean) { - val searchItems = ArrayList(items) - if (isMandatoryUpdate || System.currentTimeMillis() - refreshTime > 500) { - refreshTime = System.currentTimeMillis() - val diffResult = - DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), searchItems)) - adapter.setItems(searchItems, diffResult) - } + private fun upSearchItems(items: List) { + adapter.setItems(items) } /** @@ -317,7 +308,10 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se override fun showBookInfo(name: String, author: String) { viewModel.getSearchBook(name, author) { searchBook -> searchBook?.let { - startActivity(Pair("bookUrl", it.bookUrl)) + startActivity( + Pair("name", it.name), + Pair("author", it.author) + ) } } } @@ -327,7 +321,8 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se */ override fun showBookInfo(book: Book) { startActivity( - Pair("bookUrl", book.bookUrl) + Pair("name", book.name), + Pair("author", book.author) ) } diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index ccfc5dd15..318937697 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.book.search import android.app.Application +import android.os.Handler import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel @@ -12,15 +13,18 @@ import io.legado.app.utils.getPrefBoolean import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.isActive -class SearchViewModel(application: Application) : BaseViewModel(application) - , SearchBookModel.CallBack { +class SearchViewModel(application: Application) : BaseViewModel(application), + SearchBookModel.CallBack { + val handler = Handler() private val searchBookModel = SearchBookModel(this, this) var isSearchLiveData = MutableLiveData() var searchBookLiveData = MutableLiveData>() var searchKey: String = "" var isLoading = false - var searchBooks = arrayListOf() + private var searchBooks = arrayListOf() private var searchID = 0L + private var postTime = 0L + private val sendRunnable = Runnable { upAdapter() } /** * 开始搜索 @@ -36,6 +40,18 @@ class SearchViewModel(application: Application) : BaseViewModel(application) searchBookModel.search(searchID, searchKey) } + @Synchronized + private fun upAdapter() { + if (System.currentTimeMillis() >= postTime + 500) { + handler.removeCallbacks(sendRunnable) + postTime = System.currentTimeMillis() + searchBookLiveData.postValue(searchBooks) + } else { + handler.removeCallbacks(sendRunnable) + handler.postDelayed(sendRunnable, 500 - System.currentTimeMillis() + postTime) + } + } + override fun onSearchStart() { isSearchLiveData.postValue(true) } @@ -151,7 +167,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) }) if (!scope.isActive) return searchBooks = copyDataS - searchBookLiveData.postValue(copyDataS) + upAdapter() } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index 46f3eb49f..71ee36149 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -334,11 +334,11 @@ class BookSourceEditActivity : "sourceRegex" -> contentRule.sourceRegex = it.value } } - source.ruleSearch = GSON.toJson(searchRule) - source.ruleExplore = GSON.toJson(exploreRule) - source.ruleBookInfo = GSON.toJson(bookInfoRule) - source.ruleToc = GSON.toJson(tocRule) - source.ruleContent = GSON.toJson(contentRule) + source.ruleSearch = searchRule + source.ruleExplore = exploreRule + source.ruleBookInfo = bookInfoRule + source.ruleToc = tocRule + source.ruleContent = contentRule return source } @@ -350,7 +350,7 @@ class BookSourceEditActivity : return true } - override fun sendText(text: String) { + private fun insertText(text: String) { if (text.isBlank()) return val view = window.decorView.findFocus() if (view is EditText) { @@ -365,6 +365,14 @@ class BookSourceEditActivity : } } + override fun sendText(text: String) { + if (text == AppConst.keyboardToolChars[0]) { + insertText(AppConst.urlOption) + } else { + insertText(text) + } + } + private fun showKeyboardTopPopupWindow() { mSoftKeyboardTool?.let { if (it.isShowing) return diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index d3cb63af3..d5feb380c 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -57,6 +57,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity private var bookSourceLiveDate: LiveData>? = null private var groups = linkedSetOf() private var groupMenu: SubMenu? = null + private var sort = 0 override fun onActivityCreated(savedInstanceState: Bundle?) { initUriScheme() @@ -74,6 +75,8 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity override fun onPrepareOptionsMenu(menu: Menu?): Boolean { groupMenu = menu?.findItem(R.id.menu_group)?.subMenu + groupMenu?.findItem(R.id.action_sort)?.subMenu + ?.setGroupCheckable(R.id.menu_group_sort, true, true) upGroupMenu() return super.onPrepareOptionsMenu(menu) } @@ -92,6 +95,32 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity allowExtensions = arrayOf("txt", "json") ) R.id.menu_import_source_onLine -> showImportDialog() + R.id.menu_sort_manual -> { + item.isChecked = true + sort = 0 + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_auto -> { + item.isChecked = true + sort = 2 + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_pin_yin -> { + item.isChecked = true + sort = 3 + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_url -> { + item.isChecked = true + sort = 4 + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_enabled_group -> { + search_view.setQuery(getString(R.string.enabled), true) + } + R.id.menu_disabled_group -> { + search_view.setQuery(getString(R.string.disabled), true) + } } if (item.groupId == R.id.source_group) { search_view.setQuery(item.title, true) @@ -104,14 +133,14 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity when (it.path) { "/importonline" -> it.getQueryParameter("src")?.let { url -> Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - if (url.startsWith("http", false)){ + if (url.startsWith("http", false)) { viewModel.importSource(url) { msg -> title_bar.snackbar(msg) } - } - else{ - viewModel.importSourceFromFilePath(url){msg -> - title_bar.snackbar(msg)} + } else { + viewModel.importSourceFromFilePath(url) { msg -> + title_bar.snackbar(msg) + } } } else -> { @@ -143,15 +172,30 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity private fun initLiveDataBookSource(searchKey: String? = null) { bookSourceLiveDate?.removeObservers(this) - bookSourceLiveDate = if (searchKey.isNullOrEmpty()) { - App.db.bookSourceDao().liveDataAll() - } else { - App.db.bookSourceDao().liveDataSearch("%$searchKey%") + bookSourceLiveDate = when { + searchKey.isNullOrEmpty() -> { + App.db.bookSourceDao().liveDataAll() + } + searchKey == getString(R.string.enabled) -> { + App.db.bookSourceDao().liveDataEnabled() + } + searchKey == getString(R.string.disabled) -> { + App.db.bookSourceDao().liveDataDisabled() + } + else -> { + App.db.bookSourceDao().liveDataSearch("%$searchKey%") + } } - bookSourceLiveDate?.observe(this, Observer { + bookSourceLiveDate?.observe(this, Observer { data -> + val sourceList = when (sort) { + 1 -> data.sortedBy { it.weight } + 2 -> data.sortedBy { it.bookSourceName } + 3 -> data.sortedBy { it.bookSourceUrl } + else -> data + } val diffResult = DiffUtil - .calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), it)) - adapter.setItems(it, diffResult) + .calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), sourceList)) + adapter.setItems(sourceList, diffResult) upCountView() }) } @@ -202,11 +246,35 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection()) R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode) - R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection()) + R.id.menu_check_source -> checkSource() + R.id.menu_top_sel -> viewModel.topSource(*adapter.getSelection().toTypedArray()) + R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray()) } return true } + @SuppressLint("InflateParams") + private fun checkSource() { + alert(titleResource = R.string.search_book_key) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + edit_view.setText(CheckSource.keyword) + } + } + okButton { + editText?.text?.toString()?.let { + if (it.isNotEmpty()) { + CheckSource.keyword = it + } + } + CheckSource.start(this@BookSourceActivity, adapter.getSelection()) + } + noButton { } + }.show().applyTint() + } + private fun upGroupMenu() { groupMenu?.removeGroup(R.id.source_group) groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) @@ -286,6 +354,10 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity viewModel.topSource(bookSource) } + override fun toBottom(bookSource: BookSource) { + viewModel.bottomSource(bookSource) + } + override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { exportRequestCode -> viewModel.exportSelection( diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt index eb37cb868..a035341ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt @@ -46,14 +46,14 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : callBack.upCountView() } - fun getSelection(): LinkedHashSet { - val selection = linkedSetOf() + fun getSelection(): List { + val selection = arrayListOf() getItems().map { if (selected.contains(it)) { selection.add(it) } } - return selection + return selection.sortedBy { it.customOrder } } override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList) { @@ -138,6 +138,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : popupMenu.setOnMenuItemClickListener { menuItem -> when (menuItem.itemId) { R.id.menu_top -> callBack.toTop(source) + R.id.menu_bottom -> callBack.toBottom(source) R.id.menu_del -> callBack.del(source) R.id.menu_enable_explore -> { callBack.update(source.copy(enabledExplore = !source.enabledExplore)) @@ -197,6 +198,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : fun edit(bookSource: BookSource) fun update(vararg bookSource: BookSource) fun toTop(bookSource: BookSource) + fun toBottom(bookSource: BookSource) fun upOrder() fun upCountView() } 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 5857fe8ef..c0e255783 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 @@ -19,10 +19,23 @@ import java.io.File class BookSourceViewModel(application: Application) : BaseViewModel(application) { - fun topSource(bookSource: BookSource) { + fun topSource(vararg sources: BookSource) { execute { - bookSource.customOrder = App.db.bookSourceDao().minOrder - 1 - App.db.bookSourceDao().insert(bookSource) + val minOrder = App.db.bookSourceDao().minOrder - 1 + sources.forEachIndexed { index, bookSource -> + bookSource.customOrder = minOrder - index + } + App.db.bookSourceDao().update(*sources) + } + } + + fun bottomSource(vararg sources: BookSource) { + execute { + val maxOrder = App.db.bookSourceDao().maxOrder + 1 + sources.forEachIndexed { index, bookSource -> + bookSource.customOrder = maxOrder + index + } + App.db.bookSourceDao().update(*sources) } } @@ -44,7 +57,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelection(sources: LinkedHashSet) { + fun enableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -54,7 +67,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelection(sources: LinkedHashSet) { + fun disableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -64,7 +77,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelectExplore(sources: LinkedHashSet) { + fun enableSelectExplore(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -74,7 +87,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelectExplore(sources: LinkedHashSet) { + fun disableSelectExplore(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -84,13 +97,13 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun delSelection(sources: LinkedHashSet) { + fun delSelection(sources: List) { execute { App.db.bookSourceDao().delete(*sources.toTypedArray()) } } - fun exportSelection(sources: LinkedHashSet, file: File) { + fun exportSelection(sources: List, file: File) { execute { val json = GSON.toJson(sources) FileUtils.createFileIfNotExist(file, "exportBookSource.json") @@ -102,7 +115,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun exportSelection(sources: LinkedHashSet, doc: DocumentFile) { + fun exportSelection(sources: List, doc: DocumentFile) { execute { val json = GSON.toJson(sources) doc.findFile("exportBookSource.json")?.delete() @@ -145,10 +158,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) execute { val sources = App.db.bookSourceDao().getByGroup(group) sources.map { source -> - source.bookSourceGroup?.splitNotBlank(",")?.toHashSet()?.let { - it.remove(group) - source.bookSourceGroup = TextUtils.join(",", it) - } + source.removeGroup(group) } App.db.bookSourceDao().update(*sources.toTypedArray()) } @@ -220,6 +230,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) else -> "格式不对" } }.onError { + it.printStackTrace() finally(it.localizedMessage ?: "") }.onSuccess { finally(it) @@ -227,7 +238,11 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } private fun importSourceUrl(url: String): Int { - HttpHelper.simpleGet(url, "UTF-8")?.let { body -> + HttpHelper.simpleGet(url, "UTF-8").let { body -> + if (body == null) { + toast("访问网站失败") + return 0 + } val bookSources = mutableListOf() val items: List> = jsonPath.parse(body).read("$") for (item in items) { @@ -239,6 +254,5 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) App.db.bookSourceDao().insert(*bookSources.toTypedArray()) return bookSources.size } - return 0 } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt index c849ae1de..94c883909 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt @@ -190,10 +190,11 @@ object BackupRestoreUi { AppConfig.backupPath = uri.toString() } } - oldDataRequestCode -> - if (resultCode == RESULT_OK) data?.data?.let { uri -> + oldDataRequestCode -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> ImportOldData.importUri(uri) } + } } } diff --git a/app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt index 02aab3668..4f897c791 100644 --- a/app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt @@ -13,66 +13,73 @@ import io.legado.app.utils.readText import java.io.File class FileAssociationViewModel(application: Application) : BaseViewModel(application) { + fun dispatchIndent(uri: Uri): Intent? { - val url: String - //如果是普通的url,需要根据返回的内容判断是什么 - if (uri.scheme == "file" || uri.scheme == "content") { - val content = if (uri.scheme == "file") { - val file = File(uri.path.toString()) - if (file.exists()) { - file.readText() + try { + val url: String + //如果是普通的url,需要根据返回的内容判断是什么 + if (uri.scheme == "file" || uri.scheme == "content") { + val content = if (uri.scheme == "file") { + val file = File(uri.path.toString()) + if (file.exists()) { + file.readText() + } else { + null + } } else { - null + DocumentFile.fromSingleUri(context, uri)?.readText(context) } - } else { - DocumentFile.fromSingleUri(context, uri)?.readText(context) - } - var scheme = "" - if (content != null) { - if (content.isJsonObject() || content.isJsonArray()) { - //暂时根据文件内容判断属于什么 - when { - content.contains("bookSourceUrl") -> { - scheme = "booksource" - } - content.contains("sourceUrl") -> { - scheme = "rsssource" - } - content.contains("pattern") -> { - scheme = "replace" + var scheme = "" + if (content != null) { + if (content.isJsonObject() || content.isJsonArray()) { + //暂时根据文件内容判断属于什么 + when { + content.contains("bookSourceUrl") -> { + scheme = "booksource" + } + content.contains("sourceUrl") -> { + scheme = "rsssource" + } + content.contains("pattern") -> { + scheme = "replace" + } } } - } - if (TextUtils.isEmpty(scheme)) { - execute { - if (uri.scheme == "content"){ - LocalBook.importFile(uri.toString()) - }else{ - LocalBook.importFile(uri.path.toString()) + if (TextUtils.isEmpty(scheme)) { + execute { + if (uri.scheme == "content") { + LocalBook.importFile(uri.toString()) + } else { + LocalBook.importFile(uri.path.toString()) + } + toast("添加本地文件成功${uri.path}") } - toast("添加本地文件成功${uri.path}") + return null } + } else { + toast("文件不存在") return null } + // content模式下,需要传递完整的路径,方便后续解析 + url = if (uri.scheme == "content") { + "yuedu://${scheme}/importonline?src=$uri" + } else { + "yuedu://${scheme}/importonline?src=${uri.path}" + } + + } else if (uri.scheme == "yuedu") { + url = uri.toString() } else { - toast("文件不存在") - return null - } - // content模式下,需要传递完整的路径,方便后续解析 - url = if (uri.scheme == "content"){ - "yuedu://${scheme}/importonline?src=$uri" - }else{ - "yuedu://${scheme}/importonline?src=${uri.path}" + url = "yuedu://booksource/importonline?src=${uri.path}" } - - } else if (uri.scheme == "yuedu") { - url = uri.toString() - } else { - url = "yuedu://booksource/importonline?src=${uri.path}" + val data = Uri.parse(url) + val newIndent = Intent(Intent.ACTION_VIEW) + newIndent.data = data + return newIndent + } catch (e: Exception) { + e.printStackTrace() + toast(e.localizedMessage) + return null } - val data = Uri.parse(url) - val newIndent = Intent(Intent.ACTION_VIEW) - newIndent.data = data - return newIndent } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index da2672841..42a3db246 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -1,8 +1,12 @@ package io.legado.app.ui.config +import android.annotation.SuppressLint import android.content.SharedPreferences import android.os.Build import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -22,7 +26,8 @@ import io.legado.app.ui.widget.prefs.IconListPreference import io.legado.app.utils.* -class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { +class ThemeConfigFragment : PreferenceFragmentCompat(), + SharedPreferences.OnSharedPreferenceChangeListener { val items = arrayListOf("极简", "曜夜", "经典", "黑白", "A屏黑") @@ -33,12 +38,13 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar preferenceScreen.removePreference(it) } } - upPreferenceSummary("barElevation", AppConfig.elevation.toString()) + upPreferenceSummary(PreferKey.barElevation, AppConfig.elevation.toString()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ATH.applyEdgeEffectColor(listView) + setHasOptionsMenu(true) } override fun onResume() { @@ -51,22 +57,37 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar super.onPause() } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.theme_config, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_theme_mode -> { + AppConfig.isNightTheme = !AppConfig.isNightTheme + App.INSTANCE.applyDayNight() + } + } + return super.onOptionsItemSelected(item) + } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { sharedPreferences ?: return when (key) { PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key)) - "transparentStatusBar" -> recreateActivities() - "colorPrimary", - "colorAccent", - "colorBackground", - "colorBottomBackground" -> { + PreferKey.transparentStatusBar -> recreateActivities() + PreferKey.cPrimary, + PreferKey.cAccent, + PreferKey.cBackground, + PreferKey.cBBackground -> { if (backgroundIsDark(sharedPreferences)) { alert { title = "白天背景太暗" message = "将会恢复默认背景?" yesButton { putPrefInt( - "colorBackground", + PreferKey.cBackground, getCompatColor(R.color.md_grey_100) ) upTheme(false) @@ -80,17 +101,17 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar upTheme(false) } } - "colorPrimaryNight", - "colorAccentNight", - "colorBackgroundNight", - "colorBottomBackgroundNight" -> { + PreferKey.cNPrimary, + PreferKey.cNAccent, + PreferKey.cNBackground, + PreferKey.cNBBackground -> { if (backgroundIsLight(sharedPreferences)) { alert { title = "夜间背景太亮" message = "将会恢复默认背景?" yesButton { putPrefInt( - "colorBackgroundNight", + PreferKey.cNBackground, getCompatColor(R.color.md_grey_800) ) upTheme(true) @@ -108,59 +129,18 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar } + @SuppressLint("PrivateResource") override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { - "defaultTheme" -> alert(title = "切换默认主题") { - items(items) { _, which -> - when (which) { - 0 -> { - putPrefInt("colorPrimary", getCompatColor(R.color.md_grey_100)) - putPrefInt("colorAccent", getCompatColor(R.color.lightBlue_color)) - putPrefInt("colorBackground", getCompatColor(R.color.md_grey_100)) - AppConfig.isNightTheme = false - } - 1 -> { - putPrefInt("colorPrimaryNight", getCompatColor(R.color.shine_color)) - putPrefInt("colorAccentNight", getCompatColor(R.color.lightBlue_color)) - putPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) - AppConfig.isNightTheme = true - } - 2 -> { - putPrefInt("colorPrimary", getCompatColor(R.color.md_light_blue_500)) - putPrefInt("colorAccent", getCompatColor(R.color.md_pink_800)) - putPrefInt("colorBackground", getCompatColor(R.color.md_grey_100)) - AppConfig.isNightTheme = false - } - 3 -> { - putPrefInt("colorPrimary", getCompatColor(R.color.white)) - putPrefInt("colorAccent", getCompatColor(R.color.black)) - putPrefInt("colorBackground", getCompatColor(R.color.white)) - AppConfig.isNightTheme = false - } - 4 -> { - putPrefInt("colorPrimaryNight", getCompatColor(R.color.black)) - putPrefInt( - "colorAccentNight", - getCompatColor(R.color.md_grey_600) - ) - putPrefInt( - "colorBackgroundNight", - getCompatColor(R.color.black) - ) - AppConfig.isNightTheme = true - } - } - App.INSTANCE.applyDayNight() - recreateActivities() - } - }.show().applyTint() - "barElevation" -> NumberPickerDialog(requireContext()) + "defaultTheme" -> changeTheme() + PreferKey.barElevation -> NumberPickerDialog(requireContext()) .setTitle(getString(R.string.bar_elevation)) .setMaxValue(32) .setMinValue(0) .setValue(AppConfig.elevation) .setCustomButton((R.string.btn_default_s)) { - AppConfig.elevation = App.INSTANCE.resources.getDimension(R.dimen.design_appbar_elevation).toInt() + AppConfig.elevation = + App.INSTANCE.resources.getDimension(R.dimen.design_appbar_elevation).toInt() recreateActivities() } .show { @@ -171,10 +151,56 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar return super.onPreferenceTreeClick(preference) } + private fun changeTheme() { + alert(title = "切换默认主题") { + items(items) { _, which -> + when (which) { + 0 -> { + putPrefInt(PreferKey.cPrimary, getCompatColor(R.color.md_grey_100)) + putPrefInt(PreferKey.cAccent, getCompatColor(R.color.lightBlue_color)) + putPrefInt(PreferKey.cBackground, getCompatColor(R.color.md_grey_100)) + putPrefInt(PreferKey.cBBackground, getCompatColor(R.color.md_grey_200)) + AppConfig.isNightTheme = false + } + 1 -> { + putPrefInt(PreferKey.cNPrimary, getCompatColor(R.color.md_grey_900)) + putPrefInt(PreferKey.cNAccent, getCompatColor(R.color.lightBlue_color)) + putPrefInt(PreferKey.cNBackground, getCompatColor(R.color.md_grey_900)) + putPrefInt(PreferKey.cNBBackground, getCompatColor(R.color.md_grey_900)) + AppConfig.isNightTheme = true + } + 2 -> { + putPrefInt(PreferKey.cPrimary, getCompatColor(R.color.md_light_blue_500)) + putPrefInt(PreferKey.cAccent, getCompatColor(R.color.md_pink_800)) + putPrefInt(PreferKey.cBackground, getCompatColor(R.color.md_grey_100)) + putPrefInt(PreferKey.cBBackground, getCompatColor(R.color.md_grey_200)) + AppConfig.isNightTheme = false + } + 3 -> { + putPrefInt(PreferKey.cPrimary, getCompatColor(R.color.white)) + putPrefInt(PreferKey.cAccent, getCompatColor(R.color.black)) + putPrefInt(PreferKey.cBackground, getCompatColor(R.color.white)) + putPrefInt(PreferKey.cBBackground, getCompatColor(R.color.white)) + AppConfig.isNightTheme = false + } + 4 -> { + putPrefInt(PreferKey.cNPrimary, getCompatColor(R.color.black)) + putPrefInt(PreferKey.cNAccent, getCompatColor(R.color.md_grey_500)) + putPrefInt(PreferKey.cNBackground, getCompatColor(R.color.black)) + putPrefInt(PreferKey.cNBBackground, getCompatColor(R.color.black)) + AppConfig.isNightTheme = true + } + } + App.INSTANCE.applyDayNight() + recreateActivities() + } + }.show().applyTint() + } + private fun backgroundIsDark(sharedPreferences: SharedPreferences): Boolean { return !ColorUtils.isColorLight( sharedPreferences.getInt( - "colorBackground", + PreferKey.cBackground, getCompatColor(R.color.md_grey_100) ) ) @@ -183,7 +209,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar private fun backgroundIsLight(sharedPreferences: SharedPreferences): Boolean { return ColorUtils.isColorLight( sharedPreferences.getInt( - "colorBackgroundNight", + PreferKey.cNBackground, getCompatColor(R.color.md_grey_800) ) ) @@ -205,7 +231,8 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar private fun upPreferenceSummary(preferenceKey: String, value: String?) { val preference = findPreference(preferenceKey) ?: return when (preferenceKey) { - "barElevation" -> preference.summary = getString(R.string.bar_elevation_s, value) + PreferKey.barElevation -> preference.summary = + getString(R.string.bar_elevation_s, value) } } } \ No newline at end of file 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 181ec0330..a417f4ca1 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 @@ -18,7 +18,6 @@ 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 import io.legado.app.ui.main.bookshelf.BookshelfFragment import io.legado.app.ui.main.explore.ExploreFragment import io.legado.app.ui.main.my.MyFragment @@ -26,13 +25,16 @@ import io.legado.app.ui.main.rss.RssFragment import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_main.* +import org.jetbrains.anko.toast class MainActivity : VMBaseActivity(R.layout.activity_main), BottomNavigationView.OnNavigationItemSelectedListener, + BottomNavigationView.OnNavigationItemReselectedListener, ViewPager.OnPageChangeListener by ViewPager.SimpleOnPageChangeListener() { override val viewModel: MainViewModel get() = getViewModel(MainViewModel::class.java) - + private var exitTime: Long = 0 + private var bookshelfReselected: Long = 0 private var pagePosition = 0 private val fragmentId = arrayOf(0, 1, 2, 3) private val fragmentMap = mapOf( @@ -49,6 +51,7 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.addOnPageChangeListener(this) bottom_navigation_view.setOnNavigationItemSelectedListener(this) + bottom_navigation_view.setOnNavigationItemReselectedListener(this) bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS } @@ -73,6 +76,18 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), return false } + override fun onNavigationItemReselected(item: MenuItem) { + when (item.itemId) { + R.id.menu_bookshelf -> { + if (System.currentTimeMillis() - bookshelfReselected > 300) { + bookshelfReselected = System.currentTimeMillis() + } else { + (fragmentMap[0] as? BookshelfFragment)?.gotoTop() + } + } + } + } + private fun upVersion() { if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) { putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) @@ -104,10 +119,17 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), view_pager_main.currentItem = 0 return true } - if (!BaseReadAloudService.pause) { - moveTaskToBack(true) - return true + if (System.currentTimeMillis() - exitTime > 2000) { + toast(R.string.double_click_exit) + exitTime = System.currentTimeMillis() + } else { + if (BaseReadAloudService.pause) { + finish() + } else { + moveTaskToBack(true) + } } + return true } } } @@ -121,11 +143,6 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } } - override fun onDestroy() { - super.onDestroy() - ReadAloud.stop(this) - } - override fun observeLiveBus() { observeEvent(EventBus.RECREATE) { recreate() diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index 61c951ae5..b7392aaef 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -3,7 +3,9 @@ package io.legado.app.ui.main import android.app.Application import io.legado.app.App import io.legado.app.base.BaseViewModel +import io.legado.app.constant.BookType import io.legado.app.constant.EventBus +import io.legado.app.data.entities.Book import io.legado.app.data.entities.RssSource import io.legado.app.help.http.HttpHelper import io.legado.app.help.storage.Restore @@ -19,7 +21,15 @@ class MainViewModel(application: Application) : BaseViewModel(application) { fun upChapterList() { execute { - App.db.bookDao().hasUpdateBooks.forEach { book -> + upChapterList(App.db.bookDao().hasUpdateBooks) + } + } + + fun upChapterList(books: List) { + execute { + books.filter { + it.origin != BookType.local && it.canUpdate + }.forEach { book -> if (!updateList.contains(book.bookUrl)) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> synchronized(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 f51bdc0a6..cfd4f7d6d 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 @@ -31,6 +31,7 @@ import io.legado.app.ui.book.download.DownloadActivity import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.local.ImportBookActivity import io.legado.app.ui.book.search.SearchActivity +import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.bookshelf.books.BooksFragment import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* @@ -52,11 +53,12 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b override val viewModel: BookshelfViewModel get() = getViewModel(BookshelfViewModel::class.java) - + private val activityViewModel: MainViewModel + get() = getViewModelOfActivity(MainViewModel::class.java) private var bookGroupLiveData: LiveData>? = null private var noGroupLiveData: LiveData? = null private val bookGroups = mutableListOf() - private val fragmentMap = hashMapOf() + private val fragmentMap = hashMapOf() private var showGroupNone = false override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { @@ -73,6 +75,13 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b super.onCompatOptionsItemSelected(item) when (item.itemId) { R.id.menu_search -> startActivity() + R.id.menu_update_toc -> { + val group = bookGroups[tab_layout.selectedTabPosition] + val fragment = fragmentMap[group.groupId] + fragment?.getBooks()?.let { + activityViewModel.upChapterList(it) + } + } R.id.menu_bookshelf_layout -> configBookshelf() R.id.menu_group_manage -> GroupManageDialog() .show(childFragmentManager, "groupManageDialog") @@ -82,7 +91,10 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b Pair("groupId", selectedGroup.groupId), Pair("groupName", selectedGroup.groupName) ) - R.id.menu_download -> startActivity() + R.id.menu_download -> startActivity( + Pair("groupId", selectedGroup.groupId), + Pair("groupName", selectedGroup.groupName) + ) } } @@ -254,6 +266,10 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } } + fun gotoTop() { + fragmentMap[selectedGroup.groupId]?.gotoTop() + } + private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt index 1438c4507..be725abe9 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf.books import android.content.Context import android.os.Bundle +import android.view.View import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.constant.BookType @@ -22,33 +23,30 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) : ATH.applyBackgroundTint(this) tv_name.text = item.name iv_cover.load(item.getDisplayCover(), item.name, item.author) - if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { - bv_unread.invisible() - rl_loading.show() - } else { - rl_loading.hide() - bv_unread.setBadgeCount(item.getUnreadChapterNum()) - bv_unread.setHighlight(item.lastCheckCount > 0) - } + upRefresh(this, item) } else { bundle.keySet().map { when (it) { "name" -> tv_name.text = item.name "cover" -> iv_cover.load(item.getDisplayCover(), item.name, item.author) - "refresh" -> if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { - bv_unread.invisible() - rl_loading.show() - } else { - rl_loading.hide() - bv_unread.setBadgeCount(item.getUnreadChapterNum()) - bv_unread.setHighlight(item.lastCheckCount > 0) - } + "refresh" -> upRefresh(this, item) } } } } } + private fun upRefresh(itemView: View, item: Book) = with(itemView) { + if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { + bv_unread.invisible() + rl_loading.show() + } else { + rl_loading.hide() + bv_unread.setBadgeCount(item.getUnreadChapterNum()) + bv_unread.setHighlight(item.lastCheckCount > 0) + } + } + override fun registerListener(holder: ItemViewHolder) { holder.itemView.apply { onClick { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt index 755f55f20..7ed9a7d99 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf.books import android.content.Context import android.os.Bundle +import android.view.View import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.constant.BookType @@ -25,14 +26,7 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) : tv_read.text = item.durChapterTitle tv_last.text = item.latestChapterTitle iv_cover.load(item.getDisplayCover(), item.name, item.author) - if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { - bv_unread.invisible() - rl_loading.show() - } else { - rl_loading.hide() - bv_unread.setBadgeCount(item.getUnreadChapterNum()) - bv_unread.setHighlight(item.lastCheckCount > 0) - } + upRefresh(this, item) } else { bundle.keySet().map { when (it) { @@ -41,20 +35,24 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) : "dur" -> tv_read.text = item.durChapterTitle "last" -> tv_last.text = item.latestChapterTitle "cover" -> iv_cover.load(item.getDisplayCover(), item.name, item.author) - "refresh" -> if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { - bv_unread.invisible() - rl_loading.show() - } else { - rl_loading.hide() - bv_unread.setBadgeCount(item.getUnreadChapterNum()) - bv_unread.setHighlight(item.lastCheckCount > 0) - } + "refresh" -> upRefresh(this, item) } } } } } + private fun upRefresh(itemView: View, item: Book) = with(itemView) { + if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { + bv_unread.invisible() + rl_loading.show() + } else { + rl_loading.hide() + bv_unread.setHighlight(item.lastCheckCount > 0) + bv_unread.setBadgeCount(item.getUnreadChapterNum()) + } + } + override fun registerListener(holder: ItemViewHolder) { holder.itemView.apply { onClick { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt index 27954c634..0756daca5 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt @@ -16,54 +16,45 @@ class BooksDiffCallBack(private val oldItems: List, private val newItems: } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldItems[oldItemPosition].bookUrl == newItems[newItemPosition].bookUrl + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] + return oldItem.name == newItem.name + && oldItem.author == newItem.author } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - if (oldItem.name != newItem.name) - return false - if (oldItem.author != newItem.author) - return false - if (oldItem.durChapterTitle != newItem.durChapterTitle) - return false - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) - return false - if (oldItem.lastCheckCount != newItem.lastCheckCount) - return false - if (oldItem.getDisplayCover() != newItem.getDisplayCover()) - return false - if (oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum()) - return false - return true + return when { + oldItem.durChapterTime != newItem.durChapterTime -> false + oldItem.name != newItem.name -> false + oldItem.author != newItem.author -> false + oldItem.durChapterTitle != newItem.durChapterTitle -> false + oldItem.latestChapterTitle != newItem.latestChapterTitle -> false + oldItem.lastCheckCount != newItem.lastCheckCount -> false + oldItem.getDisplayCover() != newItem.getDisplayCover() -> false + oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() -> false + else -> true + } } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] val bundle = bundleOf() - if (oldItem.name != newItem.name) - bundle.putString("name", null) - if (oldItem.author != newItem.author) - bundle.putString("author", null) - if (oldItem.durChapterTitle != newItem.durChapterTitle) - bundle.putString("dur", null) - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) - bundle.putString("last", null) - if (oldItem.getDisplayCover() != newItem.getDisplayCover()) - bundle.putString("cover", null) - if (oldItem.lastCheckCount != newItem.lastCheckCount) - bundle.putString("refresh", null) - if (oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() + if (oldItem.name != newItem.name) bundle.putString("name", null) + if (oldItem.author != newItem.author) bundle.putString("author", null) + if (oldItem.durChapterTitle != newItem.durChapterTitle) bundle.putString("dur", null) + if (oldItem.latestChapterTitle != newItem.latestChapterTitle) bundle.putString("last", null) + if (oldItem.getDisplayCover() != newItem.getDisplayCover()) bundle.putString("cover", null) + if (oldItem.lastCheckCount != newItem.lastCheckCount + || oldItem.durChapterTime != newItem.durChapterTime + || oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() || oldItem.lastCheckCount != newItem.lastCheckCount ) { bundle.putString("refresh", null) } - - if (bundle.isEmpty) { - return null - } + if (bundle.isEmpty) return null return bundle } 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 ab8984752..f0731f1ed 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 @@ -16,6 +16,7 @@ import io.legado.app.constant.BookType import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book +import io.legado.app.help.AppConfig import io.legado.app.help.IntentDataHelp import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor @@ -45,14 +46,14 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } } - private lateinit var activityViewModel: MainViewModel + private val activityViewModel: MainViewModel + get() = getViewModelOfActivity(MainViewModel::class.java) private lateinit var booksAdapter: BaseBooksAdapter private var bookshelfLiveData: LiveData>? = null private var position = 0 private var groupId = -1 override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - activityViewModel = getViewModelOfActivity(MainViewModel::class.java) arguments?.let { position = it.getInt("position", 0) groupId = it.getInt("groupId", -1) @@ -66,7 +67,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), refresh_layout.setColorSchemeColors(accentColor) refresh_layout.setOnRefreshListener { refresh_layout.isRefreshing = false - activityViewModel.upChapterList() + activityViewModel.upChapterList(booksAdapter.getItems()) } val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) if (bookshelfLayout == 0) { @@ -118,6 +119,18 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), }) } + fun getBooks(): List { + return booksAdapter.getItems() + } + + fun gotoTop() { + if (AppConfig.isEInkMode) { + rv_bookshelf.scrollToPosition(0) + } else { + rv_bookshelf.smoothScrollToPosition(0) + } + } + override fun open(book: Book) { when (book.type) { BookType.audio -> @@ -130,7 +143,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } override fun openBookInfo(book: Book) { - context?.startActivity(Pair("bookUrl", book.bookUrl)) + context?.startActivity( + Pair("name", book.name), + Pair("author", book.author) + ) } override fun isUpdate(bookUrl: String): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt index 88bb95afe..b16cbaf17 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt @@ -65,6 +65,9 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. BackupRestoreUi.onActivityResult(requestCode, resultCode, data) } + /** + * 配置 + */ class PreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt index d58f7587e..955c5ded3 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt @@ -1,6 +1,8 @@ package io.legado.app.ui.main.rss import android.content.Context +import android.view.View +import androidx.appcompat.widget.PopupMenu import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter @@ -8,6 +10,7 @@ import io.legado.app.data.entities.RssSource import io.legado.app.help.ImageLoader import kotlinx.android.synthetic.main.item_rss.view.* import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.sdk27.listeners.onLongClick class RssAdapter(context: Context, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_rss) { @@ -29,9 +32,32 @@ class RssAdapter(context: Context, val callBack: CallBack) : callBack.openRss(it) } } + holder.itemView.onLongClick { + getItem(holder.layoutPosition)?.let { + showMenu(holder.itemView.iv_icon, it) + } + true + } + } + + private fun showMenu(view: View, rssSource: RssSource) { + val popupMenu = PopupMenu(context, view) + popupMenu.inflate(R.menu.rss_main_item) + popupMenu.setOnMenuItemClickListener { + when (it.itemId) { + R.id.menu_top -> callBack.toTop(rssSource) + R.id.menu_edit -> callBack.edit(rssSource) + R.id.menu_del -> callBack.del(rssSource) + } + true + } + popupMenu.show() } interface CallBack { fun openRss(rssSource: RssSource) + fun toTop(rssSource: RssSource) + fun edit(rssSource: RssSource) + fun del(rssSource: RssSource) } } \ No newline at end of file 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 5bb42f081..334ce9e64 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 @@ -8,22 +8,27 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.GridLayoutManager import io.legado.app.App import io.legado.app.R -import io.legado.app.base.BaseFragment +import io.legado.app.base.VMBaseFragment 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.RssSortActivity import io.legado.app.ui.rss.favorites.RssFavoritesActivity +import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity +import io.legado.app.ui.rss.source.manage.RssSourceViewModel +import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.startActivity import kotlinx.android.synthetic.main.fragment_rss.* import kotlinx.android.synthetic.main.view_title_bar.* -class RssFragment : BaseFragment(R.layout.fragment_rss), +class RssFragment : VMBaseFragment(R.layout.fragment_rss), RssAdapter.CallBack { private lateinit var adapter: RssAdapter + override val viewModel: RssSourceViewModel + get() = getViewModel(RssSourceViewModel::class.java) override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { setSupportToolbar(toolbar) @@ -62,4 +67,16 @@ class RssFragment : BaseFragment(R.layout.fragment_rss), override fun openRss(rssSource: RssSource) { startActivity(Pair("url", rssSource.sourceUrl)) } + + override fun toTop(rssSource: RssSource) { + viewModel.topSource(rssSource) + } + + override fun edit(rssSource: RssSource) { + startActivity(Pair("data", rssSource.sourceUrl)) + } + + override fun del(rssSource: RssSource) { + viewModel.del(rssSource) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt index 73c64a0e1..0027f3209 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt @@ -1,26 +1,32 @@ package io.legado.app.ui.replacerule.edit import android.os.Bundle -import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.EditText +import android.widget.PopupWindow import androidx.appcompat.widget.Toolbar import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer import io.legado.app.R +import io.legado.app.constant.AppConst import io.legado.app.constant.Theme import io.legado.app.data.entities.ReplaceRule +import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import io.legado.app.utils.toast import kotlinx.android.synthetic.main.dialog_replace_edit.* +import org.jetbrains.anko.sdk27.listeners.onFocusChange class ReplaceEditDialog : DialogFragment(), - Toolbar.OnMenuItemClickListener { + Toolbar.OnMenuItemClickListener, + KeyboardToolPop.CallBack { companion object { @@ -41,12 +47,11 @@ class ReplaceEditDialog : DialogFragment(), } private lateinit var viewModel: ReplaceEditViewModel + private lateinit var mSoftKeyboardTool: PopupWindow override fun onStart() { super.onStart() - val dm = DisplayMetrics() - activity?.windowManager?.defaultDisplay?.getMetrics(dm) - dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), WRAP_CONTENT) + dialog?.window?.setLayout(MATCH_PARENT, WRAP_CONTENT) } override fun onCreateView( @@ -60,6 +65,7 @@ class ReplaceEditDialog : DialogFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + mSoftKeyboardTool = KeyboardToolPop(requireContext(), AppConst.keyboardToolChars, this) tool_bar.inflateMenu(R.menu.replace_edit) tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.setOnMenuItemClickListener(this) @@ -69,12 +75,20 @@ class ReplaceEditDialog : DialogFragment(), arguments?.let { viewModel.initData(it) } + et_replace_rule.onFocusChange { v, hasFocus -> + if (hasFocus) { + mSoftKeyboardTool.width = v.width + mSoftKeyboardTool.showAsDropDown(v) + } else { + mSoftKeyboardTool.dismiss() + } + } } override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_save -> { - val rule = getReplaceRule(); + val rule = getReplaceRule() if (!rule.isValid()){ toast(R.string.replace_rule_invalid) } @@ -111,6 +125,30 @@ class ReplaceEditDialog : DialogFragment(), val callBack get() = activity as? CallBack + private fun insertText(text: String) { + if (text.isBlank()) return + val view = dialog?.window?.decorView?.findFocus() + if (view is EditText) { + val start = view.selectionStart + val end = view.selectionEnd + val edit = view.editableText//获取EditText的文字 + if (start < 0 || start >= edit.length) { + edit.append(text) + } else { + edit.replace(start, end, text)//光标所在位置插入文字 + } + } + } + + override fun sendText(text: String) { + if (text == AppConst.keyboardToolChars[0]) { + val view = dialog?.window?.decorView?.findFocus() + view?.clearFocus() + } else { + insertText(text) + } + } + interface CallBack { fun onReplaceRuleSave() } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt index 00b296359..19fd6d0fb 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.rss.article +import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable import com.bumptech.glide.load.DataSource @@ -18,41 +19,46 @@ import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.textColorResource -class RssArticlesAdapter(context: Context, val callBack: CallBack) : - SimpleRecyclerAdapter(context, R.layout.item_rss_article) { +class RssArticlesAdapter(context: Context, layoutId: Int, val callBack: CallBack) : + SimpleRecyclerAdapter(context, layoutId) { + @SuppressLint("CheckResult") override fun convert(holder: ItemViewHolder, item: RssArticle, payloads: MutableList) { with(holder.itemView) { tv_title.text = item.title tv_pub_date.text = item.pubDate - if (item.image.isNullOrBlank()) { + if (item.image.isNullOrBlank() && !callBack.isGridLayout) { image_view.gone() } else { - ImageLoader.load(context, item.image) - .addListener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - image_view.gone() - return false - } + ImageLoader.load(context, item.image).apply { + if (callBack.isGridLayout) { + placeholder(R.drawable.image_rss_article) + } else { + addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + image_view.gone() + return false + } - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - image_view.visible() - return false - } + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + image_view.visible() + return false + } - }) - .into(image_view) + }) + } + }.into(image_view) } if (item.read) { tv_title.textColorResource = R.color.tv_text_summary @@ -71,6 +77,7 @@ class RssArticlesAdapter(context: Context, val callBack: CallBack) : } interface CallBack { + val isGridLayout: Boolean fun readRss(rssArticle: RssArticle) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt index f7a1b6bb6..f2329cb84 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.View import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.App @@ -42,6 +43,8 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme lateinit var adapter: RssArticlesAdapter private lateinit var loadMoreView: LoadMoreView private var rssArticlesData: LiveData>? = null + override val isGridLayout: Boolean + get() = activityViewModel.isGridLayout override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.init(arguments) @@ -53,9 +56,15 @@ class RssArticlesFragment : VMBaseFragment(R.layout.fragme private fun initView() { ATH.applyEdgeEffectColor(recycler_view) - recycler_view.layoutManager = LinearLayoutManager(requireContext()) - recycler_view.addItemDecoration(VerticalDivider(requireContext())) - adapter = RssArticlesAdapter(requireContext(), this) + recycler_view.layoutManager = if (activityViewModel.isGridLayout) { + recycler_view.setPadding(8, 0, 8, 0) + GridLayoutManager(requireContext(), 2) + } else { + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + LinearLayoutManager(requireContext()) + + } + adapter = RssArticlesAdapter(requireContext(), activityViewModel.layoutId, this) recycler_view.adapter = adapter loadMoreView = LoadMoreView(requireContext()) adapter.addFooterView(loadMoreView) 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 3bcfc00ab..8d2d06df8 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 @@ -16,7 +16,6 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application var isLoading = true var order = System.currentTimeMillis() private var nextPageUrl: String? = null - private val articles = arrayListOf() var sortName: String = "" var sortUrl: String = "" @@ -60,26 +59,35 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application Rss.getArticles(sortName, pageUrl, rssSource, pageUrl) .onSuccess(Dispatchers.IO) { nextPageUrl = it.nextPageUrl - it.articles.let { list -> - if (list.isEmpty()) { - loadFinally.postValue(true) - return@let - } - if (articles.contains(list.first())) { - loadFinally.postValue(false) - } else { - list.forEach { rssArticle -> - rssArticle.order = order-- - } - App.db.rssArticleDao().insert(*list.toTypedArray()) - } - } - isLoading = false + loadMoreSuccess(it.articles) + } + .onError { + loadFinally.postValue(false) } } else { loadFinally.postValue(false) } } + private fun loadMoreSuccess(articles: MutableList) { + articles.let { list -> + if (list.isEmpty()) { + loadFinally.postValue(false) + return@let + } + val firstArticle = list.first() + val dbArticle = App.db.rssArticleDao() + .get(firstArticle.origin, firstArticle.link) + if (dbArticle != null) { + loadFinally.postValue(false) + } else { + list.forEach { rssArticle -> + rssArticle.order = order-- + } + App.db.rssArticleDao().insert(*list.toTypedArray()) + } + } + isLoading = false + } } \ 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 index 2d45bcd70..41fad7292 100644 --- 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 @@ -53,6 +53,10 @@ class RssSortActivity : VMBaseActivity(R.layout.activity_rss_a viewModel.clearArticles() } } + R.id.menu_switch_layout -> { + viewModel.switchLayout() + upFragments() + } } return super.onCompatOptionsItemSelected(item) } @@ -84,6 +88,10 @@ class RssSortActivity : VMBaseActivity(R.layout.activity_rss_a private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + override fun getItemPosition(`object`: Any): Int { + return POSITION_NONE + } + override fun getPageTitle(position: Int): CharSequence? { return fragments.keys.elementAt(position) } 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 index 4b1149f2e..135064bce 100644 --- 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 @@ -4,6 +4,7 @@ import android.app.Application import android.content.Intent import androidx.lifecycle.MutableLiveData import io.legado.app.App +import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssReadRecord @@ -15,6 +16,13 @@ class RssSortViewModel(application: Application) : BaseViewModel(application) { var rssSource: RssSource? = null val titleLiveData = MutableLiveData() var order = System.currentTimeMillis() + val isGridLayout get() = rssSource?.articleStyle == 2 + val layoutId + get() = when (rssSource?.articleStyle) { + 1 -> R.layout.item_rss_article_1 + 2 -> R.layout.item_rss_article_2 + else -> R.layout.item_rss_article + } fun initData(intent: Intent, finally: () -> Unit) { execute { @@ -32,6 +40,19 @@ class RssSortViewModel(application: Application) : BaseViewModel(application) { } } + fun switchLayout() { + rssSource?.let { + if (it.articleStyle < 2) { + it.articleStyle = it.articleStyle + 1 + } else { + it.articleStyle = 0 + } + execute { + App.db.rssSourceDao().update(it) + } + } + } + fun read(rssArticle: RssArticle) { execute { App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link)) diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index f5b84642f..72af31aca 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -138,17 +138,28 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } fun clHtml(content: String): String { - return if (content.contains(" - $content - """.trimIndent() + return when { + !rssSource?.style.isNullOrEmpty() -> { + """ + + $content + """.trimIndent() + } + content.contains(" + $content + """.trimIndent() + } } } 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 88059b955..bd070639a 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 @@ -144,6 +144,7 @@ class RssSourceEditActivity : add(EditEntity("ruleImage", rssSource?.ruleImage, R.string.r_image)) add(EditEntity("ruleLink", rssSource?.ruleLink, R.string.r_link)) add(EditEntity("ruleContent", rssSource?.ruleContent, R.string.r_content)) + add(EditEntity("style", rssSource?.style, R.string.r_style)) add(EditEntity("header", rssSource?.header, R.string.source_http_header)) } adapter.editEntities = sourceEntities @@ -169,6 +170,7 @@ class RssSourceEditActivity : "ruleImage" -> source.ruleImage = it.value "ruleLink" -> source.ruleLink = it.value "ruleContent" -> source.ruleContent = it.value + "style" -> source.style = it.value "header" -> source.header = it.value } } @@ -183,7 +185,7 @@ class RssSourceEditActivity : return true } - override fun sendText(text: String) { + private fun insertText(text: String) { if (text.isBlank()) return val view = window.decorView.findFocus() if (view is EditText) { @@ -198,6 +200,14 @@ class RssSourceEditActivity : } } + override fun sendText(text: String) { + if (text == AppConst.keyboardToolChars[0]) { + insertText(AppConst.urlOption) + } else { + insertText(text) + } + } + private fun showKeyboardTopPopupWindow() { mSoftKeyboardTool?.let { if (it.isShowing) return diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index 1f7503e47..be9866ef2 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -106,8 +106,8 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode) - R.id.menu_check_source -> { - } + R.id.menu_top_sel -> viewModel.topSource(*adapter.getSelection().toTypedArray()) + R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray()) } return true } @@ -204,7 +204,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r private fun upGroupMenu() { groupMenu?.removeGroup(R.id.source_group) - groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) + groups.sortedWith(Collator.getInstance(Locale.CHINESE)) .map { groupMenu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) } @@ -339,6 +339,10 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r viewModel.topSource(source) } + override fun toBottom(source: RssSource) { + viewModel.bottomSource(source) + } + override fun upOrder() { viewModel.upOrder() } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt index 72de1e250..156dc1610 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt @@ -40,14 +40,14 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : callBack.upCountView() } - fun getSelection(): LinkedHashSet { - val selection = linkedSetOf() + fun getSelection(): List { + val selection = arrayListOf() getItems().forEach { if (selected.contains(it)) { selection.add(it) } } - return selection + return selection.sortedBy { it.customOrder } } override fun convert(holder: ItemViewHolder, item: RssSource, payloads: MutableList) { @@ -111,6 +111,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : popupMenu.setOnMenuItemClickListener { menuItem -> when (menuItem.itemId) { R.id.menu_top -> callBack.toTop(source) + R.id.menu_bottom -> callBack.toBottom(source) R.id.menu_del -> callBack.del(source) } true @@ -151,6 +152,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : fun edit(source: RssSource) fun update(vararg source: RssSource) fun toTop(source: RssSource) + fun toBottom(source: RssSource) fun upOrder() fun upCountView() } 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 42b354709..a15cb2e77 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 @@ -9,6 +9,7 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssSource +import io.legado.app.help.SourceHelp import io.legado.app.help.http.HttpHelper import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* @@ -17,10 +18,23 @@ import java.io.File class RssSourceViewModel(application: Application) : BaseViewModel(application) { - fun topSource(rssSource: RssSource) { + fun topSource(vararg sources: RssSource) { execute { - rssSource.customOrder = App.db.rssSourceDao().minOrder - 1 - App.db.rssSourceDao().insert(rssSource) + val minOrder = App.db.rssSourceDao().minOrder - 1 + sources.forEachIndexed { index, rssSource -> + rssSource.customOrder = minOrder - index + } + App.db.rssSourceDao().update(*sources) + } + } + + fun bottomSource(vararg sources: RssSource) { + execute { + val maxOrder = App.db.rssSourceDao().maxOrder + 1 + sources.forEachIndexed { index, rssSource -> + rssSource.customOrder = maxOrder + index + } + App.db.rssSourceDao().update(*sources) } } @@ -42,7 +56,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelection(sources: LinkedHashSet) { + fun enableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -52,7 +66,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelection(sources: LinkedHashSet) { + fun disableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -62,13 +76,13 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun delSelection(sources: LinkedHashSet) { + fun delSelection(sources: List) { execute { App.db.rssSourceDao().delete(*sources.toTypedArray()) } } - fun exportSelection(sources: LinkedHashSet, file: File) { + fun exportSelection(sources: List, file: File) { execute { val json = GSON.toJson(sources) FileUtils.createFileIfNotExist(file, "exportRssSource.json") @@ -80,7 +94,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun exportSelection(sources: LinkedHashSet, doc: DocumentFile) { + fun exportSelection(sources: List, doc: DocumentFile) { execute { val json = GSON.toJson(sources) doc.findFile("exportRssSource.json")?.delete() @@ -147,10 +161,9 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) null } } - if (null != content) { GSON.fromJsonArray(content)?.let { - App.db.rssSourceDao().insert(*it.toTypedArray()) + SourceHelp.insertRssSource(*it.toTypedArray()) } } }.onSuccess { @@ -172,7 +185,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } else { GSON.fromJsonArray(text1)?.let { - App.db.rssSourceDao().insert(*it.toTypedArray()) + SourceHelp.insertRssSource(*it.toTypedArray()) count = 1 } } @@ -187,7 +200,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) rssSources.add(it) } } - App.db.rssSourceDao().insert(*rssSources.toTypedArray()) + SourceHelp.insertRssSource(*rssSources.toTypedArray()) "导入${rssSources.size}条" } text1.isAbsUrl() -> { @@ -213,10 +226,10 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) sources.add(source) } } - App.db.rssSourceDao().insert(*sources.toTypedArray()) + SourceHelp.insertRssSource(*sources.toTypedArray()) return sources.size - } return 0 } + } \ No newline at end of file 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 eabfad602..d9725f019 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 @@ -39,9 +39,9 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co if (!isBattery) return outFrame.set( 1.dp, - layout.getLineTop(0) + 2.dp, + layout.getLineBaseline(0) + layout.getLineAscent(0) + 2.dp, width - 3.dp, - layout.getLineBottom(0) - 2.dp + layout.getLineBaseline(0) + 2.dp ) val dj = (outFrame.bottom - outFrame.top) / 3 polar.set( diff --git a/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt b/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt index 426391be4..e0268da7e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt @@ -130,7 +130,7 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView { fun load(path: String?, name: String?, author: String?) { setText(name, author) - ImageLoader.load(context, path)//Glide自动识别http://和file:// + ImageLoader.load(context, path)//Glide自动识别http://,content://和file:// .placeholder(R.drawable.image_cover_default) .error(R.drawable.image_cover_default) .listener(object : RequestListener { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt index 101b6678a..0ea45510f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt @@ -14,6 +14,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.jaredrummler.android.colorpicker.* import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.ColorUtils class ColorPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), ColorPickerDialogListener { @@ -22,8 +23,9 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex private val sizeLarge = 1 private var onShowDialogListener: OnShowDialogListener? = null - private var color = Color.BLACK + private var mColor = Color.BLACK private var showDialog: Boolean = false + @ColorPickerDialog.DialogType private var dialogType: Int = 0 private var colorShape: Int = 0 @@ -42,7 +44,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference) showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true) - dialogType = a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS) + dialogType = + a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS) colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE) allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true) allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true) @@ -50,7 +53,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true) previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, sizeNormal) val presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0) - dialogTitle = a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title) + dialogTitle = + a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title) presets = if (presetsResId != 0) { context.resources.getIntArray(presetsResId) } else { @@ -67,7 +71,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex override fun onClick() { super.onClick() if (onShowDialogListener != null) { - onShowDialogListener!!.onShowColorPickerDialog(title as String, color) + onShowDialogListener!!.onShowColorPickerDialog(title as String, mColor) } else if (showDialog) { val dialog = ColorPickerDialogCompat.newBuilder() .setDialogType(dialogType) @@ -78,7 +82,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex .setAllowCustom(allowCustom) .setShowAlphaSlider(showAlphaSlider) .setShowColorShades(showColorShades) - .setColor(color) + .setColor(mColor) .create() dialog.setColorPickerDialogListener(this) getActivity().supportFragmentManager @@ -111,10 +115,12 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex } override fun onBindViewHolder(holder: PreferenceViewHolder) { - val v = io.legado.app.ui.widget.prefs.Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, - io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30) + val v = io.legado.app.ui.widget.prefs.Preference.bindView( + context, holder, icon, title, summary, widgetLayoutResource, + io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30 + ) if (v is ColorPanelView) { - v.color = color + v.color = mColor } super.onBindViewHolder(holder) } @@ -122,10 +128,10 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex override fun onSetInitialValue(defaultValue: Any?) { super.onSetInitialValue(defaultValue) if (defaultValue is Int) { - color = (defaultValue as Int?)!! - persistInt(color) + mColor = if (!showAlphaSlider) ColorUtils.withAlpha(defaultValue, 1f) else defaultValue + persistInt(mColor) } else { - color = getPersistedInt(-0x1000000) + mColor = getPersistedInt(-0x1000000) } } @@ -147,8 +153,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex * @param color The newly selected color */ fun saveValue(@ColorInt color: Int) { - this.color = color - persistInt(this.color) + mColor = if (showAlphaSlider) color else ColorUtils.withAlpha(color, 1f) + persistInt(mColor) notifyChanged() callChangeListener(color) } @@ -231,17 +237,23 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex class Builder internal constructor() { internal var colorPickerDialogListener: ColorPickerDialogListener? = null + @StringRes internal var dialogTitle = R.string.cpv_default_title + @StringRes internal var presetsButtonText = R.string.cpv_presets + @StringRes internal var customButtonText = R.string.cpv_custom + @StringRes internal var selectedButtonText = R.string.cpv_select + @DialogType internal var dialogType = TYPE_PRESETS internal var presets = MATERIAL_COLORS + @ColorInt internal var color = Color.BLACK internal var dialogId = 0 @@ -249,6 +261,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex internal var allowPresets = true internal var allowCustom = true internal var showColorShades = true + @ColorShape internal var colorShape = ColorShape.CIRCLE diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt index 2ebb11344..f16799d0e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.preference.PreferenceViewHolder import io.legado.app.R @@ -45,7 +46,7 @@ class Preference(context: Context, attrs: AttributeSet) : val tvSummary = it.findViewById(R.id.preference_desc) if (tvSummary is TextView) { tvSummary.text = summary - tvSummary.isVisible = summary != null && summary.isNotEmpty() + tvSummary.isGone = summary.isNullOrEmpty() } val iconView = it.findViewById(R.id.preference_icon) @@ -54,11 +55,13 @@ class Preference(context: Context, attrs: AttributeSet) : iconView.setImageDrawable(icon) iconView.setColorFilter(context.accentColor) } + } if (weightLayoutRes != null && weightLayoutRes != 0 && viewId != null && viewId != 0) { val lay = it.findViewById(R.id.preference_widget) if (lay is FrameLayout) { + var needRequestLayout = false var v = it.itemView.findViewById(viewId) if (v == null) { val inflater: LayoutInflater = context.layoutInflater @@ -67,7 +70,8 @@ class Preference(context: Context, attrs: AttributeSet) : lay.addView(childView) lay.isVisible = true v = lay.findViewById(viewId) - } + } else + needRequestLayout = true if (weightWidth > 0 || weightHeight > 0) { val lp = lay.layoutParams @@ -78,7 +82,8 @@ class Preference(context: Context, attrs: AttributeSet) : lp.width = (context.resources.displayMetrics.density * weightWidth).roundToInt() lay.layoutParams = lp - } + } else if (needRequestLayout) + v.requestLayout() return v } 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 9cf6c2bfd..c150dbe56 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 @@ -12,7 +12,6 @@ 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 -import io.legado.app.utils.getCompatColor class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCategory(context, attrs) { @@ -28,12 +27,9 @@ class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCate val view = it.findViewById(R.id.preference_title) if (view is TextView) { // && !view.isInEditMode view.text = title - if (view.isInEditMode) { - view.setTextColor(context.getCompatColor(R.color.colorAccent)) - } else { - view.setBackgroundColor(context.backgroundColor) - view.setTextColor(context.accentColor) - } + if (view.isInEditMode) return + view.setBackgroundColor(context.backgroundColor) + view.setTextColor(context.accentColor) view.isVisible = title != null && title.isNotEmpty() val da = it.findViewById(R.id.preference_divider_above) diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt index 2f6becfc2..41038169f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt @@ -26,7 +26,7 @@ class SwitchPreference(context: Context, attrs: AttributeSet) : widgetLayoutResource, R.id.switchWidget ) - if (v is SwitchCompat) { + if (v is SwitchCompat && !v.isInEditMode) { ATH.setTint(v, context.accentColor) } super.onBindViewHolder(holder) diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt index 0a2ebf741..c2ebb8352 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt @@ -189,7 +189,6 @@ class FastScrollRecyclerView : RecyclerView { private fun layout(context: Context, attrs: AttributeSet?) { mFastScroller = FastScroller(context, attrs) mFastScroller?.id = R.id.fast_scroller - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/AnimationUtilsSupport.kt b/app/src/main/java/io/legado/app/utils/AnimationUtilsSupport.kt new file mode 100644 index 000000000..1400b11f2 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/AnimationUtilsSupport.kt @@ -0,0 +1,17 @@ +package io.legado.app.utils + +import android.content.Context +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import androidx.annotation.AnimRes +import io.legado.app.help.AppConfig + +object AnimationUtilsSupport { + fun loadAnimation(context: Context, @AnimRes id: Int): Animation { + val animation = AnimationUtils.loadAnimation(context, id) + if (AppConfig.isEInkMode) { + animation.duration = 0 + } + return animation + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt index 138a0c063..7786bfed4 100644 --- a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt +++ b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt @@ -18,6 +18,7 @@ import kotlin.math.* @Suppress("unused", "WeakerAccess") object BitmapUtils { + /** * 从path中获取图片信息,在通过BitmapFactory.decodeFile(String path)方法将突破转成Bitmap时, * 遇到大一些的图片,我们经常会遇到OOM(Out Of Memory)的问题。所以用到了我们上面提到的BitmapFactory.Options这个类。 @@ -51,15 +52,11 @@ object BitmapUtils { * @param path 图片路径 * @return */ - fun decodeBitmap(path: String): Bitmap { val opts = BitmapFactory.Options() - opts.inJustDecodeBounds = true BitmapFactory.decodeFile(path, opts) - opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128) - opts.inJustDecodeBounds = false return BitmapFactory.decodeFile(path, opts) @@ -125,7 +122,6 @@ object BitmapUtils { width: Int, height: Int ): Bitmap? { - var inputStream = context.assets.open(fileNameInAssets) val op = BitmapFactory.Options() // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; @@ -171,11 +167,8 @@ object BitmapUtils { minSideLength: Int, maxNumOfPixels: Int ): Int { - val initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels) - var roundedSize: Int - if (initialSize <= 8) { roundedSize = 1 while (roundedSize < initialSize) { @@ -184,7 +177,6 @@ object BitmapUtils { } else { roundedSize = (initialSize + 7) / 8 * 8 } - return roundedSize } @@ -198,27 +190,34 @@ object BitmapUtils { val w = options.outWidth.toDouble() val h = options.outHeight.toDouble() - val lowerBound = if (maxNumOfPixels == -1) - 1 - else - ceil(sqrt(w * h / maxNumOfPixels)).toInt() + val lowerBound = when (maxNumOfPixels) { + -1 -> 1 + else -> ceil(sqrt(w * h / maxNumOfPixels)).toInt() + } - val upperBound = if (minSideLength == -1) 128 else min( - floor(w / minSideLength), - floor(h / minSideLength) - ).toInt() + val upperBound = when (minSideLength) { + -1 -> 128 + else -> min( + floor(w / minSideLength), + floor(h / minSideLength) + ).toInt() + } if (upperBound < lowerBound) { // return the larger one when there is no overlapping zone. return lowerBound } - return if (maxNumOfPixels == -1 && minSideLength == -1) { - 1 - } else if (minSideLength == -1) { - lowerBound - } else { - upperBound + return when { + maxNumOfPixels == -1 && minSideLength == -1 -> { + 1 + } + minSideLength == -1 -> { + lowerBound + } + else -> { + upperBound + } } } @@ -278,7 +277,8 @@ object BitmapUtils { val averagePixelGreen = pixelSumGreen / 3000 return Color.rgb( averagePixelRed + 3, - averagePixelGreen + 3, averagePixelBlue + 3 + averagePixelGreen + 3, + averagePixelBlue + 3 ) } diff --git a/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt index 154cce984..b7ef740cc 100644 --- a/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt +++ b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt @@ -6,7 +6,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.transition.TransitionManager -class ConstraintUtil(val constraintLayout: ConstraintLayout) { +class ConstraintUtil(private val constraintLayout: ConstraintLayout) { private var begin: ConstraintBegin? = null private val applyConstraintSet = ConstraintSet() @@ -19,14 +19,14 @@ class ConstraintUtil(val constraintLayout: ConstraintLayout) { /** * 开始修改 */ - fun begin(): ConstraintBegin? { + fun begin(): ConstraintBegin { synchronized(ConstraintBegin::class.java) { if (begin == null) { - begin = ConstraintBegin() + begin = ConstraintBegin(constraintLayout, applyConstraintSet) } } applyConstraintSet.clone(constraintLayout) - return begin + return begin!! } /** @@ -53,214 +53,228 @@ class ConstraintUtil(val constraintLayout: 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 - } +class ConstraintBegin( + private val constraintLayout: ConstraintLayout, + private val applyConstraintSet: ConstraintSet +) { - /** - * 为某个控件设置 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 + /** + * 清除关系

+ * 注意:这里不仅仅会清除关系,还会清除对应控件的宽高为 w:0,h:0 + * @param viewIds + * @return + */ + fun clear(@IdRes vararg viewIds: Int): ConstraintBegin { + for (viewId in viewIds) { + applyConstraintSet.clear(viewId) } + 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 - } + /** + * 清除某个控件的,某个关系 + * @param viewId + * @param anchor + * @return + */ + fun clear(viewId: Int, anchor: Int): ConstraintBegin { + applyConstraintSet.clear(viewId, anchor) + 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 - } + fun setHorizontalWeight(viewId: Int, weight: Float): ConstraintBegin { + applyConstraintSet.setHorizontalWeight(viewId, weight) + 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 - } + fun setVerticalWeight(viewId: Int, weight: Float): ConstraintBegin { + applyConstraintSet.setVerticalWeight(viewId, weight) + 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 - } + /** + * 为某个控件设置 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 + } - /** - * 为某个控件设置关联关系 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 - } + /** + * 为某个控件设置 marginLeft + * @param viewId 某个控件ID + * @param left marginLeft + * @return + */ + fun setMarginLeft(@IdRes viewId: Int, left: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.LEFT, 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 - } + /** + * 为某个控件设置 marginRight + * @param viewId 某个控件ID + * @param right marginRight + * @return + */ + fun setMarginRight(@IdRes viewId: Int, right: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.RIGHT, 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 - } + /** + * 为某个控件设置 marginTop + * @param viewId 某个控件ID + * @param top marginTop + * @return + */ + fun setMarginTop(@IdRes viewId: Int, top: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.TOP, 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 - } + /** + * 为某个控件设置marginBottom + * @param viewId 某个控件ID + * @param bottom marginBottom + * @return + */ + fun setMarginBottom(@IdRes viewId: Int, bottom: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.BOTTOM, 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 - } + /** + * 为某个控件设置关联关系 left_to_left_of + * @param startId + * @param endId + * @return + */ + fun leftToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.LEFT, 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 - } + /** + * 为某个控件设置关联关系 left_to_right_of + * @param startId + * @param endId + * @return + */ + fun leftToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.LEFT, 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 - } + /** + * 为某个控件设置关联关系 top_to_top_of + * @param startId + * @param endId + * @return + */ + fun topToTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.TOP) + 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 - } + /** + * 为某个控件设置关联关系 top_to_bottom_of + * @param startId + * @param endId + * @return + */ + fun topToBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.BOTTOM) + return this + } - /** - * 为某个控件设置宽度 - * @param viewId - * @param width - * @return - */ - fun setWidth(@IdRes viewId: Int, width: Int): ConstraintBegin { - applyConstraintSet.constrainWidth(viewId, width) - return this - } + /** + * 为某个控件设置关联关系 right_to_left_of + * @param startId + * @param endId + * @return + */ + fun rightToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.LEFT) + return this + } - /** - * 某个控件设置高度 - * @param viewId - * @param height - * @return - */ - fun setHeight(@IdRes viewId: Int, height: Int): ConstraintBegin { - applyConstraintSet.constrainHeight(viewId, height) - return this - } + /** + * 为某个控件设置关联关系 right_to_right_of + * @param startId + * @param endId + * @return + */ + fun rightToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.RIGHT) + return this + } - /** - * 提交应用生效 - */ - fun commit() { - applyConstraintSet.applyTo(constraintLayout) - } + /** + * 为某个控件设置关联关系 bottom_to_bottom_of + * @param startId + * @param endId + * @return + */ + fun bottomToBottomOf(@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 bottomToTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.TOP) + return this } -} \ No newline at end of file + /** + * 为某个控件设置宽度 + * @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) + } +} diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index bc3f6f9fa..7a9d1e275 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -3,6 +3,7 @@ package io.legado.app.utils import android.annotation.SuppressLint import android.content.* +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap @@ -178,4 +179,16 @@ fun Context.openUrl(uri: Uri) { toast(e.localizedMessage ?: "open url error") } } -} \ No newline at end of file +} + +val Context.channel: String + get() { + try { + val pm = packageManager + val appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + return appInfo.metaData.getString("channel") ?: "" + } catch (e: Exception) { + e.printStackTrace(); + } + return "" + } 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 3f736abc5..b29b4e2b4 100644 --- a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt @@ -1,15 +1,21 @@ package io.legado.app.utils -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonSyntaxException +import com.google.gson.* +import com.google.gson.internal.LinkedTreeMap import com.google.gson.reflect.TypeToken import org.jetbrains.anko.attempt import java.lang.reflect.ParameterizedType import java.lang.reflect.Type +import kotlin.math.ceil + val GSON: Gson by lazy { GsonBuilder() + .registerTypeAdapter( + object : TypeToken?>() {}.type, + MapDeserializerDoubleAsIntFix() + ) + .registerTypeAdapter(Int::class.java, IntJsonDeserializer()) .disableHtmlEscaping() .setPrettyPrinting() .create() @@ -17,7 +23,6 @@ val GSON: Gson by lazy { inline fun genericType(): Type = object : TypeToken() {}.type -@Throws(JsonSyntaxException::class) inline fun Gson.fromJsonObject(json: String?): T? {//可转成任意类型 return attempt { val result: T? = fromJson(json, genericType()) @@ -25,7 +30,6 @@ inline fun Gson.fromJsonObject(json: String?): T? {//可转成任意 }.value } -@Throws(JsonSyntaxException::class) inline fun Gson.fromJsonArray(json: String?): List? { return attempt { val result: List? = fromJson(json, ParameterizedTypeImpl(T::class.java)) @@ -40,3 +44,94 @@ class ParameterizedTypeImpl(private val clazz: Class<*>) : ParameterizedType { override fun getActualTypeArguments(): Array = arrayOf(clazz) } + +/** + * int类型转化失败时跳过 + */ +class IntJsonDeserializer : JsonDeserializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): Int? { + return when { + json.isJsonPrimitive -> { + val prim = json.asJsonPrimitive + if (prim.isNumber) { + prim.asNumber.toInt() + } else { + null + } + } + else -> null + } + } + +} + + +/** + * 修复Int变为Double的问题 + */ +class MapDeserializerDoubleAsIntFix : + JsonDeserializer?> { + + @Throws(JsonParseException::class) + override fun deserialize( + jsonElement: JsonElement, + type: Type, + jsonDeserializationContext: JsonDeserializationContext + ): Map? { + @Suppress("unchecked_cast") + return read(jsonElement) as? Map + } + + fun read(json: JsonElement): Any? { + when { + json.isJsonArray -> { + val list: MutableList = ArrayList() + val arr = json.asJsonArray + for (anArr in arr) { + list.add(read(anArr)) + } + return list + } + json.isJsonObject -> { + val map: MutableMap = + LinkedTreeMap() + val obj = json.asJsonObject + val entitySet = + obj.entrySet() + for ((key, value) in entitySet) { + map[key] = read(value) + } + return map + } + json.isJsonPrimitive -> { + val prim = json.asJsonPrimitive + when { + prim.isBoolean -> { + return prim.asBoolean + } + prim.isString -> { + return prim.asString + } + prim.isNumber -> { + val num: Number = prim.asNumber + // here you can handle double int/long values + // and return any type you want + // this solution will transform 3.0 float to long values + return if (ceil(num.toDouble()) == num.toLong().toDouble()) { + num.toLong() + } else { + num.toDouble() + } + } + } + } + } + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/MenuExtensions.kt b/app/src/main/java/io/legado/app/utils/MenuExtensions.kt index 9f13d543d..9167eda46 100644 --- a/app/src/main/java/io/legado/app/utils/MenuExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/MenuExtensions.kt @@ -19,13 +19,8 @@ fun Menu.applyTint(context: Context, theme: Theme = Theme.Auto): Menu = this.let if (menu is MenuBuilder) { menu.setOptionalIconsVisible(true) } - val primaryTextColor = context.primaryTextColor val defaultTextColor = context.getCompatColor(R.color.tv_text_default) - val tintColor = when (theme) { - Theme.Dark -> context.getCompatColor(R.color.md_white_1000) - Theme.Light -> context.getCompatColor(R.color.md_black_1000) - else -> primaryTextColor - } + val tintColor = UIUtils.getMenuColor(context, theme) menu.forEach { item -> (item as MenuItemImpl).let { impl -> //overflow:展开的item diff --git a/app/src/main/java/io/legado/app/utils/UIUtils.kt b/app/src/main/java/io/legado/app/utils/UIUtils.kt new file mode 100644 index 000000000..31b02199b --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/UIUtils.kt @@ -0,0 +1,45 @@ +package io.legado.app.utils + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.os.Build +import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat +import io.legado.app.R +import io.legado.app.constant.Theme +import io.legado.app.lib.theme.primaryTextColor + +@Suppress("unused") +object UIUtils { + + /** 设置更多工具条图标和颜色 */ + fun setToolbarMoreIconCustomColor(toolbar: Toolbar?, color: Int? = null) { + toolbar ?: return + val moreIcon = ContextCompat.getDrawable(toolbar.context, R.drawable.ic_more) + if (moreIcon != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (color != null) { + moreIcon.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) + } + toolbar.overflowIcon = moreIcon + } + } + + + fun getMenuColor( + context: Context, + theme: Theme = Theme.Auto, + requiresOverflow: Boolean = false + ): Int { + val defaultTextColor = context.getCompatColor(R.color.tv_text_default) + if (requiresOverflow) + return defaultTextColor + val primaryTextColor = context.primaryTextColor + return when (theme) { + Theme.Dark -> context.getCompatColor(R.color.md_white_1000) + Theme.Light -> context.getCompatColor(R.color.md_black_1000) + else -> primaryTextColor + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index e37539851..7ed6d8cd6 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -1,5 +1,6 @@ package io.legado.app.utils +import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas @@ -11,8 +12,11 @@ import android.view.inputmethod.InputMethodManager import android.widget.RadioGroup import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.menu.MenuPopupHelper +import androidx.appcompat.widget.PopupMenu import androidx.core.view.get import io.legado.app.App +import java.lang.reflect.Field private tailrec fun getCompatActivity(context: Context?): AppCompatActivity? { @@ -107,4 +111,17 @@ fun RadioGroup.getCheckedIndex(): Int { fun RadioGroup.checkByIndex(index: Int) { check(get(index).id) +} + +@SuppressLint("RestrictedApi") +fun PopupMenu.show(x: Int, y: Int) { + try { + val field: Field = this.javaClass.getDeclaredField("mPopup") + field.isAccessible = true + (field.get(this) as MenuPopupHelper).show(x, y) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_img_border.xml b/app/src/main/res/drawable/bg_img_border.xml new file mode 100644 index 000000000..07783726c --- /dev/null +++ b/app/src/main/res/drawable/bg_img_border.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml index c7542ba87..b4f63fa80 100644 --- a/app/src/main/res/drawable/ic_add.xml +++ b/app/src/main/res/drawable/ic_add.xml @@ -1,11 +1,11 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_online.xml b/app/src/main/res/drawable/ic_add_online.xml index 8a3cc9b8b..d11efea01 100644 --- a/app/src/main/res/drawable/ic_add_online.xml +++ b/app/src/main/res/drawable/ic_add_online.xml @@ -1,11 +1,11 @@ - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrange.xml b/app/src/main/res/drawable/ic_arrange.xml index ce2d50152..416dbf843 100644 --- a/app/src/main/res/drawable/ic_arrange.xml +++ b/app/src/main/res/drawable/ic_arrange.xml @@ -1,17 +1,17 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_download_line.xml b/app/src/main/res/drawable/ic_download_line.xml index 3b0ae72ea..c882860c7 100644 --- a/app/src/main/res/drawable/ic_download_line.xml +++ b/app/src/main/res/drawable/ic_download_line.xml @@ -1,14 +1,14 @@ - - - - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_groups.xml b/app/src/main/res/drawable/ic_groups.xml index 0ebcd4526..e15e9af8e 100644 --- a/app/src/main/res/drawable/ic_groups.xml +++ b/app/src/main/res/drawable/ic_groups.xml @@ -1,20 +1,20 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 000000000..b0357ce02 --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml index 921e00d7a..b915218e5 100644 --- a/app/src/main/res/drawable/ic_search.xml +++ b/app/src/main/res/drawable/ic_search.xml @@ -1,17 +1,17 @@ - - - - + + + + diff --git a/app/src/main/res/drawable/ic_view_quilt.xml b/app/src/main/res/drawable/ic_view_quilt.xml index a0fb3d300..88b5317e7 100644 --- a/app/src/main/res/drawable/ic_view_quilt.xml +++ b/app/src/main/res/drawable/ic_view_quilt.xml @@ -1,26 +1,26 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/image_rss_article.jpg b/app/src/main/res/drawable/image_rss_article.jpg new file mode 100644 index 000000000..471b5650f Binary files /dev/null and b/app/src/main/res/drawable/image_rss_article.jpg differ diff --git a/app/src/main/res/layout/activity_book_search.xml b/app/src/main/res/layout/activity_book_search.xml index ebb7a3186..6daf33cd4 100644 --- a/app/src/main/res/layout/activity_book_search.xml +++ b/app/src/main/res/layout/activity_book_search.xml @@ -31,8 +31,8 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/activity_import_book.xml b/app/src/main/res/layout/activity_import_book.xml index 38d0b4f55..7fb75bdb2 100644 --- a/app/src/main/res/layout/activity_import_book.xml +++ b/app/src/main/res/layout/activity_import_book.xml @@ -53,13 +53,19 @@ tools:ignore="UnusedAttribute" /> - + android:layout_weight="1" + app:layout_constraintTop_toBottomOf="@id/layTop" + app:layout_constraintBottom_toTopOf="@id/select_action_bar"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_read_aloud.xml b/app/src/main/res/layout/dialog_read_aloud.xml index 504865171..422c1932b 100644 --- a/app/src/main/res/layout/dialog_read_aloud.xml +++ b/app/src/main/res/layout/dialog_read_aloud.xml @@ -273,7 +273,7 @@ android:layout_height="50dp" android:background="?android:attr/selectableItemBackgroundBorderless" android:clickable="true" - android:contentDescription="@string/interface_setting" + android:contentDescription="@string/to_backstage" android:focusable="true" android:orientation="vertical" android:paddingBottom="7dp"> diff --git a/app/src/main/res/layout/dialog_read_book_style.xml b/app/src/main/res/layout/dialog_read_book_style.xml index 9436e0f0a..b57705513 100644 --- a/app/src/main/res/layout/dialog_read_book_style.xml +++ b/app/src/main/res/layout/dialog_read_book_style.xml @@ -37,15 +37,14 @@ android:layout_height="wrap_content" android:layout_weight="1" /> - diff --git a/app/src/main/res/layout/item_bookshelf_grid.xml b/app/src/main/res/layout/item_bookshelf_grid.xml index 50bb20d12..f96b8d507 100644 --- a/app/src/main/res/layout/item_bookshelf_grid.xml +++ b/app/src/main/res/layout/item_bookshelf_grid.xml @@ -61,8 +61,8 @@ android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:paddingLeft="12dp" - android:paddingRight="12dp" + android:paddingLeft="4dp" + android:paddingRight="4dp" android:paddingBottom="4dp" android:includeFontPadding="false" android:gravity="top|center_horizontal" diff --git a/app/src/main/res/layout/item_bookshelf_list.xml b/app/src/main/res/layout/item_bookshelf_list.xml index b381aaa90..3d6f71213 100644 --- a/app/src/main/res/layout/item_bookshelf_list.xml +++ b/app/src/main/res/layout/item_bookshelf_list.xml @@ -14,8 +14,8 @@ android:layout_width="60dp" android:layout_height="80dp" android:layout_marginStart="16dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="8dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="12dp" android:contentDescription="@string/img_cover" android:scaleType="centerCrop" android:src="@drawable/image_cover_default" @@ -56,10 +56,11 @@ android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginLeft="8dp" - android:layout_marginTop="8dp" + android:layout_marginLeft="10dp" + android:layout_marginTop="12dp" android:includeFontPadding="false" - android:paddingLeft="4dp" + android:paddingBottom="4dp" + android:paddingLeft="2dp" android:singleLine="true" android:text="@string/book_name" android:textColor="@color/tv_text_default" @@ -74,15 +75,14 @@ android:id="@+id/iv_author" android:layout_width="@dimen/desc_icon_size" android:layout_height="@dimen/desc_icon_size" - android:layout_marginLeft="8dp" android:contentDescription="@string/author" android:paddingStart="2dp" android:paddingEnd="2dp" android:src="@drawable/ic_author" app:layout_constraintBottom_toBottomOf="@+id/tv_author" - app:layout_constraintLeft_toRightOf="@+id/iv_cover" + app:layout_constraintLeft_toLeftOf="@+id/tv_name" app:layout_constraintTop_toTopOf="@+id/tv_author" - app:tint="@color/tv_text_secondary" + app:tint="@color/tv_text_summary" tools:ignore="RtlHardcoded,RtlSymmetry" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/item_rss.xml b/app/src/main/res/layout/item_rss.xml index b06e2e37b..f3765bf58 100644 --- a/app/src/main/res/layout/item_rss.xml +++ b/app/src/main/res/layout/item_rss.xml @@ -20,6 +20,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:textSize="13sp" + android:gravity="top|center_horizontal" + android:lines="2" + android:ellipsize="end" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/iv_icon" /> diff --git a/app/src/main/res/layout/item_rss_article.xml b/app/src/main/res/layout/item_rss_article.xml index df31c7d70..311944d98 100644 --- a/app/src/main/res/layout/item_rss_article.xml +++ b/app/src/main/res/layout/item_rss_article.xml @@ -43,6 +43,7 @@ android:paddingLeft="16dp" android:visibility="gone" android:scaleType="centerCrop" + android:src="@drawable/image_rss_article" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="RtlHardcoded,RtlSymmetry" /> diff --git a/app/src/main/res/layout/item_rss_article_1.xml b/app/src/main/res/layout/item_rss_article_1.xml new file mode 100644 index 000000000..9c46e1aaf --- /dev/null +++ b/app/src/main/res/layout/item_rss_article_1.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_rss_article_2.xml b/app/src/main/res/layout/item_rss_article_2.xml new file mode 100644 index 000000000..55eaa7aac --- /dev/null +++ b/app/src/main/res/layout/item_rss_article_2.xml @@ -0,0 +1,54 @@ + + + + + + + + + + \ 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 ec591bc6f..95fd4f1f3 100644 --- a/app/src/main/res/layout/view_book_page.xml +++ b/app/src/main/res/layout/view_book_page.xml @@ -20,6 +20,19 @@ + + + + + android:layout_height="wrap_content" + android:layout_marginTop="8dp" /> + + android:checkableBehavior="single"> - + - + - + + + + + + + + + diff --git a/app/src/main/res/menu/book_source_item.xml b/app/src/main/res/menu/book_source_item.xml index a3cdcd258..c61d69dab 100644 --- a/app/src/main/res/menu/book_source_item.xml +++ b/app/src/main/res/menu/book_source_item.xml @@ -5,6 +5,10 @@ android:id="@+id/menu_top" android:title="@string/to_top" /> + + diff --git a/app/src/main/res/menu/book_source_sel.xml b/app/src/main/res/menu/book_source_sel.xml index 456bf4645..5ed76592d 100644 --- a/app/src/main/res/menu/book_source_sel.xml +++ b/app/src/main/res/menu/book_source_sel.xml @@ -22,6 +22,16 @@ android:title="@string/disable_explore" app:showAsAction="never" /> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/download.xml b/app/src/main/res/menu/download.xml index 452269d37..6c5b7a8f3 100644 --- a/app/src/main/res/menu/download.xml +++ b/app/src/main/res/menu/download.xml @@ -8,4 +8,25 @@ android:icon="@drawable/ic_play_24dp" app:showAsAction="ifRoom" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_bookshelf.xml b/app/src/main/res/menu/main_bookshelf.xml index 1c63a7629..628026f7c 100644 --- a/app/src/main/res/menu/main_bookshelf.xml +++ b/app/src/main/res/menu/main_bookshelf.xml @@ -8,6 +8,12 @@ android:title="@string/search" app:showAsAction="ifRoom" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/rss_main_item.xml b/app/src/main/res/menu/rss_main_item.xml new file mode 100644 index 000000000..19dc2f30a --- /dev/null +++ b/app/src/main/res/menu/rss_main_item.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/rss_source_item.xml b/app/src/main/res/menu/rss_source_item.xml index 17483eeec..77c3a18cd 100644 --- a/app/src/main/res/menu/rss_source_item.xml +++ b/app/src/main/res/menu/rss_source_item.xml @@ -5,6 +5,10 @@ android:id="@+id/menu_top" android:title="@string/to_top" /> + + diff --git a/app/src/main/res/menu/rss_source_sel.xml b/app/src/main/res/menu/rss_source_sel.xml index 48797b9e6..2c0b61e80 100644 --- a/app/src/main/res/menu/rss_source_sel.xml +++ b/app/src/main/res/menu/rss_source_sel.xml @@ -13,14 +13,18 @@ app:showAsAction="never" /> + + diff --git a/app/src/main/res/menu/theme_config.xml b/app/src/main/res/menu/theme_config.xml new file mode 100644 index 000000000..5033089ac --- /dev/null +++ b/app/src/main/res/menu/theme_config.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index c7eb91ae9..8ac7aa66a 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -7,7 +7,7 @@ @color/md_grey_900 @color/md_grey_850 @color/md_grey_800 - #AA111111 + #10303030 #69000000 diff --git a/app/src/main/res/values-zh-rHK/arrays.xml b/app/src/main/res/values-zh-rHK/arrays.xml index ebe6c25dd..315f18e35 100644 --- a/app/src/main/res/values-zh-rHK/arrays.xml +++ b/app/src/main/res/values-zh-rHK/arrays.xml @@ -20,6 +20,7 @@ 跟隨系統 亮色主題 暗色主題 + E-Ink(墨水屏) @@ -49,4 +50,9 @@ 系統等寬字體 + + 正常 + 粗體 + 細體 + \ 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 index 7f07c94e0..4f5ea6840 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -88,7 +88,7 @@ 下載已選擇的章節到本地 換源 - \u3000\u3000這是一款使用 Kotlin 全新開發的開源的閲讀應用程式,歡迎你的加入。關注公眾號[开源阅读软件]! + \u3000\u3000這是一款使用 Kotlin 全新開發的開源的閲讀應用程式,歡迎你的加入。關注公眾號[legado-top]! 閲讀3.0下載地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app @@ -375,6 +375,8 @@ 登錄 %s 成功 當前源沒有配置登陸地址 + 沒有上一頁 + 沒有下一頁 源名稱 (sourceName) @@ -401,7 +403,7 @@ 章節名稱規則 (ChapterName) 章節 URL 規則 (chapterUrl) VIP 標識 (isVip) - 章節信息 (ChapterInfo) + 更新時間 (ChapterInfo) 正文規則 (content) 正文下一頁 URL 規則 (nextContentUrl) webJs @@ -417,6 +419,7 @@ 描述規則 (ruleDescription) 圖片 url 規則 (ruleImage) 內容規則 (ruleContent) + 樣式 (style) 鏈接規則 (ruleLink) @@ -664,11 +667,12 @@ 關注公衆號 WeChat 你的支持是我更新的動力 - 公众号[开源阅读软件] + 公众号[开源阅读] 正在自動換源 點擊加入 信息 + 切換佈局 主色調 @@ -685,4 +689,20 @@ 夜間,強調色 夜間,背景色 夜間,導航欄顏色 + 隱藏頁眉 + 隱藏頁脚 + 自動換源 + 置底 + 文字兩端對齊 + 自動翻頁速度 + 地址排序 + 文章字體轉換 + 請選擇備份路徑 + 其它 + legado-top + 本地和WebDav壹起備份 + 優先從WebDav恢復,失敗時從本地恢復 + 選擇舊版備份文件夾 + 已啓用 + 已禁用 diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml new file mode 100644 index 000000000..5d4e2aaeb --- /dev/null +++ b/app/src/main/res/values-zh-rTW/arrays.xml @@ -0,0 +1,115 @@ + + + + @string/book_type_text + @string/book_type_audio + + + + 度小美 + 度小宇 + 度逍遙 + 度丫丫 + 度小嬌 + 度米朵 + 度博文 + 度小童 + 度小萌 + 百度騷男 + 百度評書 + 百度主持 + + + + @string/indent_0 + @string/indent_1 + @string/indent_2 + @string/indent_3 + @string/indent_4 + + + + .txt + .json + .xml + + + + @string/jf_convert_o + @string/jf_convert_j + @string/jf_convert_f + + + + 跟隨系統 + 亮色主題 + 暗色主題 + E-Ink(墨水屏) + + + + 自動 + 黑色 + 白色 + 跟隨背景 + + + + 預設 + 1分鐘 + 2分鐘 + 3分鐘 + 常亮 + + + + @string/default_path + @string/sys_folder_picker + @string/app_folder_picker + + + + @string/screen_unspecified + @string/screen_portrait + @string/screen_landscape + @string/screen_sensor + + + + iconMain + icon1 + icon2 + icon3 + icon4 + icon5 + icon6 + + + + 關閉 + 繁體轉簡體 + 簡體轉繁體 + + + + 系統預設字體 + 系統襯線字體 + 系統等寬字體 + + + + + 標題 + 時間 + 電量 + 頁數 + 進度 + 頁數及進度 + + + + 正常 + 粗體 + 細體 + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..672ad5e4d --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,709 @@ + + + 閱讀 + 閱讀·搜尋 + 閱讀需要存取記憶卡權限,請前往“設定”—“應用權限”—打開所需權限 + + + Home + 復原 + 匯入閱讀資料 + 建立子資料夾 + 建立legado資料夾作為備份資料夾 + 備份路徑 + 匯入舊版資料 + 匯入Github資料 + 淨化取代 + Send + + 提示 + 取消 + 確定 + 去設定 + 無法跳轉至設定介面 + + 點擊重試 + 正在載入 + 提醒 + 編輯 + 刪除 + 取代 + 取代淨化 + 配置取代淨化規則 + 暫無 + 啟用 + 取代淨化-搜尋 + 書架 + 收藏夾 + 收藏 + 已收藏 + 未收藏 + 訂閱 + 全部 + 最近閱讀 + 最後閱讀 + 更新日誌 + 書架還空著,先去添加吧! + 搜尋 + 下載 + 列表 + 網格三列 + 網格四列 + 網格五列 + 網格六列 + 書架布局 + 檢視 + 書城 + 添加本機 + 書源 + 書源管理 + 建立/匯入/編輯/管理書源 + 設定 + 主題設定 + 與介面/顏色相關的一些設定 + 其它設定 + 與功能相關的一些設定 + 關於 + 捐贈 + 退出 + 尚未儲存,是否繼續編輯 + 閱讀樣式設定 + 版本 + 本機 + 搜尋 + 來源: %s + 最近: %s + 書名 + 最新: %s + 是否將《%s》放入書架? + 共%s個Text文件 + 載入中… + 重試 + Web 服務 + 啟用Web服務 + web編輯書源 + http://%1$s:%2$d + 離線下載 + 離線下載 + 下載選擇的章節到本機 + 換源 + + \u3000\u3000這是一款使用Kotlin全新開發的開源的閱讀軟體,歡迎您的加入。關注公眾號[legado-top]! + + + 閱讀3.0下載網址:\nhttps://play.google.com/store/apps/details?id=io.legado.app + + Version %s + 自動重新整理 + 打開軟體時自動更新書籍 + 自動下載最新章節 + 更新書籍時自動下載最新章節 + 備份與復原 + WebDav設定 + WebDav設定/匯入舊版本資料 + 備份 + 復原 + 備份請給與儲存權限 + 復原請給與儲存權限 + 確認 + 取消 + 確認備份嗎? + 新備份會取代原有備份。\n備份資料夾YueDu + 確認復原嗎? + 復原書架會覆蓋現有書架。 + 備份成功 + 備份失敗 + 正在復原 + 復原成功 + 復原失敗 + 螢幕方向 + 跟隨感測器 + 橫向 + 豎向 + 跟隨系統 + 免責聲明 + 共%d章 + 介面 + 亮度 + 目錄 + 下一章 + 上一章 + 隱藏狀態欄 + 閱讀介面隱藏狀態欄 + 朗讀 + 正在朗讀 + 點擊打開閱讀介面 + 播放 + 正在播放 + 點擊打開播放介面 + 播放暫停 + 返回 + 重新整理 + 開始 + 停止 + 暫停 + 繼續 + 定時 + 朗讀暫停 + 正在朗讀(還剩%d分鐘) + 閱讀介面隱藏虛擬按鍵 + 隱藏導航欄 + 導航欄顏色 + GitHub + 評分 + 發送郵件 + 無法打開 + 分享失敗 + 無章節 + 添加網址 + 添加書籍網址 + 背景 + 作者 + 作者: %s + 朗讀停止 + 清除快取 + 成功清理快取 + 儲存 + 編輯源 + 編輯書源 + 禁用書源 + 建立書源 + 建立訂閱源 + 添加書籍 + 掃描 + 複製源 + 貼上源 + 源規則說明 + 檢查更新 + 掃描二維碼 + 掃描本機圖片 + 規則說明 + 分享 + 軟體分享 + 跟隨系統 + 添加 + 匯入書源 + 本機匯入 + 網路匯入 + 取代淨化 + 取代規則編輯 + 取代規則 + 取代為 + 封面 + + 音量鍵翻頁 + 點擊翻頁 + 點擊總是翻下一頁 + 翻頁動畫 + 螢幕超時 + 返回 + 選單 + 調節 + 滾動條 + 清除快取會刪除所有已儲存章節,是否確認刪除? + 書源共享 + 取代規則名稱 + 取代規則為空或者不滿足正規表示式要求 + 選擇操作 + 全選 + 全選(%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) + 樣式(style) + 連結規則(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 + 預設 + 主選單 + 點擊授予權限 + 閱讀需要存取記憶卡權限,請點擊下方的"授予權限"按鈕,或前往“設定”—“應用權限”—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的“選擇資料夾”,使用系統資料夾選擇器。 + 全文朗讀中不能朗讀選中文字 + 擴展到瀏海 + 更新目錄中 + 全程響應耳機按鍵 + 即使退出軟體也響應耳機按鍵 + 開發人員 + 聯繫我們 + 開源許可 + 其它 + legado-top + 關注公眾號 + 微信 + 您的支援是我更新的動力 + 公眾號[開源閱讀] + 正在自動換源 + 點擊加入 + + 訊息 + 隱藏頁首 + 隱藏頁尾 + 切換布局 + + + 主色調 + 強調色 + 背景色 + 底部操作欄顏色 + 白天 + 白天,主色調 + 白天,強調色 + 白天,背景色 + 白天,底欄色 + 夜間 + 夜間,主色調 + 夜間,強調色 + 夜間,背景色 + 夜間,底欄色 + 自動換源 + 文字兩端對齊 + 自動翻頁速度 + 地址排序 + 文章字體轉換 + 請選擇備份路徑 + 本地和WebDav壹起備份 + 優先從WebDav恢復,失敗時從本地恢復 + 選擇舊版備份文件夾 + 已啓用 + 已禁用 + + diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml index 40bc6684e..b6b32fa5b 100644 --- a/app/src/main/res/values/array_values.xml +++ b/app/src/main/res/values/array_values.xml @@ -23,6 +23,7 @@ 0 1 2 + 3 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index b1046096f..5b580aeb3 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -44,6 +44,7 @@ 跟随系统 亮色主题 暗色主题 + E-Ink(墨水屏) @@ -106,4 +107,10 @@ 页数及进度 + + 正常 + 粗体 + 细体 + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8d6199035..b730f36c9 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,7 +6,6 @@ #66666666 #FF578FCC - #FF212227 #eb4333 #439b53 @@ -16,7 +15,7 @@ @color/md_grey_50 @color/md_grey_100 @color/md_grey_200 - #AAFFFFFF + #7fffffff #00000000 #10000000 diff --git a/app/src/main/res/values/pref_key_value.xml b/app/src/main/res/values/pref_key_value.xml index e9aef95f4..a2a2611cb 100644 --- a/app/src/main/res/values/pref_key_value.xml +++ b/app/src/main/res/values/pref_key_value.xml @@ -1,22 +1,23 @@ - auto_refresh - list_screen_direction - full_screen - threads_num - user_agent - bookshelf_px - read_type - expandGroupFind - defaultToRead - autoDownload - checkUpdate + auto_refresh + list_screen_direction + full_screen + threads_num + user_agent + bookshelf_px + read_type + expandGroupFind + defaultToRead + autoDownload + checkUpdate + 开源阅读 - https://gitee.com/alanskycn/yuedu/blob/master/Rule/README.md - https://github.com/gedoor/legado - https://github.com/gedoor/legado/graphs/contributors - https://gedoor.github.io/MyBookshelf/disclaimer.html - https://gedoor.github.io/MyBookshelf/ - https://github.com/gedoor/legado/releases/latest - https://api.github.com/repos/gedoor/legado/releases/latest + https://celeter.github.io/ + https://github.com/gedoor/legado + https://github.com/gedoor/legado/graphs/contributors + https://gedoor.github.io/MyBookshelf/disclaimer.html + https://gedoor.github.io/MyBookshelf/ + https://github.com/gedoor/legado/releases/latest + https://api.github.com/repos/gedoor/legado/releases/latest diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index edf2698e6..ab9bdaa15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ 创建子文件夹 创建legado文件夹作为备份文件夹 备份路径 + 请选择备份路径 导入旧版数据 导入Github数据 净化替换 @@ -88,7 +89,7 @@ 下载选择的章节到本地 换源 - \u3000\u3000这是一款使用Kotlin全新开发的开源的阅读软件,欢迎您的加入。关注公众号[开源阅读软件]! + \u3000\u3000这是一款使用Kotlin全新开发的开源的阅读软件,欢迎您的加入。关注公众号[开源阅读]! 阅读3.0下载地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app @@ -308,6 +309,7 @@ 行距 段距 置顶 + 置底 自动展开发现 默认展开第一组发现 当前线程数 %s @@ -375,6 +377,8 @@ 登录%s 成功 当前源没有配置登陆地址 + 没有上一页 + 没有下一页 源名称(sourceName) @@ -401,7 +405,7 @@ 章节名称规则(ChapterName) 章节URL规则(chapterUrl) VIP标识(isVip) - 章节信息(ChapterInfo) + 更新时间(ChapterInfo) 正文规则(content) 正文下一页URL规则(nextContentUrl) webJs @@ -417,6 +421,7 @@ 描述规则(ruleDescription) 图片url规则(ruleImage) 内容规则(ruleContent) + 样式(style) 链接规则(ruleLink) @@ -661,18 +666,20 @@ 开发人员 联系我们 开源许可 - 其它 - 开源阅读软件 + 其它 + 开源阅读 关注公众号 微信 您的支持是我更新的动力 - 公众号[开源阅读软件] + 公众号[开源阅读] 正在自动换源 点击加入 信息 隐藏页眉 隐藏页脚 + 切换布局 + 文章字重切换 主色调 @@ -689,5 +696,13 @@ 夜间,强调色 夜间,背景色 夜间,底栏色 - + 自动换源 + 文字两端对齐 + 自动翻页速度 + 地址排序 + 本地和WebDav一起备份 + 优先从WebDav恢复,失败时从本地恢复 + 选择旧版备份文件夹 + 已启用 + 已禁用 diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 3c607cd9d..a5c5922e6 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -51,6 +51,12 @@ android:summary="@string/this_github_url" app:iconSpaceReserved="false" /> + + + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + android:key="webDavCreateDir" + android:defaultValue="true" + android:title="@string/mkdirs" + android:summary="@string/mkdirs_description" + app:allowDividerAbove="false" + app:allowDividerBelow="false" + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + android:key="backupUri" + android:title="@string/backup_path" + android:summary="@string/select_backup_path" + app:iconSpaceReserved="false" /> + android:key="web_dav_backup" + android:title="@string/backup" + android:summary="@string/backup_summary" + app:iconSpaceReserved="false" /> + android:key="web_dav_restore" + android:title="@string/restore" + android:summary="@string/restore_summary" + app:iconSpaceReserved="false" /> + android:key="import_old" + android:title="@string/menu_import_old_version" + android:summary="@string/import_old_summary" + app:iconSpaceReserved="false" /> diff --git a/app/src/main/res/xml/pref_config_read.xml b/app/src/main/res/xml/pref_config_read.xml index 6b55f0fcc..3b1e81f3c 100644 --- a/app/src/main/res/xml/pref_config_read.xml +++ b/app/src/main/res/xml/pref_config_read.xml @@ -36,6 +36,12 @@ android:key="hideNavigationBar" 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 7c63c98fe..37b223d60 100644 --- a/app/src/main/res/xml/pref_main.xml +++ b/app/src/main/res/xml/pref_main.xml @@ -30,14 +30,6 @@ android:icon="@drawable/ic_cfg_theme" app:iconSpaceReserved="false" /> - - diff --git a/build.gradle b/build.gradle index 21c80e3a6..051a1d77c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.0' 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' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20b544410..372426236 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Feb 25 08:10:32 CST 2020 +#Sat May 30 10:00:31 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip