diff --git a/.github/workflows/autoupdatefork.yml b/.github/workflows/autoupdatefork.yml new file mode 100644 index 000000000..9fa5d6425 --- /dev/null +++ b/.github/workflows/autoupdatefork.yml @@ -0,0 +1,32 @@ +#更新fork +name: update fork + +on: + schedule: + - cron: '0 16 * * *' #设置定时任务 + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.event.repository.owner.id == github.event.sender.id && github.actor != 'gedoor' }} + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install git + run: | + sudo apt-get update + sudo apt-get -y install git + - name: Set env + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "github-actions" + - name: Update fork + run: | + git remote add upstream https://github.com/gedoor/legado.git + git remote -v + git fetch upstream + git checkout master + git merge upstream/master + git push origin master diff --git a/.github/workflows/legado.jks b/.github/workflows/legado.jks new file mode 100644 index 000000000..89ea725fd Binary files /dev/null and b/.github/workflows/legado.jks differ diff --git a/.github/workflows/legado.yml b/.github/workflows/legado.yml new file mode 100644 index 000000000..01e8c8b8c --- /dev/null +++ b/.github/workflows/legado.yml @@ -0,0 +1,57 @@ +name: Android CI + +on: + release: + types: [published] + push: + branches: + - master +# tags: +# - '3.*' +# pull_request: +# branches: +# - master +# watch: +# types: [started] +# schedule: +# - cron: '0 4 * * *' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: clear 18PlusList.txt + run: | + echo "清空18PlusList.txt" + echo "">$GITHUB_WORKSPACE/app/src/main/assets/18PlusList.txt + - name: release apk sign + run: | + echo "给apk增加签名" + cp $GITHUB_WORKSPACE/.github/workflows/legado.jks $GITHUB_WORKSPACE/app/legado.jks + sed '$a\RELEASE_STORE_FILE=./legado.jks' $GITHUB_WORKSPACE/gradle.properties -i + sed '$a\RELEASE_KEY_ALIAS=legado' $GITHUB_WORKSPACE/gradle.properties -i + sed '$a\RELEASE_STORE_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i + sed '$a\RELEASE_KEY_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i + - name: apk live together + run: | + echo "设置apk共存" + sed "s/'.release'/'.releaseA'/" $GITHUB_WORKSPACE/app/build.gradle -i + sed 's/.release/.releaseA/' $GITHUB_WORKSPACE/app/google-services.json -i + - name: build with gradle + run: | + echo "开始进行release构建" + chmod +x gradlew + ./gradlew assembleAppRelease + - name : upload apk + uses: actions/upload-artifact@master + if: always() + with: + name: legado apk + path: ${{ github.workspace }}/app/build/outputs/apk/app/release 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/README.md b/README.md index 1c7bf34df..d01b120a2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,18 @@ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) ## 阅读3.0 -书源规则 https://celeter.github.io +* [书源规则](https://alanskycn.gitee.io/teachme/) +* [更新日志](/app/src/main/assets/updateLog.md) + +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B1.jpg) +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B2.jpg) +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B3.jpg) +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B4.jpg) +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B5.jpg) +![image](https://github.com/gedoor/gedoor.github.io/blob/master/images/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B6.jpg) + +### 阅读API +阅读3.0 提供了2种方式的API:`Web方式`和`Content Provider方式`。您可以在[这里](api.md)根据需要自行调用。 ## 免责声明 https://gedoor.github.io/MyBookshelf/disclaimer.html diff --git a/api.md b/api.md new file mode 100644 index 000000000..3a10e4507 --- /dev/null +++ b/api.md @@ -0,0 +1,169 @@ +# 阅读API +## 对于Web的配置 +您需要先在设置中启用"Web 服务"。 +## 使用 +### Web +以下说明假设您的操作在本机进行,且开放端口为1234。 +如果您要从远程计算机访问[阅读](),请将`127.0.0.1`替换成手机IP。 +#### 插入单个书源 +``` +URL = http://127.0.0.1:1234/saveSource +Method = POST +``` + +请求BODY内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt) + +#### 插入多个书源 +``` +URL = http://127.0.0.1:1234/saveSources +Method = POST +``` + +请求BODY内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 获取书源 +``` +URL = http://127.0.0.1:1234/getSource?url=xxx +Method = GET +``` + +获取指定URL对应的书源信息。 + +#### 获取所有书源 +``` +URL = http://127.0.0.1:1234/getSources +Method = GET +``` + +获取APP内的所有书源。 + +#### 删除多个书源 +``` +URL = http://127.0.0.1:1234/deleteSources +Method = POST +``` + +请求BODY内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 插入书籍 +``` +URL = http://127.0.0.1:1234/saveBook +Method = POST +``` + +请求BODY内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)。 + +#### 获取所有书籍 +``` +URL = http://127.0.0.1:1234/getBookshelf +Method = GET +``` + +获取APP内的所有书籍。 + +#### 获取书籍章节列表 +``` +URL = http://127.0.0.1:1234/getChapterList?url=xxx +Method = GET +``` + +获取指定图书的章节列表。 + +#### 获取书籍内容 + +``` +URL = http://127.0.0.1:1234/getBookContent?url=xxx&index=1 +Method = GET +``` + +获取指定图书的第`index`章节的文本内容。 + +### Content Provider +* 需声明`io.legado.READ_WRITE`权限 +* `providerHost`为`包名.readerProvider`, 如`io.legado.app.release.readerProvider`,不同包的地址不同,防止冲突安装失败 +* 以下出现的`providerHost`请自行替换 +#### 插入单个书源 +``` +URL = content://providerHost/source/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt) + +#### 插入多个书源 +``` +URL = content://providerHost/sources/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 获取书源 +``` +URL = content://providerHost/source/query?url=xxx +Method = query +``` + +获取指定URL对应的书源信息。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取所有书源 +``` +URL = content://providerHost/sources/query +Method = query +``` + +获取APP内的所有书源。 +用`Cursor.getString(0)`取出返回结果。 + +#### 删除多个书源 +``` +URL = content://providerHost/sources/delete +Method = delete +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。 + +#### 插入书籍 +``` +URL = content://providerHost/book/insert +Method = insert +``` + +创建`Key="json"`的`ContentValues`,内容为`JSON`字符串, +格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)。 + +#### 获取所有书籍 +``` +URL = content://providerHost/books/query +Method = query +``` + +获取APP内的所有书籍。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取书籍章节列表 +``` +URL = content://providerHost/book/chapter/query?url=xxx +Method = query +``` + +获取指定图书的章节列表。 +用`Cursor.getString(0)`取出返回结果。 + +#### 获取书籍内容 + +``` +URL = content://providerHost/book/content/query?url=xxx&index=1 +Method = query +``` + +获取指定图书的第`index`章节的文本内容。 +用`Cursor.getString(0)`取出返回结果。 diff --git a/app/build.gradle b/app/build.gradle index 7dadb803b..43a086553 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: "de.timfreiheit.resourceplaceholders" +apply plugin: 'de.timfreiheit.resourceplaceholders' apply plugin: 'io.fabric' androidExtensions { @@ -37,7 +37,6 @@ android { targetSdkVersion 29 versionCode gitCommits versionName version - flavorDimensions "versionCode" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" project.ext.set("archivesBaseName", name + "_" + version) multiDexEnabled true @@ -45,7 +44,8 @@ android { annotationProcessorOptions { arguments = [ "room.incremental" : "true", - "room.expandProjection": "true" + "room.expandProjection": "true", + "room.schemaLocation" : "$projectDir/schemas".toString() ] } } @@ -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,26 +66,30 @@ 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 { - outputFileName = "${name}_${defaultConfig.versionName}.apk" + def flavor = variant.productFlavors[0].name + outputFileName = "${name}_${flavor}_${defaultConfig.versionName}.apk" } } } - productFlavors{ - app{ - manifestPlaceholders = [APP_CHANNEL_VALUE:"app"] + flavorDimensions "mode" + productFlavors { + app { + dimension "mode" + manifestPlaceholders = [APP_CHANNEL_VALUE: "app"] } - google{ + google { + dimension "mode" applicationId "io.legado.play" - manifestPlaceholders = [APP_CHANNEL_VALUE:"google"] + manifestPlaceholders = [APP_CHANNEL_VALUE: "google"] } } 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 @@ -107,28 +111,30 @@ kapt { } dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation "com.android.support:multidex:1.0.3" //kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" //fireBase - implementation 'com.google.firebase:firebase-core:17.2.3' + implementation 'com.google.firebase:firebase-core:17.5.0' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' //androidX - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.3.1' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.media:media:1.1.0' - implementation 'androidx.preference:preference:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + 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.material:material:1.2.1' implementation 'com.google.android:flexbox:1.1.0' - implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.google.code.gson:gson:2.8.6' //lifecycle def lifecycle_version = '2.2.0' @@ -139,9 +145,10 @@ dependencies { def room_version = '2.2.5' implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" + testImplementation "androidx.room:room-testing:2.2.5" //paging - implementation 'androidx.paging:paging-runtime:2.1.2' + implementation 'androidx.paging:paging-runtime-ktx:2.1.2' //anko def anko_version = '0.10.8' @@ -152,24 +159,25 @@ 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" //规则相关 - implementation 'org.jsoup:jsoup:1.12.1' + implementation 'org.jsoup:jsoup:1.13.1' implementation 'cn.wanghaomiao:JsoupXpath:2.3.2' implementation 'com.jayway.jsonpath:json-path:2.4.0' //JS rhino implementation 'com.github.gedoor:rhino-android:1.4' - //Retrofit - implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0' - implementation 'com.squareup.retrofit2:retrofit:2.6.1' + //网络 + //noinspection GradleDependency + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' //Glide - implementation 'com.github.bumptech.glide:glide:4.9.0' + implementation 'com.github.bumptech.glide:glide:4.11.0' //webServer implementation 'org.nanohttpd:nanohttpd:2.3.1' @@ -182,15 +190,23 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' //apache - implementation 'org.apache.commons:commons-lang3:3.9' + implementation 'org.apache.commons:commons-lang3:3.11' implementation 'org.apache.commons:commons-text:1.8' //MarkDown - implementation 'ru.noties.markwon:core:3.0.2' + implementation 'ru.noties.markwon:core:3.1.0' //转换繁体 - implementation 'com.github.houbb:opencc4j:1.4.0' + implementation 'com.hankcs:hanlp:portable-1.7.8' + + //epub + implementation('com.positiondev.epublib:epublib-core:3.1') { + exclude group: 'org.slf4j' + exclude group: 'xmlpull' + } + //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 9af1203b9..215f31c33 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才起作用 @@ -158,9 +152,7 @@ -keep class **.data.**{*;} -dontwarn rx.** - -dontwarn okio.** --dontwarn retrofit2.** -dontwarn javax.annotation.** -dontwarn org.apache.log4j.lf5.viewer.** -dontnote org.apache.log4j.lf5.viewer.** @@ -171,21 +163,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 retrofit2.**{*;} -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.**{*;} @@ -199,7 +190,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.** @@ -220,7 +211,7 @@ -keep class javax.xml.**{*;} -keep class org.xmlpull.**{*;} --keep class org.simpleframework.xml.**{*;} +-keep class org.simpleframework.**{*;} -dontwarn org.simpleframework.xml.** -keepclassmembers class * { diff --git a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt index 86b312778..9820b40fb 100644 --- a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt @@ -1,5 +1,7 @@ package io.legado.app +import android.net.Uri +import android.util.Log import androidx.test.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 @@ -16,9 +18,12 @@ import org.junit.Assert.* @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test - fun useAppContext() { + fun testContentProvider() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() - assertEquals("cn.legado.book", appContext.packageName) - } + Log.d("test", + appContext.contentResolver.query(Uri.parse("content://io.legado.app.api.ReaderProvider/sources/query"),null,null,null,null) + !!.getString(0) + ) + } } diff --git a/app/src/debug/res/values-zh-rHK/strings.xml b/app/src/debug/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..07b0a5dbe --- /dev/null +++ b/app/src/debug/res/values-zh-rHK/strings.xml @@ -0,0 +1,5 @@ + + + 閲讀.D + 閲讀·D·搜索 + \ No newline at end of file diff --git a/app/src/debug/res/values-zh-rTW/strings.xml b/app/src/debug/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..07b0a5dbe --- /dev/null +++ b/app/src/debug/res/values-zh-rTW/strings.xml @@ -0,0 +1,5 @@ + + + 閲讀.D + 閲讀·D·搜索 + \ No newline at end of file diff --git a/app/src/debug/res/values-zh/strings.xml b/app/src/debug/res/values-zh/strings.xml new file mode 100644 index 000000000..e4464c88d --- /dev/null +++ b/app/src/debug/res/values-zh/strings.xml @@ -0,0 +1,4 @@ + + 阅读·D + 阅读·D·搜索 + \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index cb15b9df4..ffc41e3cd 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,4 +1,4 @@ - 悦读.debug - 悦读.debug·搜索 + legado·D + legado·D·search \ No newline at end of file diff --git a/app/src/google/res/values-zh-rCN/strings.xml b/app/src/google/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..ed2d735c4 --- /dev/null +++ b/app/src/google/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + + 阅读Pro + + \ No newline at end of file diff --git a/app/src/google/res/values-zh-rHK/strings.xml b/app/src/google/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..daa6a610a --- /dev/null +++ b/app/src/google/res/values-zh-rHK/strings.xml @@ -0,0 +1,6 @@ + + + + 閱讀Pro + + \ No newline at end of file diff --git a/app/src/google/res/values/strings.xml b/app/src/google/res/values/strings.xml new file mode 100644 index 000000000..773c6e15d --- /dev/null +++ b/app/src/google/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + legadoPro + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cfb6793b8..ef2c71ab2 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"> + @@ -15,6 +18,7 @@ + - + + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher1"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher2"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher3"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher4"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher5"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + + android:enabled="false" + android:icon="@mipmap/launcher6"> + + + android:launchMode="singleTask" + android:resource="@xml/shortcuts" /> - + - + android:alwaysRetainTaskState="true" + android:launchMode="singleTask" /> + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - + android:launchMode="singleTop" /> + - - - - - - - - - - + android:launchMode="singleTop" /> + - - - - - - - - - + - + - + + + + - + - + - + - + - - - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - + + - + 【备份与恢复】,选择【导入旧版本数据】。 -* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 -* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 +# 更新日志 +* 关注公众号 **[开源阅读]()** 菜单•软件下载 提前享受新版本。 +* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 +- 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 + +**2020/09/15** +* 修复导入排版字体重复报错的bug +* 添加正文搜索 by [h11128](https://github.com/h11128) + +**2020/09/12** +* web看书同步最新章 +* web写源增加图片样式等规则 +* 正文规则可以使用@get:{title}获取目录标题,js里使用title + +**2020/09/11** +* 修复一些bug +* 背景配置自由添加 + +**2020/09/10** +* 修复自动换源的bug +* 修复保存主题的bug +* 书源排序,分享,注释优化 by [h11128](https://github.com/h11128) + +**2020/09/09** +* 修复主题导入导出bug +* 优化分屏模式状态栏 +* 书源基本属性增加“书源注释” + +**2020/09/08** +* 页眉页脚跟随背景 +* 主题导入导出 + +**2020/09/07** +* 订阅源和替换规则添加滑动选择 +* 修复排版配置导入导出 +* 订阅界面添加下载文件功能 + +**2020/09/06** +* 优化翻页 +* EInk模式独立背景 +* 阅读排版配置导入导出,包括背景和字体,支持网络导入 + +**2020/09/03** +* 修复替换中的回车消失的bug +* 所有内容恢复htmlFormat, 在想其它办法解决丢失一些内容的问题 +* 图片(漫画)支持导出 + +**2020/09/02** +* 搜索url支持put,get,js里使用java.put,java.get +* 对于搜索重定向的源,可以使用此方法获得重定向后的url +``` + +var url='https://www.yooread.net/e/search/index.php,'+JSON.stringify({ +"method":"POST", +"body":"show=title&tempid=1&keyboard="+key +}); +String(java.connect(url).raw().request().url()) + +``` +* 正文合并后替换规则支持所有规则写法,包括js + +**2020/09/01** +* 导入书源列表添加全不选 +* 详情页菜单添加清理缓存,清理当前书籍缓存 +* 修复滑动选择,选择数量不更新的bug +* 字体跟随背景,每个背景对应一个字体 +* 优化图片下载 + +**2020/08/29** +* 修复一个null引起的崩溃bug +* 修复我的界面滚动时图标消失的bug +* 修复从详情页目录打开章节内容不对的bug +* 书源选择增加滑动选择, 选择框区域滑动时进行选择 by [Mupceet](https://github.com/Mupceet) +* 请求头,支持http代理,socks4 socks5代理设置 by [10bits](https://github.com/10bits) +``` +socks5代理 +{ + "proxy":"socks5://127.0.0.1:1080" +} +http代理 +{ + "proxy":"http://127.0.0.1:1080" +} +支持代理服务器验证 +{ + "proxy":"socks5://127.0.0.1:1080@用户名@密码" +} +注意:这些请求头是无意义的,会被忽略掉 +``` + +**2020/08/28** +* 修复一些bug +* 换源不再改变书名和作者,防止换到一些错误的书源后不能再换源 + +**2020/08/27** +* 修复主题bug +* 修复封面bug +* 优化书籍更新,搜索,换源 +* e-ink模式不再固定背景 + +**2020/08/26** +* js添加java.encodeURI(speakText),用于解决js编码时有~的语句朗读不出来 +* 修复书名太长删除阅读记录按钮不显示的bug +* 完成本地书籍编码选择 + +**2020/08/25** +* 阅读记录可以删除了 +* 修复翻页模式选择颜色问题 +* 修复toolbar在一些情况下文字颜色不对的bug +* 多设备阅读记录叠加 +* 封面链接支持修改headers + +**2020/08/24** +* 应用被杀死时停止朗读 +* 默认封面添加删除操作 +* 备份阅读记录 +* 书源添加移除分组支持多选,多个分组以逗号(中英均可)隔开 +* 可以自定义在线朗读了 + +**2020/08/22** +* 添加阅读时间记录 + +**2020/08/21** +* 图片(漫画源)支持离线下载了 + +**2020/08/20** +* 正文图片(漫画源)链接支持修改headers + +**2020/08/19** +* 选择文本替换时带入书名和书源 + +**2020/08/16** +* 添加亮度调节控件显示开关 +* 添加应用内语言切换 +* 底栏颜色限制去除,自动适配 + +**2020/08/12** +* 增加了Content Provider 接口支持 by [w568w](https://github.com/w568w) +* 修复阅读界面加入书架后,书籍详情页还是显示加入书架按钮的bug +* 修复低版本手机自动阅读速度拉动最左边崩溃的bug +* 给亮度调节加个半透明背景,很多人找不到 +* 修复替换分组选择无效的bug +* 备份添加书签 +* 修复web端进度更新后手机端进入阅读界面进度不变的bug +* 增加了txt目录规则备份 +* 优化了导入功能,导入之前对比已有书源,可选择性导入 +* 其它一些bug修复 + +**2020/08/08** +* 继续适配主题,现在应该所有地方都按照主题变色了 +* 朗读定时增加到3个小时,朗读暂停恢复后继续定时 +* 优化了主题颜色选择,会影响体验的颜色禁止选,会有提示 +* 订阅规则下一页支持页数,下一页规则填page + +**2020/08/07** +* 修复其它一些主题色没有适配到的地方 +* 添加默认启用替换配置 + +**2020/08/06** +* 菜单背景根随主题色 +* 修复其它一些主题色没有适配到的地方 +* 取消图片颜色为FULL时的自动滚动 +* 其它一些优化,升级库文件之类 +* 显示订阅加入恢复忽略列表 + +**2020/08/03** +* 修复一些主题色没有适配到的地方 +* 尝试修复书架最新章节更新不及时的bug + +**2020/08/02** +* 阅读菜单底部几个按钮的背景动态设置为底部操作栏颜色 +* 优化书签功能,解决一些bug + +**2020/07/29** +* 正文图片样式为FULL的自动为滚动模式 + +**2020/07/28** +* 长图正文规则添加图片样式FULL,可以滚动浏览了 + +**2020/07/26** +* 优化翻页,加快翻页速度 + +**2020/07/25** +* 正文规则添加多页合并后的替换规则,格式同样是##regex##replaceTo +* 正文图片添加长按缩放 +* 正文规则添加图片样式规则,可以设置为FULL +* 其它一些bug修复 + +**2020/07/21** +* 优化文字选择,不再缓存 +* 添加忽略恢复列表,方便不同手机配置不同 +* 其它一些bug修复 + +**2020/07/19** +* 添加自定义默认封面 +* 修复封面选择本地图片时书架不显示的bug + +**2020/07/14** +* 添加英文语言 by [52fisher](https://github.com/52fisher) + +**2020/07/13** +* 在线阅读图片支持测试成功,最好把替换净化关了,防止图片url不对 +* 书源保留img标签就行,@html自动保留标签 + +**2020/07/12** +* epub显示图片,未完善 +* 在线阅读也支持图片,还未测试 + +**2020/07/11** +* epub可以正确识别书名和作者了 +* epub封面正确显示 + +**2020/07/10** +* 修复一些窗口再墨水屏上背景透明的问题 +* 添加epub支持 +* web阅读时记住进度 +* 导入书源时系统文件选择器可以选择json文件 + +**2020/07/06** +* 优化下载 + +**2020/07/05** +* 修复夜间模式底栏颜色调整无效的bug +* 【web看书】加了翻页、排序等 by [Celeter](https://github.com/Celeter) +* 两部xx' is recognized as a title by [52fisher](https://github.com/52fisher) +* 添加下载错误日志,从下载菜单浏览 +* 修复vip标识引发的bug + +**2020/07/04** +* 修复滚动bug +* 其它一些优化 + +**2020/07/03** +* 修复关闭两端对齐是朗读高亮不准确的bug +* 添加文字底部对齐开关 + +**2020/06/25** +* E-Ink模式合并到主题模式里, E-Ink模式不能修改阅读界面背景和文字颜色 +* 添加判断,防止背景透明引起重影,花屏问题 + +**2020/06/22** +* 修复xpath获取正文多了许多逗号的bug +* 修复检验有效书源移除失效分组失败的bug + +**2020/06/21** +* 双击书架图标返回顶部 + +**2020/06/20** +* 适配NavigationBar + +**2020/06/19** +* 修复eInk bug +* 修复分组下载bug +* 导入本地添加滚动条 + +**2020/06/18** +* fadeapp.widgets:scrollless-recyclerView导致有些手机重影,暂时去除 +* 下载界面添加分组 +* 修复eInk bug + +**2020/06/17** +* 修复更新书架时更新禁止更新的问题 +* 修复导入旧版本数据问题 + +**2020/06/16** +* 刷新时只刷新当前书架 +* 修复恢复备份需要退出重进的问题 +* 保存打开 E-Ink 模式前的主题、翻页动画,关闭后恢复之前的配置, 现在可以切着玩了 +* 修复因繁体语言导致的崩溃bug + +**2020/06/15** +* 添加 E-Ink 模式 by [Modificator](https://github.com/Modificator) +* 修复发现打开书时可能的错误 + +**2020/06/14** +* 修复txt文件目录识别 +* 书源分组添加已启用已禁用 + +**2020/06/13** +* 优化搜索 + +**2020/06/12** +* 修复分组变化的bug + +**2020/06/10** +* 正文字体的粗细选择增加可以选择细体(Android O生效) by [hingbong](https://github.com/hingbong) +* 修复bug + +**2020/06/09** +* 修复从发现界面打开已在书架的书时,显示不对的问题 + +**2020/06/07** +* 优化书源检测,自定义搜索关键词 +* 失效书源如果校验为有效会去掉失效标志 + +**2020/06/06** +* 修复一些bug,包括从阅读界面退出后还是显示红色更新的bug + +**2020/06/03** +* zh-TW translation by david082321 +* 修复音频播放时播放速度调节会再下一章失效的bug + +**2020/05/31** +* 更新到android studio 4.0 +* 书源排序添加按url +* 去除朗读通知的进度条 +* 修复恢复问题,暂时去除混淆 + +**2020/05/24** +* 添加自动翻页速度调节 + +**2020/05/23** +* 添加文字两端对齐配置 + +**2020/05/20** +* Rss列表增加一种显示样式 + +**2020/05/18** +* 修复http://alanskycn.gitee.io/书源导入失败问题,被屏蔽UA了 +* Rss列表添加样式切换 + +**2020/05/17** +* 自动翻页功能完成 +* 替换规则输入时弹出辅助输入条 + +**2020/05/10** +* 添加识别rss分组中的频道信息,在菜单中可以切换频道 from [yangyxd](https://github.com/yangyxd) +* 源管理添加置底,批量置顶,批量置地 +* 封面选择本地图片完成 + +**2020/05/04** +* 优化txt文件目录解析 + +**2020/05/03** +* 优化一些界面显示问题 +* 订阅源添加style +* 修复一些重复目录的bug + +**2020/05/02** +* 修复不停换源的bug +* 修复本地书籍自动换源 +* 修复书源校验的一些问题 + +**2020/05/01** +* 尝试修复朗读时可能错位的bug +* 添加自动换源配置 +* 换源添加禁用菜单 + +**2020/04/29** +* 修复bug +* 订阅界面添加长按菜单 + +**2020/04/26** +* 添加导入旧的书源转换 +* 修复不自动朗读下一章的bug + +**2020/04/25** +* 修复翻页按键设置为空时崩溃的bug +* 翻页按键优先自定义按键,可覆盖音量按键 +* 写书源时的辅助键盘添加※ +* 更改了书源格式,不再需要转义符 + +**2020/04/24** +* 坚果云最近调整了策略,必须使用应用密码才能备份,用户信息,安全,第三方应用 +* text目录规则添加id字段,负值为系统自带规则 +* 其它一些优化 + +**2020/04/20** +* 优化阅读界面信息显示 + +**2020/04/19** +* 添加阅读界面各种信息设置 + +**2020/04/18** +* feat: 中文简繁处理库换成 HanLP, 中文增加 zh-rHK 翻译, [hingbong](https://github.com/hingbong) +* 修复更新时间不对的bug + +**2020/04/13** +* 去除rss朗读时的引号 + +**2020/04/13** +* 修复调用webView返回结果多了引号的bug + +**2020/04/12** +* 解决无法取消加粗的bug +* 修复换源自动加入书架的bug + +**2020/04/09** +* 修复书架刷新闪烁 + +**2020/04/08** +* 可以隐藏书架未分组 + +**2020/04/07** +* 书架添加未分组,有未分组书籍时自动显示 +* 其它一些优化 + +**2020/04/04** +* 优化备份逻辑 +* 修复订阅分类太多显示不全的bug +* 修复一些分类要手动刷新的问题 + +**2020/04/02** +* 书架书名和作者作为唯一值 +* 添加订阅分类,分类规则和发现一样,分类一::url1 && 分类2::url2 + +**2020/03/29** +* 添加退出软件后是否响应耳机按键的开关 +* 优化书源校验 + +**2020/03/26** +* 修复txt目录bug +* 最近工作比较忙,只有晚上有时间写软件,bug之类的不要催,白天不回消息 + +**2020/03/25** +* 修复7.1.1的网络问题,是retrofit2库最新版本的bug,暂时退回上版本 +* 去除下载路径的配置,减少错误 +* 添加隐藏状态栏是否扩展到刘海 + +**2020/03/24** +* txt文件第一章之前的文字不再放到简介里 +* 优化txt目录识别,章节超过3万字判断为目录识别错误重新识别 +* 修复文件关联 by [wqfantexi](https://github.com/wqfantexi) **2020/03/22** -* 添加文件关联 by wqfantexi +* 添加文件关联 by [wqfantexi](https://github.com/wqfantexi) * 手动排序可以了,在书架整理里面拖动排序 * 删除分组时同时删除书籍里的分组信息,下次添加新分组时不会自动出现在分组内 * 修复换源丢失分组信息的bug @@ -19,14 +438,14 @@ * 修复rss请求头无效bug **2020/03/19** -* 美化界面我的 by yangyxd +* 美化界面我的 by [yangyxd](https://github.com/yangyxd) * 优化搜索 **2020/03/18** * 尝试修复搜索时崩溃 * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 -* 美化界面 by yangyxd +* 美化界面 by [yangyxd](https://github.com/yangyxd) * 订阅后台播放 **2020/03/16** @@ -51,8 +470,8 @@ **2020/03/11** * 修复调节上边距时下边距一起动的bug -* 适配沚水的web阅读 by 六月 -* 分组管理页面调整 by yangyxd +* 适配沚水的web阅读 by [六月](https://github.com/Celeter) +* 分组管理页面调整 by [yangyxd](https://github.com/yangyxd) **2020/03/10** * 优化文字选择菜单弹出位置 @@ -61,13 +480,13 @@ **2020/03/09** * 底部文字对齐 -* 主题添加阴影调节 by yangyxd +* 主题添加阴影调节 by [yangyxd](https://github.com/yangyxd) **2020/03/08** * 订阅长按保存图片 * 订阅全屏播放 * 书架全部分组可以隐藏了 -* 内置web书架基本能用了 by 六月 +* 内置web书架基本能用了 by [六月](https://github.com/Celeter) * 书架整理加入未分组 * 显示总进度 * 隐藏状态栏时,标题显示在上方 @@ -139,7 +558,7 @@ **2020/02/26** * 修复仿真翻页 -* 功能添加: 选择默认字体时, 可选择字体默认字体(非衬线), 系统衬线字体, 系统等宽字体by hingbong +* 功能添加: 选择默认字体时, 可选择字体默认字体(非衬线), 系统衬线字体, 系统等宽字体by [hingbong](https://github.com/hingbong) **2020/02/25** * 优化文本选择和滚动,感觉很完美了 @@ -263,66 +682,11 @@ * 适配Android 10 权限 * 导入旧版本配置不在需要存储权限 * 选择字体不在需要存储权限 - -**2019/12/30** * 修改书源调试 - 调试搜索>>输入关键字,如:`系统` - 调试发现>>输入发现URL,如:`月票榜::https://www.qidian.com/rank/yuepiao?page={{page}}` - 调试详情页>>输入详情页URL,如:`https://m.qidian.com/book/1015609210` - 调试目录页>>输入目录页URL,如:`++https://www.zhaishuyuan.com/read/30394` - 调试正文页>>输入正文页URL,如:`--https://www.zhaishuyuan.com/chapter/30394/20940996` - * 修改订阅中自动添加style的情景 订阅源的内容规则中存在` - -
- - + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/app/src/main/assets/web/css/detail.42c41bd6.css b/app/src/main/assets/web/css/detail.42c41bd6.css deleted file mode 100644 index 693f789a8..000000000 --- a/app/src/main/assets/web/css/detail.42c41bd6.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";.detail-wrapper[data-v-6d476c76]{padding:2% 0}.detail-wrapper .detail .bar .el-breadcrumb[data-v-6d476c76]{font-size:24px;font-weight:500;line-height:48px}.detail-wrapper .detail .bar .el-breadcrumb .index[data-v-6d476c76]{color:#333}.detail-wrapper .detail .bar .el-breadcrumb .sub[data-v-6d476c76]{color:#676767}.detail-wrapper .detail .el-divider[data-v-6d476c76]{margin-top:2%}.detail-wrapper .detail .catalog[data-v-6d476c76]{margin-top:2%;display:grid;grid-template-columns:repeat(auto-fill,300px);-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.detail-wrapper .detail .catalog .note[data-v-6d476c76]{width:200px;font-weight:500;font-size:14px;line-height:40px;padding-left:12px;padding-right:12px;word-wrap:break-word;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;text-align:left}@font-face{font-family:FZZCYSK;src:local("☺"),url(../fonts/popfont.f39ecc1a.ttf);font-style:normal;font-weight:400}.cata-wrapper[data-v-8c647fa4]{margin:-16px;padding:18px 0 24px 25px}.cata-wrapper .title[data-v-8c647fa4]{font-size:18px;font-weight:400;font-family:FZZCYSK;margin:0 0 20px 0;color:#ed4259;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;border-bottom:1px solid #ed4259}.cata-wrapper .data-wrapper[data-v-8c647fa4]{height:300px;overflow:auto}.cata-wrapper .data-wrapper .cata[data-v-8c647fa4]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.cata-wrapper .data-wrapper .cata .selected[data-v-8c647fa4]{color:#eb4259}.cata-wrapper .data-wrapper .cata .log[data-v-8c647fa4]{width:50%;height:40px;cursor:pointer;float:left;font:16px/40px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.cata-wrapper .data-wrapper .cata .log .log-text[data-v-8c647fa4]{margin-right:26px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.cata-wrapper .night[data-v-8c647fa4] .log{border-bottom:1px solid #666}.cata-wrapper .day[data-v-8c647fa4] .log{border-bottom:1px solid #f2f2f2}@font-face{font-family:iconfont;src:url(../fonts/iconfont.f9a3fb0e.woff) format("woff")}[data-v-3d7e2fe5] .iconfont,[data-v-3d7e2fe5] .moon-icon{font-family:iconfont;font-style:normal}.settings-wrapper[data-v-3d7e2fe5]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin:-13px;width:478px;height:300px;text-align:left;padding:40px 0 40px 24px;background:#ede7da url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII=) repeat}.settings-wrapper .settings-title[data-v-3d7e2fe5]{font-size:18px;line-height:22px;margin-bottom:28px;font-family:FZZCYSK;font-weight:400}.settings-wrapper .setting-list ul[data-v-3d7e2fe5]{list-style:none outside none;margin:0;padding:0}.settings-wrapper .setting-list ul li[data-v-3d7e2fe5]{list-style:none outside none}.settings-wrapper .setting-list ul li i[data-v-3d7e2fe5]{font:12px/16px PingFangSC-Regular,-apple-system,Simsun;display:inline-block;min-width:48px;margin-right:16px;vertical-align:middle;color:#666}.settings-wrapper .setting-list ul li .theme-item[data-v-3d7e2fe5]{line-height:32px;width:34px;height:34px;margin-right:16px;border-radius:100%;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul li .theme-item .iconfont[data-v-3d7e2fe5]{display:none}.settings-wrapper .setting-list ul li .selected[data-v-3d7e2fe5]{color:#ed4259}.settings-wrapper .setting-list ul li .selected .iconfont[data-v-3d7e2fe5]{display:inline}.settings-wrapper .setting-list ul .font-list[data-v-3d7e2fe5]{margin-top:28px}.settings-wrapper .setting-list ul .font-list .font-item[data-v-3d7e2fe5]{width:78px;height:34px;cursor:pointer;margin-right:16px;border-radius:2px;text-align:center;vertical-align:middle;display:inline-block;font:14px/34px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.settings-wrapper .setting-list ul .font-list .font-item[data-v-3d7e2fe5]:hover,.settings-wrapper .setting-list ul .font-list .selected[data-v-3d7e2fe5]{color:#ed4259;border:1px solid #ed4259}.settings-wrapper .setting-list ul .font-size[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width[data-v-3d7e2fe5]{margin-top:28px}.settings-wrapper .setting-list ul .font-size .resize[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width .resize[data-v-3d7e2fe5]{display:inline-block;width:274px;height:34px;vertical-align:middle;border-radius:2px}.settings-wrapper .setting-list ul .font-size .resize span[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width .resize span[data-v-3d7e2fe5]{width:89px;height:34px;line-height:34px;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul .font-size .resize span em[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width .resize span em[data-v-3d7e2fe5]{font-style:normal}.settings-wrapper .setting-list ul .font-size .resize .less[data-v-3d7e2fe5]:hover,.settings-wrapper .setting-list ul .font-size .resize .more[data-v-3d7e2fe5]:hover,.settings-wrapper .setting-list ul .read-width .resize .less[data-v-3d7e2fe5]:hover,.settings-wrapper .setting-list ul .read-width .resize .more[data-v-3d7e2fe5]:hover{color:#ed4259}.settings-wrapper .setting-list ul .font-size .resize .lang[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width .resize .lang[data-v-3d7e2fe5]{color:#a6a6a6;font-weight:400;font-family:FZZCYSK}.settings-wrapper .setting-list ul .font-size .resize b[data-v-3d7e2fe5],.settings-wrapper .setting-list ul .read-width .resize b[data-v-3d7e2fe5]{display:inline-block;height:20px;vertical-align:middle}.night[data-v-3d7e2fe5] .selected,.night[data-v-3d7e2fe5] .theme-item{border:1px solid #666}.night[data-v-3d7e2fe5] .moon-icon{color:#ed4259}.night[data-v-3d7e2fe5] .font-list .font-item,.night[data-v-3d7e2fe5] .resize{border:1px solid #666;background:rgba(45,45,45,.5)}.night[data-v-3d7e2fe5] .resize b{border-right:1px solid #666}.day[data-v-3d7e2fe5] .theme-item{border:1px solid #e5e5e5}.day[data-v-3d7e2fe5] .selected{border:1px solid #ed4259}.day[data-v-3d7e2fe5] .moon-icon{display:inline;color:hsla(0,0%,100%,.2)}.day[data-v-3d7e2fe5] .font-list .font-item{background:hsla(0,0%,100%,.5);border:1px solid rgba(0,0,0,.1)}.day[data-v-3d7e2fe5] .resize{border:1px solid #e5e5e5;background:hsla(0,0%,100%,.5)}.day[data-v-3d7e2fe5] .resize b{border-right:1px solid #e5e5e5}p[data-v-6ee085ae]{display:block;word-wrap:break-word;word-break:break-all}[data-v-08ce53b2] .pop-setting{margin-left:68px;top:0}[data-v-08ce53b2] .pop-cata{margin-left:10px}.chapter-wrapper[data-v-08ce53b2]{padding:0 4%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.chapter-wrapper[data-v-08ce53b2] .no-point{pointer-events:none}.chapter-wrapper .tool-bar[data-v-08ce53b2]{position:fixed;top:0;left:50%;z-index:100}.chapter-wrapper .tool-bar .tools[data-v-08ce53b2]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.chapter-wrapper .tool-bar .tools .tool-icon[data-v-08ce53b2]{font-size:18px;width:58px;height:48px;text-align:center;padding-top:12px;cursor:pointer;outline:none}.chapter-wrapper .tool-bar .tools .tool-icon .iconfont[data-v-08ce53b2]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .tool-bar .tools .tool-icon .icon-text[data-v-08ce53b2]{font-size:12px}.chapter-wrapper .read-bar[data-v-08ce53b2]{position:fixed;bottom:0;right:50%;z-index:100}.chapter-wrapper .read-bar .tools[data-v-08ce53b2]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.chapter-wrapper .read-bar .tools .tool-icon[data-v-08ce53b2]{font-size:18px;width:42px;height:31px;padding-top:12px;text-align:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;outline:none;margin-top:-1px}.chapter-wrapper .read-bar .tools .tool-icon .iconfont[data-v-08ce53b2]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .chapter-bar .el-breadcrumb .item[data-v-08ce53b2]{font-size:14px;color:#606266}.chapter-wrapper .chapter[data-v-08ce53b2]{font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif;text-align:left;padding:0 65px;min-height:100vh;width:670px;margin:0 auto}.chapter-wrapper .chapter[data-v-08ce53b2] .el-icon-loading{font-size:36px;color:#b5b5b5}.chapter-wrapper .chapter[data-v-08ce53b2] .el-loading-text{font-weight:500;color:#b5b5b5}.chapter-wrapper .chapter .content[data-v-08ce53b2]{font-size:18px;line-height:1.8;overflow:hidden;font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif}.chapter-wrapper .chapter .content .title[data-v-08ce53b2]{margin-bottom:57px;font:24px/32px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.chapter-wrapper .chapter .content .bottom-bar[data-v-08ce53b2],.chapter-wrapper .chapter .content .top-bar[data-v-08ce53b2]{height:64px}.day[data-v-08ce53b2] .popup{-webkit-box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04)}.day[data-v-08ce53b2] .tool-icon{border:1px solid rgba(0,0,0,.1);margin-top:-1px;color:#000}.day[data-v-08ce53b2] .tool-icon .icon-text{color:rgba(0,0,0,.4)}.day[data-v-08ce53b2] .chapter{border:1px solid #d8d8d8;color:#262626}.night[data-v-08ce53b2] .popup{-webkit-box-shadow:0 2px 4px rgba(0,0,0,.48),0 0 6px rgba(0,0,0,.16);box-shadow:0 2px 4px rgba(0,0,0,.48),0 0 6px rgba(0,0,0,.16)}.night[data-v-08ce53b2] .tool-icon{border:1px solid #444;margin-top:-1px;color:#666}.night[data-v-08ce53b2] .tool-icon .icon-text{color:#666}.night[data-v-08ce53b2] .chapter{border:1px solid #444;color:#666}.night[data-v-08ce53b2] .popper__arrow{background:#666} \ 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..d44d08651 100644 --- a/app/src/main/assets/web/index.html +++ b/app/src/main/assets/web/index.html @@ -14,7 +14,7 @@
基本
-
源URL :
+
源域名 :
@@ -33,6 +33,11 @@
+
+
源注释 :
+ +
登录地址:
@@ -245,13 +250,23 @@
脚本注入:
+ placeholder="注入javascript,用于模拟鼠标点击等,必须有返回值,一般为String类型">
资源正则:
+
+
替换规则:
+ +
+
+
图片样式:
+ +

其它规则
@@ -348,19 +363,19 @@
diff --git a/app/src/main/assets/web/index.js b/app/src/main/assets/web/index.js index fb2298bbd..9d09d50de 100644 --- a/app/src/main/assets/web/index.js +++ b/app/src/main/assets/web/index.js @@ -18,7 +18,7 @@ function hashParam(key, val) { } } // 创建书源规则容器对象 -const RuleJSON = (() => { +function Container() { let ruleJson = {}; let searchJson = {}; let exploreJson = {}; @@ -36,26 +36,31 @@ 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; -})(); +} // 选项卡Tab切换事件处理 function showTab(tabName) { $$('.tabtitle>*').forEach(node => { node.className = node.className.replace(' this', ''); }); @@ -101,6 +106,7 @@ function HttpPost(url, data) { } // 将书源表单转化为书源对象 function rule2json() { + let RuleJSON = Container(); // 转换base Object.keys(RuleJSON).forEach(key => { if (!key.startsWith("rule")) { @@ -110,38 +116,53 @@ function rule2json() { // 转换搜索规则 let searchJson = {}; - Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { - searchJson[key] = $('#' + 'ruleSearch_' + key).value; + //Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + Object.keys(RuleJSON.ruleSearch).forEach(key => { + if ($('#' + 'ruleSearch_' + key).value) + 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 => { - exploreJson[key] = $('#' + 'ruleExplore_' + key).value; + //Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + Object.keys(RuleJSON.ruleExplore).forEach(key => { + if ($('#' + 'ruleExplore_' + key).value) + 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 => { - bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value; + //Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + Object.keys(RuleJSON.ruleBookInfo).forEach(key => { + if ($('#' + 'ruleBookInfo_' + key).value) + 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 => { - tocJson[key] = $('#' + 'ruleToc_' + key).value; + //Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + Object.keys(RuleJSON.ruleToc).forEach(key => { + if ($('#' + 'ruleToc_' + key).value) + 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 => { - contentJson[key] = $('#' + 'ruleContent_' + key).value; + //Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + Object.keys(RuleJSON.ruleContent).forEach(key => { + if ($('#' + 'ruleContent_' + key).value) + 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); @@ -153,6 +174,7 @@ function rule2json() { } // 将书源对象填充到书源表单 function json2rule(RuleEditor) { + let RuleJSON = Container(); // 转换base Object.keys(RuleJSON).forEach(key => { if (!key.startsWith("rule")) { @@ -171,40 +193,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/js/about.2589b5fe.js b/app/src/main/assets/web/js/about.2589b5fe.js deleted file mode 100644 index 9fe4335aa..000000000 --- a/app/src/main/assets/web/js/about.2589b5fe.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about"],{"4fed":function(t,e,s){"use strict";var a=s("708f"),i=s.n(a);i.a},"708f":function(t,e,s){},"7b5b":function(t,e,s){},"7e43":function(t,e,s){t.exports=s.p+"img/noCover.b5c48bc1.jpeg"},d504:function(t,e,s){"use strict";s.r(e);var a=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"index-wrapper"},[a("div",{staticClass:"navigation-wrapper"},[a("div",{staticClass:"navigation-title"},[t._v(" 阅读 ")]),a("div",{staticClass:"navigation-sub-title"},[t._v(" 清风不识字,何故乱翻书 ")]),a("div",{staticClass:"search-wrapper"},[a("el-input",{staticClass:"search-input",attrs:{size:"mini",placeholder:"搜索书籍"},model:{value:t.search,callback:function(e){t.search=e},expression:"search"}},[a("i",{staticClass:"el-input__icon el-icon-search",attrs:{slot:"prefix"},slot:"prefix"})])],1),a("div",{staticClass:"recent-wrapper"},[a("div",{staticClass:"recent-title"},[t._v(" 最近阅读 ")]),a("div",{staticClass:"reading-recent"},[a("el-tag",{staticClass:"recent-book",class:{"no-point":""==t.readingRecent.url},attrs:{type:"warning"},on:{click:function(e){return t.toDetail(t.readingRecent.url,t.readingRecent.name)}}},[t._v(" "+t._s(t.readingRecent.name)+" ")])],1)]),a("div",{staticClass:"setting-wrapper"},[a("div",{staticClass:"setting-title"},[t._v(" 基本设定 ")]),a("div",{staticClass:"setting-item"},[a("el-tag",{staticClass:"setting-connect",class:{"no-point":t.newConnect},attrs:{type:t.connectType},on:{click:t.setIP}},[t._v(" "+t._s(t.connectStatus)+" ")])],1)]),a("div",{staticClass:"bottom-icons"},[a("a",{attrs:{href:"https://github.com/zsakvo/web-yuedu",target:"_blank"}},[a("div",{staticClass:"bottom-icon"},[a("img",{attrs:{src:s("fa39"),alt:""}})])])])]),a("div",{ref:"shelfWrapper",staticClass:"shelf-wrapper"},[a("div",{staticClass:"books-wrapper"},[a("div",{staticClass:"wrapper"},t._l(t.shelf,(function(e){return a("div",{key:e.noteUrl,staticClass:"book",on:{click:function(s){return t.toDetail(e.bookUrl,e.name)}}},[a("div",{staticClass:"cover-img"},[a("img",{staticClass:"cover",attrs:{src:e.coverUrl||s("7e43"),alt:""}})]),a("div",{staticClass:"info",on:{click:function(s){return t.toDetail(e.bookUrl,e.name)}}},[a("div",{staticClass:"name"},[t._v(t._s(e.name))]),a("div",{staticClass:"sub"},[a("div",{staticClass:"author"},[t._v(" "+t._s(e.author)+" ")]),a("div",{staticClass:"dot"},[t._v("•")]),a("div",{staticClass:"size"},[t._v("共"+t._s(e.totalChapterNum)+"章")])]),a("div",{staticClass:"dur-chapter"},[t._v("已读:"+t._s(e.durChapterTitle))]),a("div",{staticClass:"last-chapter"},[t._v("最新:"+t._s(e.latestChapterTitle))])])])})),0)])])])},i=[],n=(s("7b5b"),s("82ae")),r=s.n(n),c={data:function(){return{search:"",readingRecent:{name:"尚无阅读记录",url:""}}},mounted:function(){var t=localStorage.getItem("readingRecent");if(null!=t&&(this.readingRecent=JSON.parse(t)),0==this.shelf.length){this.loading=this.$loading({target:this.$refs.shelfWrapper,lock:!0,text:"正在获取书籍信息",spinner:"el-icon-loading",background:"rgb(247,247,247)"});var e=this;r.a.get("/getBookshelf",{timeout:3e3}).then((function(t){e.loading.close(),e.$store.commit("setConnectType","success"),e.$store.commit("increaseBookNum",t.data.data.length),e.$store.commit("addBooks",t.data.data),e.$store.commit("setConnectStatus","已连接 "),e.$store.commit("setNewConnect",!1)})).catch((function(t){throw e.loading.close(),e.$store.commit("setConnectType","danger"),e.$store.commit("setConnectStatus","连接失败"),e.$message.error("后端连接失败"),e.$store.commit("setNewConnect",!1),t}))}},methods:{setIP:function(){},toDetail:function(t,e){sessionStorage.setItem("bookUrl",t),sessionStorage.setItem("bookName",e),this.readingRecent={name:e,url:t},localStorage.setItem("readingRecent",JSON.stringify(this.readingRecent)),this.$router.push({path:"/chapter"})}},computed:{shelf:function(){return this.$store.state.shelf},connectStatus:function(){return this.$store.state.connectStatus},connectType:function(){return this.$store.state.connectType},newConnect:function(){return this.$store.state.newConnect}}},o=c,l=(s("4fed"),s("623f")),u=Object(l["a"])(o,a,i,!1,null,"5e92421e",null);e["default"]=u.exports},f820:function(t,e,s){"use strict";s.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},i=[function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"about"},[s("h1",[t._v("This is an about page")])])}],n=s("623f"),r={},c=Object(n["a"])(r,a,i,!1,null,null,null);e["default"]=c.exports},fa39:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECUlEQVRYR7WXTYhcRRDHq3pY9yKrYBQ8KBsjgvHgwRhiQBTjYZm4Xe8NusawhwS/o9GLoKhgBGPAgJd1NdGIXwtZTbRf9Rqzl6gHTVyDeIkIgnEOghAM6oKHzTJd0sO8Zaa338zb7NjwmJn++Ndv+lVVVyOsoM3Ozl69sLBAiHiDc26NUuoKv9w5d14p9aeI/DI4OMgjIyN/lJXFMhOttQ8BgBaR0TLzEXEGAKzW+lCv+V0BmLmGiLtF5M5eQrFxRPxaRCaI6LOi9YUAzPwGADxxMYYjayaJ6MkoZKyTmU8AwF19Mp7LfElEW0LNZTvAzIcBYFufjedy00T0QLt2B4AxZo9S6qX/yXhT1jn3cpqme3IbSwDM/DgAvNlu3Dm3Uyl1HAA2IOJ2EdleEu5Io9H4EBHPVCqVLSISRsMuInrLazUBpqamhoaGhr4TkRsDgLVpmtbzPmPMLQBwOwD4vvzxw8P5IyJztVrtVL4my7L1iPhTx7Yj/jw/P79pfHx8vgmQZdkLiPhK+O8GBgauqVarv5f819FpxpjLlVJ/hYMi8mKSJHubAMz8KwBcF1EYI6IjqwRIlFImonGWiNZhlmVVRDxWYGTVAMx8HwB8EtMXka1orT0gIo9GJrxNRLH+FW8IMx8EgEeW5QDEgx5gTkQ2Bk7yr9b60hVb6rKAmc8BwJWBne+x4P3XiWhtPwGstV9FzpSzHuBvALgsMHaaiDp2ZbUwWZZNIuKuQOcfD7AAAJeEcaq1Xr9ao+3rmdknnscCzQse4LdWEukYazQaa2q12vl+QTDztwCwOdCr+zA8iYi3RQwREdl+ADDz9QDwIwB0OLaInPJRcEhEHoyEyAmt9d39ALDW2lg1hYjv+lfgC4WJgkTxcJIkPcuqbpC+qgKATwvm7PYAGwDgdBeRZ4notYvZCWPMDqXUe13W3to8C6y10yJyv//u6zj/2R6ziPiRiBwt6xPMrBExFZEdRcYR8WOt9bb8MNoKAJ+3Jvtwed05d4dSKtz+c4h4VGsdrRWttZMici8AXFVix+4homNLBUmWZQcQMc/9x4mommXZ84i4t11MKbV5dHR06bxvH5uZmbnZOfdN6O0RmMNE1CxulgCstdeKyBcAcFPrVTyltZ4wxiSVSuXplkhda72zh9P1rClFZFOSJHMdAP5Hq3rxR6eH+IGIvIOuqFlr94nIc10WdRzxy6riAMJnr2nn3JlcME3TppMWNWvtfhF5pmB8WX0RvZgEEEtaYUUbM2KtfUdE/FUubNHipvBmZIxZp5TaDwBprlQGIHLqzSHiPq01x4B7Xk6Z2d8TfDwPlwFozfd1f90598Hi4uKrY2NjFwrzQVkP81nNi/byAWOMv8gOp2n6fhnt/wDqJrRWLmhIrwAAAABJRU5ErkJggg=="}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/js/about~detail.08c372e6.js b/app/src/main/assets/web/js/about~detail.08c372e6.js deleted file mode 100644 index 7d1080cea..000000000 --- a/app/src/main/assets/web/js/about~detail.08c372e6.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about~detail"],{"04a7":function(e,t,r){"use strict";var n=r("d844");e.exports=function(e,t,r){return n.forEach(r,(function(r){e=r(e,t)})),e}},"050d":function(e,t,r){"use strict";var n=r("d844");function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,r){if(!t)return e;var i;if(r)i=r(t);else if(n.isURLSearchParams(t))i=t.toString();else{var s=[];n.forEach(t,(function(e,t){null!==e&&"undefined"!==typeof e&&(n.isArray(e)?t+="[]":e=[e],n.forEach(e,(function(e){n.isDate(e)?e=e.toISOString():n.isObject(e)&&(e=JSON.stringify(e)),s.push(o(t)+"="+o(e))})))})),i=s.join("&")}if(i){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},"068e":function(e,t,r){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},"0bbf":function(e,t,r){"use strict";var n=r("d844"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,r,i,s={};return e?(n.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=n.trim(e.substr(0,i)).toLowerCase(),r=n.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([r]):s[t]?s[t]+", "+r:r}})),s):s}},"11f4":function(e,t,r){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},"155b":function(e,t,r){"use strict";var n=r("068e");function o(e){if("function"!==typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var r=this;e((function(e){r.reason||(r.reason=new n(e),t(r.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var e,t=new o((function(t){e=t}));return{token:t,cancel:e}},e.exports=o},"1eb2":function(e,t,r){"use strict";var n=r("c5b9");e.exports=function(e,t,r){var o=r.config.validateStatus;!o||o(r.status)?e(r):t(n("Request failed with status code "+r.status,r.config,null,r.request,r))}},"2ed0":function(e,t,r){"use strict";(function(t){var n=r("d844"),o=r("9d72"),i={"Content-Type":"application/x-www-form-urlencoded"};function s(e,t){!n.isUndefined(e)&&n.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function a(){var e;return("undefined"!==typeof XMLHttpRequest||"undefined"!==typeof t&&"[object process]"===Object.prototype.toString.call(t))&&(e=r("a169")),e}var u={adapter:a(),transformRequest:[function(e,t){return o(t,"Accept"),o(t,"Content-Type"),n.isFormData(e)||n.isArrayBuffer(e)||n.isBuffer(e)||n.isStream(e)||n.isFile(e)||n.isBlob(e)?e:n.isArrayBufferView(e)?e.buffer:n.isURLSearchParams(e)?(s(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):n.isObject(e)?(s(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"===typeof e)try{e=JSON.parse(e)}catch(t){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};n.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),n.forEach(["post","put","patch"],(function(e){u.headers[e]=n.merge(i)})),e.exports=u}).call(this,r("eef6"))},"43d9":function(e,t,r){"use strict";var n=r("d844"),o=r("faf0"),i=r("4a67"),s=r("c9ba"),a=r("2ed0");function u(e){var t=new i(e),r=o(i.prototype.request,t);return n.extend(r,i.prototype,t),n.extend(r,t),r}var c=u(a);c.Axios=i,c.create=function(e){return u(s(c.defaults,e))},c.Cancel=r("068e"),c.CancelToken=r("155b"),c.isCancel=r("11f4"),c.all=function(e){return Promise.all(e)},c.spread=r("53f3"),e.exports=c,e.exports.default=c},"4a67":function(e,t,r){"use strict";var n=r("d844"),o=r("050d"),i=r("54b5"),s=r("c70f"),a=r("c9ba");function u(e){this.defaults=e,this.interceptors={request:new i,response:new i}}u.prototype.request=function(e){"string"===typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=a(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[s,void 0],r=Promise.resolve(e);this.interceptors.request.forEach((function(e){t.unshift(e.fulfilled,e.rejected)})),this.interceptors.response.forEach((function(e){t.push(e.fulfilled,e.rejected)}));while(t.length)r=r.then(t.shift(),t.shift());return r},u.prototype.getUri=function(e){return e=a(this.defaults,e),o(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},n.forEach(["delete","get","head","options"],(function(e){u.prototype[e]=function(t,r){return this.request(n.merge(r||{},{method:e,url:t}))}})),n.forEach(["post","put","patch"],(function(e){u.prototype[e]=function(t,r,o){return this.request(n.merge(o||{},{method:e,url:t,data:r}))}})),e.exports=u},"4f37":function(e,t,r){"use strict";var n=r("ca19"),o=r("c4e8");e.exports=function(e,t){return e&&!n(t)?o(e,t):t}},"53f3":function(e,t,r){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},"54b5":function(e,t,r){"use strict";var n=r("d844");function o(){this.handlers=[]}o.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},o.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},o.prototype.forEach=function(e){n.forEach(this.handlers,(function(t){null!==t&&e(t)}))},e.exports=o},6266:function(e,t,r){(function(e){function r(e,t){for(var r=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}function n(e){"string"!==typeof e&&(e+="");var t,r=0,n=-1,o=!0;for(t=e.length-1;t>=0;--t)if(47===e.charCodeAt(t)){if(!o){r=t+1;break}}else-1===n&&(o=!1,n=t+1);return-1===n?"":e.slice(r,n)}function o(e,t){if(e.filter)return e.filter(t);for(var r=[],n=0;n=-1&&!n;i--){var s=i>=0?arguments[i]:e.cwd();if("string"!==typeof s)throw new TypeError("Arguments to path.resolve must be strings");s&&(t=s+"/"+t,n="/"===s.charAt(0))}return t=r(o(t.split("/"),(function(e){return!!e})),!n).join("/"),(n?"/":"")+t||"."},t.normalize=function(e){var n=t.isAbsolute(e),s="/"===i(e,-1);return e=r(o(e.split("/"),(function(e){return!!e})),!n).join("/"),e||n||(e="."),e&&s&&(e+="/"),(n?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(o(e,(function(e,t){if("string"!==typeof e)throw new TypeError("Arguments to path.join must be strings");return e})).join("/"))},t.relative=function(e,r){function n(e){for(var t=0;t=0;r--)if(""!==e[r])break;return t>r?[]:e.slice(t,r-t+1)}e=t.resolve(e).substr(1),r=t.resolve(r).substr(1);for(var o=n(e.split("/")),i=n(r.split("/")),s=Math.min(o.length,i.length),a=s,u=0;u=1;--i)if(t=e.charCodeAt(i),47===t){if(!o){n=i;break}}else o=!1;return-1===n?r?"/":".":r&&1===n?"/":e.slice(0,n)},t.basename=function(e,t){var r=n(e);return t&&r.substr(-1*t.length)===t&&(r=r.substr(0,r.length-t.length)),r},t.extname=function(e){"string"!==typeof e&&(e+="");for(var t=-1,r=0,n=-1,o=!0,i=0,s=e.length-1;s>=0;--s){var a=e.charCodeAt(s);if(47!==a)-1===n&&(o=!1,n=s+1),46===a?-1===t?t=s:1!==i&&(i=1):-1!==t&&(i=-1);else if(!o){r=s+1;break}}return-1===t||-1===n||0===i||1===i&&t===n-1&&t===r+1?"":e.slice(t,n)};var i="b"==="ab".substr(-1)?function(e,t,r){return e.substr(t,r)}:function(e,t,r){return t<0&&(t=e.length+t),e.substr(t,r)}}).call(this,r("eef6"))},"82ae":function(e,t,r){e.exports=r("43d9")},"83fe":function(e,t,r){"use strict";var n=r("d844");e.exports=n.isStandardBrowserEnv()?function(){return{write:function(e,t,r,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),n.isNumber(r)&&a.push("expires="+new Date(r).toGMTString()),n.isString(o)&&a.push("path="+o),n.isString(i)&&a.push("domain="+i),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},"9d72":function(e,t,r){"use strict";var n=r("d844");e.exports=function(e,t){n.forEach(e,(function(r,n){n!==t&&n.toUpperCase()===t.toUpperCase()&&(e[t]=r,delete e[n])}))}},a169:function(e,t,r){"use strict";var n=r("d844"),o=r("1eb2"),i=r("050d"),s=r("4f37"),a=r("0bbf"),u=r("edb4"),c=r("c5b9");e.exports=function(e){return new Promise((function(t,f){var p=e.data,l=e.headers;n.isFormData(p)&&delete l["Content-Type"];var d=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";l.Authorization="Basic "+btoa(h+":"+m)}var g=s(e.baseURL,e.url);if(d.open(e.method.toUpperCase(),i(g,e.params,e.paramsSerializer),!0),d.timeout=e.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in d?a(d.getAllResponseHeaders()):null,n=e.responseType&&"text"!==e.responseType?d.response:d.responseText,i={data:n,status:d.status,statusText:d.statusText,headers:r,config:e,request:d};o(t,f,i),d=null}},d.onabort=function(){d&&(f(c("Request aborted",e,"ECONNABORTED",d)),d=null)},d.onerror=function(){f(c("Network Error",e,null,d)),d=null},d.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",d)),d=null},n.isStandardBrowserEnv()){var y=r("83fe"),v=(e.withCredentials||u(g))&&e.xsrfCookieName?y.read(e.xsrfCookieName):void 0;v&&(l[e.xsrfHeaderName]=v)}if("setRequestHeader"in d&&n.forEach(l,(function(e,t){"undefined"===typeof p&&"content-type"===t.toLowerCase()?delete l[t]:d.setRequestHeader(t,e)})),n.isUndefined(e.withCredentials)||(d.withCredentials=!!e.withCredentials),e.responseType)try{d.responseType=e.responseType}catch(b){if("json"!==e.responseType)throw b}"function"===typeof e.onDownloadProgress&&d.addEventListener("progress",e.onDownloadProgress),"function"===typeof e.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){d&&(d.abort(),f(e),d=null)})),void 0===p&&(p=null),d.send(p)}))}},bd2a:function(e,t,r){"use strict";e.exports=function(e,t,r,n,o){return e.config=t,r&&(e.code=r),e.request=n,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},c4e8:function(e,t,r){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},c5b9:function(e,t,r){"use strict";var n=r("bd2a");e.exports=function(e,t,r,o,i){var s=new Error(e);return n(s,t,r,o,i)}},c70f:function(e,t,r){"use strict";var n=r("d844"),o=r("04a7"),i=r("11f4"),s=r("2ed0");function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){a(e),e.headers=e.headers||{},e.data=o(e.data,e.headers,e.transformRequest),e.headers=n.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),n.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]}));var t=e.adapter||s.adapter;return t(e).then((function(t){return a(e),t.data=o(t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(a(e),t&&t.response&&(t.response.data=o(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},c9ba:function(e,t,r){"use strict";var n=r("d844");e.exports=function(e,t){t=t||{};var r={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];n.forEach(o,(function(e){"undefined"!==typeof t[e]&&(r[e]=t[e])})),n.forEach(i,(function(o){n.isObject(t[o])?r[o]=n.deepMerge(e[o],t[o]):"undefined"!==typeof t[o]?r[o]=t[o]:n.isObject(e[o])?r[o]=n.deepMerge(e[o]):"undefined"!==typeof e[o]&&(r[o]=e[o])})),n.forEach(s,(function(n){"undefined"!==typeof t[n]?r[n]=t[n]:"undefined"!==typeof e[n]&&(r[n]=e[n])}));var a=o.concat(i).concat(s),u=Object.keys(t).filter((function(e){return-1===a.indexOf(e)}));return n.forEach(u,(function(n){"undefined"!==typeof t[n]?r[n]=t[n]:"undefined"!==typeof e[n]&&(r[n]=e[n])})),r}},ca19:function(e,t,r){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},d844:function(e,t,r){"use strict";var n=r("faf0"),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function s(e){return"undefined"===typeof e}function a(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&"function"===typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function u(e){return"[object ArrayBuffer]"===o.call(e)}function c(e){return"undefined"!==typeof FormData&&e instanceof FormData}function f(e){var t;return t="undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer,t}function p(e){return"string"===typeof e}function l(e){return"number"===typeof e}function d(e){return null!==e&&"object"===typeof e}function h(e){return"[object Date]"===o.call(e)}function m(e){return"[object File]"===o.call(e)}function g(e){return"[object Blob]"===o.call(e)}function y(e){return"[object Function]"===o.call(e)}function v(e){return d(e)&&y(e.pipe)}function b(e){return"undefined"!==typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function x(){return("undefined"===typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!==typeof window&&"undefined"!==typeof document)}function E(e,t){if(null!==e&&"undefined"!==typeof e)if("object"!==typeof e&&(e=[e]),i(e))for(var r=0,n=e.length;r=0&&Math.floor(t)===t&&isFinite(e)}function p(e){return o(e)&&"function"===typeof e.then&&"function"===typeof e.catch}function h(e){return null==e?"":Array.isArray(e)||c(e)&&e.toString===u?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function m(e,t){for(var n=Object.create(null),r=e.split(","),o=0;o-1)return e.splice(n,1)}}var b=Object.prototype.hasOwnProperty;function _(e,t){return b.call(e,t)}function x(e){var t=Object.create(null);return function(n){var r=t[n];return r||(t[n]=e(n))}}var w=/-(\w)/g,C=x((function(e){return e.replace(w,(function(e,t){return t?t.toUpperCase():""}))})),S=x((function(e){return e.charAt(0).toUpperCase()+e.slice(1)})),O=/\B([A-Z])/g,E=x((function(e){return e.replace(O,"-$1").toLowerCase()}));function k(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function $(e,t){return e.bind(t)}var j=Function.prototype.bind?$:k;function A(e,t){t=t||0;var n=e.length-t,r=new Array(n);while(n--)r[n]=e[n+t];return r}function T(e,t){for(var n in t)e[n]=t[n];return e}function M(e){for(var t={},n=0;n0,ne=Q&&Q.indexOf("edge/")>0,re=(Q&&Q.indexOf("android"),Q&&/iphone|ipad|ipod|ios/.test(Q)||"ios"===Z),oe=(Q&&/chrome\/\d+/.test(Q),Q&&/phantomjs/.test(Q),Q&&Q.match(/firefox\/(\d+)/)),ie={}.watch,ae=!1;if(Y)try{var se={};Object.defineProperty(se,"passive",{get:function(){ae=!0}}),window.addEventListener("test-passive",null,se)}catch(Ca){}var le=function(){return void 0===G&&(G=!Y&&!J&&"undefined"!==typeof e&&(e["process"]&&"server"===e["process"].env.VUE_ENV)),G},ue=Y&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ce(e){return"function"===typeof e&&/native code/.test(e.toString())}var fe,de="undefined"!==typeof Symbol&&ce(Symbol)&&"undefined"!==typeof Reflect&&ce(Reflect.ownKeys);fe="undefined"!==typeof Set&&ce(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var pe=P,he=0,ve=function(){this.id=he++,this.subs=[]};ve.prototype.addSub=function(e){this.subs.push(e)},ve.prototype.removeSub=function(e){g(this.subs,e)},ve.prototype.depend=function(){ve.target&&ve.target.addDep(this)},ve.prototype.notify=function(){var e=this.subs.slice();for(var t=0,n=e.length;t-1)if(i&&!_(o,"default"))a=!1;else if(""===a||a===E(e)){var l=et(String,o.type);(l<0||s0&&(a=kt(a,(t||"")+"_"+n),Et(a[0])&&Et(u)&&(c[l]=we(u.text+a[0].text),a.shift()),c.push.apply(c,a)):s(a)?Et(u)?c[l]=we(u.text+a):""!==a&&c.push(we(a)):Et(a)&&Et(u)?c[l]=we(u.text+a.text):(i(e._isVList)&&o(a.tag)&&r(a.key)&&o(t)&&(a.key="__vlist"+t+"_"+n+"__"),c.push(a)));return c}function $t(e){var t=e.$options.provide;t&&(e._provided="function"===typeof t?t.call(e):t)}function jt(e){var t=At(e.$options.inject,e);t&&(je(!1),Object.keys(t).forEach((function(n){Le(e,n,t[n])})),je(!0))}function At(e,t){if(e){for(var n=Object.create(null),r=de?Reflect.ownKeys(e):Object.keys(e),o=0;o0,a=e?!!e.$stable:!i,s=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var l in o={},e)e[l]&&"$"!==l[0]&&(o[l]=Lt(t,l,e[l]))}else o={};for(var u in t)u in o||(o[u]=Nt(t,u));return e&&Object.isExtensible(e)&&(e._normalized=o),U(o,"$stable",a),U(o,"$key",s),U(o,"$hasNormal",i),o}function Lt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return e=e&&"object"===typeof e&&!Array.isArray(e)?[e]:Ot(e),e&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function Nt(e,t){return function(){return e[t]}}function Rt(e,t){var n,r,i,a,s;if(Array.isArray(e)||"string"===typeof e)for(n=new Array(e.length),r=0,i=e.length;r1?A(n):n;for(var r=A(arguments,1),o='event handler for "'+e+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Gn=function(){return Xn.now()})}function Yn(){var e,t;for(Kn=Gn(),Vn=!0,zn.sort((function(e,t){return e.id-t.id})),Un=0;UnUn&&zn[n].id>e.id)n--;zn.splice(n+1,0,e)}else zn.push(e);Wn||(Wn=!0,ht(Yn))}}var tr=0,nr=function(e,t,n,r,o){this.vm=e,o&&(e._watcher=this),e._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++tr,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new fe,this.newDepIds=new fe,this.expression="","function"===typeof t?this.getter=t:(this.getter=K(t),this.getter||(this.getter=P)),this.value=this.lazy?void 0:this.get()};nr.prototype.get=function(){var e;ye(this);var t=this.vm;try{e=this.getter.call(t,t)}catch(Ca){if(!this.user)throw Ca;tt(Ca,t,'getter for watcher "'+this.expression+'"')}finally{this.deep&&mt(e),ge(),this.cleanupDeps()}return e},nr.prototype.addDep=function(e){var t=e.id;this.newDepIds.has(t)||(this.newDepIds.add(t),this.newDeps.push(e),this.depIds.has(t)||e.addSub(this))},nr.prototype.cleanupDeps=function(){var e=this.deps.length;while(e--){var t=this.deps[e];this.newDepIds.has(t.id)||t.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},nr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():er(this)},nr.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||l(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(Ca){tt(Ca,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},nr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},nr.prototype.depend=function(){var e=this.deps.length;while(e--)this.deps[e].depend()},nr.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);var e=this.deps.length;while(e--)this.deps[e].removeSub(this);this.active=!1}};var rr={enumerable:!0,configurable:!0,get:P,set:P};function or(e,t,n){rr.get=function(){return this[t][n]},rr.set=function(e){this[t][n]=e},Object.defineProperty(e,n,rr)}function ir(e){e._watchers=[];var t=e.$options;t.props&&ar(e,t.props),t.methods&&hr(e,t.methods),t.data?sr(e):Pe(e._data={},!0),t.computed&&cr(e,t.computed),t.watch&&t.watch!==ie&&vr(e,t.watch)}function ar(e,t){var n=e.$options.propsData||{},r=e._props={},o=e.$options._propKeys=[],i=!e.$parent;i||je(!1);var a=function(i){o.push(i);var a=Ye(i,t,n,e);Le(r,i,a),i in e||or(e,"_props",i)};for(var s in t)a(s);je(!0)}function sr(e){var t=e.$options.data;t=e._data="function"===typeof t?lr(t,e):t||{},c(t)||(t={});var n=Object.keys(t),r=e.$options.props,o=(e.$options.methods,n.length);while(o--){var i=n[o];0,r&&_(r,i)||V(i)||or(e,"_data",i)}Pe(t,!0)}function lr(e,t){ye();try{return e.call(t,t)}catch(Ca){return tt(Ca,t,"data()"),{}}finally{ge()}}var ur={lazy:!0};function cr(e,t){var n=e._computedWatchers=Object.create(null),r=le();for(var o in t){var i=t[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new nr(e,a||P,P,ur)),o in e||fr(e,o,i)}}function fr(e,t,n){var r=!le();"function"===typeof n?(rr.get=r?dr(t):pr(n),rr.set=P):(rr.get=n.get?r&&!1!==n.cache?dr(t):pr(n.get):P,rr.set=n.set||P),Object.defineProperty(e,t,rr)}function dr(e){return function(){var t=this._computedWatchers&&this._computedWatchers[e];if(t)return t.dirty&&t.evaluate(),ve.target&&t.depend(),t.value}}function pr(e){return function(){return e.call(this,this)}}function hr(e,t){e.$options.props;for(var n in t)e[n]="function"!==typeof t[n]?P:j(t[n],e)}function vr(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=A(arguments,1);return n.unshift(this),"function"===typeof e.install?e.install.apply(e,n):"function"===typeof e&&e.apply(null,n),t.push(e),this}}function Or(e){e.mixin=function(e){return this.options=Ge(this.options,e),this}}function Er(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=n.cid,o=e._Ctor||(e._Ctor={});if(o[r])return o[r];var i=e.name||n.options.name;var a=function(e){this._init(e)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=t++,a.options=Ge(n.options,e),a["super"]=n,a.options.props&&kr(a),a.options.computed&&$r(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,z.forEach((function(e){a[e]=n[e]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=e,a.sealedOptions=T({},a.options),o[r]=a,a}}function kr(e){var t=e.options.props;for(var n in t)or(e.prototype,"_props",n)}function $r(e){var t=e.options.computed;for(var n in t)fr(e.prototype,n,t[n])}function jr(e){z.forEach((function(t){e[t]=function(e,n){return n?("component"===t&&c(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"===typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}}))}function Ar(e){return e&&(e.Ctor.options.name||e.tag)}function Tr(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"===typeof e?e.split(",").indexOf(t)>-1:!!f(e)&&e.test(t)}function Mr(e,t){var n=e.cache,r=e.keys,o=e._vnode;for(var i in n){var a=n[i];if(a){var s=Ar(a.componentOptions);s&&!t(s)&&Pr(n,i,r,o)}}}function Pr(e,t,n,r){var o=e[t];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),e[t]=null,g(n,t)}br(Cr),yr(Cr),jn(Cr),Pn(Cr),gn(Cr);var Lr=[String,RegExp,Array],Nr={name:"keep-alive",abstract:!0,props:{include:Lr,exclude:Lr,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var e in this.cache)Pr(this.cache,e,this.keys)},mounted:function(){var e=this;this.$watch("include",(function(t){Mr(e,(function(e){return Tr(t,e)}))})),this.$watch("exclude",(function(t){Mr(e,(function(e){return!Tr(t,e)}))}))},render:function(){var e=this.$slots.default,t=Cn(e),n=t&&t.componentOptions;if(n){var r=Ar(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Tr(i,r))||a&&r&&Tr(a,r))return t;var s=this,l=s.cache,u=s.keys,c=null==t.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):t.key;l[c]?(t.componentInstance=l[c].componentInstance,g(u,c),u.push(c)):(l[c]=t,u.push(c),this.max&&u.length>parseInt(this.max)&&Pr(l,u[0],u,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}},Rr={KeepAlive:Nr};function Ir(e){var t={get:function(){return B}};Object.defineProperty(e,"config",t),e.util={warn:pe,extend:T,mergeOptions:Ge,defineReactive:Le},e.set=Ne,e.delete=Re,e.nextTick=ht,e.observable=function(e){return Pe(e),e},e.options=Object.create(null),z.forEach((function(t){e.options[t+"s"]=Object.create(null)})),e.options._base=e,T(e.options.components,Rr),Sr(e),Or(e),Er(e),jr(e)}Ir(Cr),Object.defineProperty(Cr.prototype,"$isServer",{get:le}),Object.defineProperty(Cr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cr,"FunctionalRenderContext",{value:Jt}),Cr.version="2.6.11";var Fr=m("style,class"),Hr=m("input,textarea,option,select,progress"),zr=function(e,t,n){return"value"===n&&Hr(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Dr=m("contenteditable,draggable,spellcheck"),Br=m("events,caret,typing,plaintext-only"),Wr=function(e,t){return Gr(t)||"false"===t?"false":"contenteditable"===e&&Br(t)?t:"true"},Vr=m("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Ur="http://www.w3.org/1999/xlink",qr=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Kr=function(e){return qr(e)?e.slice(6,e.length):""},Gr=function(e){return null==e||!1===e};function Xr(e){var t=e.data,n=e,r=e;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(t=Yr(r.data,t));while(o(n=n.parent))n&&n.data&&(t=Yr(t,n.data));return Jr(t.staticClass,t.class)}function Yr(e,t){return{staticClass:Zr(e.staticClass,t.staticClass),class:o(e.class)?[e.class,t.class]:t.class}}function Jr(e,t){return o(e)||o(t)?Zr(e,Qr(t)):""}function Zr(e,t){return e?t?e+" "+t:e:t||""}function Qr(e){return Array.isArray(e)?eo(e):l(e)?to(e):"string"===typeof e?e:""}function eo(e){for(var t,n="",r=0,i=e.length;r-1?so[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:so[e]=/HTMLUnknownElement/.test(t.toString())}var uo=m("text,number,password,search,email,tel,url");function co(e){if("string"===typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}function fo(e,t){var n=document.createElement(e);return"select"!==e||t.data&&t.data.attrs&&void 0!==t.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n}function po(e,t){return document.createElementNS(no[e],t)}function ho(e){return document.createTextNode(e)}function vo(e){return document.createComment(e)}function mo(e,t,n){e.insertBefore(t,n)}function yo(e,t){e.removeChild(t)}function go(e,t){e.appendChild(t)}function bo(e){return e.parentNode}function _o(e){return e.nextSibling}function xo(e){return e.tagName}function wo(e,t){e.textContent=t}function Co(e,t){e.setAttribute(t,"")}var So=Object.freeze({createElement:fo,createElementNS:po,createTextNode:ho,createComment:vo,insertBefore:mo,removeChild:yo,appendChild:go,parentNode:bo,nextSibling:_o,tagName:xo,setTextContent:wo,setStyleScope:Co}),Oo={create:function(e,t){Eo(t)},update:function(e,t){e.data.ref!==t.data.ref&&(Eo(e,!0),Eo(t))},destroy:function(e){Eo(e,!0)}};function Eo(e,t){var n=e.data.ref;if(o(n)){var r=e.context,i=e.componentInstance||e.elm,a=r.$refs;t?Array.isArray(a[n])?g(a[n],i):a[n]===i&&(a[n]=void 0):e.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var ko=new be("",{},[]),$o=["create","activate","update","remove","destroy"];function jo(e,t){return e.key===t.key&&(e.tag===t.tag&&e.isComment===t.isComment&&o(e.data)===o(t.data)&&Ao(e,t)||i(e.isAsyncPlaceholder)&&e.asyncFactory===t.asyncFactory&&r(t.asyncFactory.error))}function Ao(e,t){if("input"!==e.tag)return!0;var n,r=o(n=e.data)&&o(n=n.attrs)&&n.type,i=o(n=t.data)&&o(n=n.attrs)&&n.type;return r===i||uo(r)&&uo(i)}function To(e,t,n){var r,i,a={};for(r=t;r<=n;++r)i=e[r].key,o(i)&&(a[i]=r);return a}function Mo(e){var t,n,a={},l=e.modules,u=e.nodeOps;for(t=0;t<$o.length;++t)for(a[$o[t]]=[],n=0;nv?(f=r(n[g+1])?null:n[g+1].elm,C(e,f,n,h,g,i)):h>g&&O(t,d,v)}function $(e,t,n,r){for(var i=n;i-1?Wo(e,t,n):Vr(t)?Gr(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Dr(t)?e.setAttribute(t,Wr(t,n)):qr(t)?Gr(n)?e.removeAttributeNS(Ur,Kr(t)):e.setAttributeNS(Ur,t,n):Wo(e,t,n)}function Wo(e,t,n){if(Gr(n))e.removeAttribute(t);else{if(ee&&!te&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var Vo={create:Do,update:Do};function Uo(e,t){var n=t.elm,i=t.data,a=e.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Xr(t),l=n._transitionClasses;o(l)&&(s=Zr(s,Qr(l))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var qo,Ko={create:Uo,update:Uo},Go="__r",Xo="__c";function Yo(e){if(o(e[Go])){var t=ee?"change":"input";e[t]=[].concat(e[Go],e[t]||[]),delete e[Go]}o(e[Xo])&&(e.change=[].concat(e[Xo],e.change||[]),delete e[Xo])}function Jo(e,t,n){var r=qo;return function o(){var i=t.apply(null,arguments);null!==i&&ei(e,o,n,r)}}var Zo=at&&!(oe&&Number(oe[1])<=53);function Qo(e,t,n,r){if(Zo){var o=Kn,i=t;t=i._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=o||e.timeStamp<=0||e.target.ownerDocument!==document)return i.apply(this,arguments)}}qo.addEventListener(e,t,ae?{capture:n,passive:r}:n)}function ei(e,t,n,r){(r||qo).removeEventListener(e,t._wrapper||t,n)}function ti(e,t){if(!r(e.data.on)||!r(t.data.on)){var n=t.data.on||{},o=e.data.on||{};qo=t.elm,Yo(n),_t(n,o,Qo,ei,Jo,t.context),qo=void 0}}var ni,ri={create:ti,update:ti};function oi(e,t){if(!r(e.data.domProps)||!r(t.data.domProps)){var n,i,a=t.elm,s=e.data.domProps||{},l=t.data.domProps||{};for(n in o(l.__ob__)&&(l=t.data.domProps=T({},l)),s)n in l||(a[n]="");for(n in l){if(i=l[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);ii(a,u)&&(a.value=u)}else if("innerHTML"===n&&oo(a.tagName)&&r(a.innerHTML)){ni=ni||document.createElement("div"),ni.innerHTML=""+i+"";var c=ni.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(c.firstChild)a.appendChild(c.firstChild)}else if(i!==s[n])try{a[n]=i}catch(Ca){}}}}function ii(e,t){return!e.composing&&("OPTION"===e.tagName||ai(e,t)||si(e,t))}function ai(e,t){var n=!0;try{n=document.activeElement!==e}catch(Ca){}return n&&e.value!==t}function si(e,t){var n=e.value,r=e._vModifiers;if(o(r)){if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}var li={create:oi,update:oi},ui=x((function(e){var t={},n=/;(?![^(]*\))/g,r=/:(.+)/;return e.split(n).forEach((function(e){if(e){var n=e.split(r);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}));function ci(e){var t=fi(e.style);return e.staticStyle?T(e.staticStyle,t):t}function fi(e){return Array.isArray(e)?M(e):"string"===typeof e?ui(e):e}function di(e,t){var n,r={};if(t){var o=e;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=ci(o.data))&&T(r,n)}(n=ci(e.data))&&T(r,n);var i=e;while(i=i.parent)i.data&&(n=ci(i.data))&&T(r,n);return r}var pi,hi=/^--/,vi=/\s*!important$/,mi=function(e,t,n){if(hi.test(t))e.style.setProperty(t,n);else if(vi.test(n))e.style.setProperty(E(t),n.replace(vi,""),"important");else{var r=gi(t);if(Array.isArray(n))for(var o=0,i=n.length;o-1?t.split(xi).forEach((function(t){return e.classList.add(t)})):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Ci(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(xi).forEach((function(t){return e.classList.remove(t)})):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?e.setAttribute("class",n):e.removeAttribute("class")}}function Si(e){if(e){if("object"===typeof e){var t={};return!1!==e.css&&T(t,Oi(e.name||"v")),T(t,e),t}return"string"===typeof e?Oi(e):void 0}}var Oi=x((function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}})),Ei=Y&&!te,ki="transition",$i="animation",ji="transition",Ai="transitionend",Ti="animation",Mi="animationend";Ei&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(ji="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ti="WebkitAnimation",Mi="webkitAnimationEnd"));var Pi=Y?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Li(e){Pi((function(){Pi(e)}))}function Ni(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),wi(e,t))}function Ri(e,t){e._transitionClasses&&g(e._transitionClasses,t),Ci(e,t)}function Ii(e,t,n){var r=Hi(e,t),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===ki?Ai:Mi,l=0,u=function(){e.removeEventListener(s,c),n()},c=function(t){t.target===e&&++l>=a&&u()};setTimeout((function(){l0&&(n=ki,c=a,f=i.length):t===$i?u>0&&(n=$i,c=u,f=l.length):(c=Math.max(a,u),n=c>0?a>u?ki:$i:null,f=n?n===ki?i.length:l.length:0);var d=n===ki&&Fi.test(r[ji+"Property"]);return{type:n,timeout:c,propCount:f,hasTransform:d}}function zi(e,t){while(e.length1}function qi(e,t){!0!==t.data.show&&Bi(t)}var Ki=Y?{create:qi,activate:qi,remove:function(e,t){!0!==e.data.show?Wi(e,t):t()}}:{},Gi=[Vo,Ko,ri,li,_i,Ki],Xi=Gi.concat(zo),Yi=Mo({nodeOps:So,modules:Xi});te&&document.addEventListener("selectionchange",(function(){var e=document.activeElement;e&&e.vmodel&&oa(e,"input")}));var Ji={inserted:function(e,t,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?xt(n,"postpatch",(function(){Ji.componentUpdated(e,t,n)})):Zi(e,t,n.context),e._vOptions=[].map.call(e.options,ta)):("textarea"===n.tag||uo(e.type))&&(e._vModifiers=t.modifiers,t.modifiers.lazy||(e.addEventListener("compositionstart",na),e.addEventListener("compositionend",ra),e.addEventListener("change",ra),te&&(e.vmodel=!0)))},componentUpdated:function(e,t,n){if("select"===n.tag){Zi(e,t,n.context);var r=e._vOptions,o=e._vOptions=[].map.call(e.options,ta);if(o.some((function(e,t){return!R(e,r[t])}))){var i=e.multiple?t.value.some((function(e){return ea(e,o)})):t.value!==t.oldValue&&ea(t.value,o);i&&oa(e,"change")}}}};function Zi(e,t,n){Qi(e,t,n),(ee||ne)&&setTimeout((function(){Qi(e,t,n)}),0)}function Qi(e,t,n){var r=t.value,o=e.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,l=e.options.length;s-1,a.selected!==i&&(a.selected=i);else if(R(ta(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));o||(e.selectedIndex=-1)}}function ea(e,t){return t.every((function(t){return!R(t,e)}))}function ta(e){return"_value"in e?e._value:e.value}function na(e){e.target.composing=!0}function ra(e){e.target.composing&&(e.target.composing=!1,oa(e.target,"input"))}function oa(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function ia(e){return!e.componentInstance||e.data&&e.data.transition?e:ia(e.componentInstance._vnode)}var aa={bind:function(e,t,n){var r=t.value;n=ia(n);var o=n.data&&n.data.transition,i=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&o?(n.data.show=!0,Bi(n,(function(){e.style.display=i}))):e.style.display=r?i:"none"},update:function(e,t,n){var r=t.value,o=t.oldValue;if(!r!==!o){n=ia(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?Bi(n,(function(){e.style.display=e.__vOriginalDisplay})):Wi(n,(function(){e.style.display="none"}))):e.style.display=r?e.__vOriginalDisplay:"none"}},unbind:function(e,t,n,r,o){o||(e.style.display=e.__vOriginalDisplay)}},sa={model:Ji,show:aa},la={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ua(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?ua(Cn(t.children)):e}function ca(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var o=n._parentListeners;for(var i in o)t[C(i)]=o[i];return t}function fa(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}function da(e){while(e=e.parent)if(e.data.transition)return!0}function pa(e,t){return t.key===e.key&&t.tag===e.tag}var ha=function(e){return e.tag||wn(e)},va=function(e){return"show"===e.name},ma={name:"transition",props:la,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(ha),n.length)){0;var r=this.mode;0;var o=n[0];if(da(this.$vnode))return o;var i=ua(o);if(!i)return o;if(this._leaving)return fa(e,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var l=(i.data||(i.data={})).transition=ca(this),u=this._vnode,c=ua(u);if(i.data.directives&&i.data.directives.some(va)&&(i.data.show=!0),c&&c.data&&!pa(i,c)&&!wn(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var f=c.data.transition=T({},l);if("out-in"===r)return this._leaving=!0,xt(f,"afterLeave",(function(){t._leaving=!1,t.$forceUpdate()})),fa(e,o);if("in-out"===r){if(wn(i))return u;var d,p=function(){d()};xt(l,"afterEnter",p),xt(l,"enterCancelled",p),xt(f,"delayLeave",(function(e){d=e}))}}return o}}},ya=T({tag:String,moveClass:String},la);delete ya.mode;var ga={props:ya,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var o=Tn(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,o(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=ca(this),s=0;sl)r(s,n=t[l++])&&(~i(u,n)||u.push(n));return u}},"032e":function(e,t,n){var r=n("d5dc"),o=n("d68d"),i=r.document,a=o(i)&&o(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},"0532":function(e,t,n){var r=n("57c4"),o=n("ed35"),i=r("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||a[i]===e)}},"0618":function(e,t,n){"use strict";var r=n("ac83");e.exports=function(){var e=r(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},"0655":function(e,t,n){"use strict";n.r(t),function(e){var n=function(){if("undefined"!==typeof Map)return Map;function e(e,t){var n=-1;return e.some((function(e,r){return e[0]===t&&(n=r,!0)})),n}return(function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n0},e.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),c?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t,r=u.some((function(e){return!!~n.indexOf(e)}));r&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),d=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),$="undefined"!==typeof WeakMap?new WeakMap:new n,j=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=f.getInstance(),r=new k(t,n,this);$.set(this,r)}return e}();["observe","unobserve","disconnect"].forEach((function(e){j.prototype[e]=function(){var t;return(t=$.get(this))[e].apply(t,arguments)}}));var A=function(){return"undefined"!==typeof o.ResizeObserver?o.ResizeObserver:j}();t["default"]=A}.call(this,n("77e5"))},"0b29":function(e,t,n){var r=n("a9f2");e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},1072:function(e,t){t.f=Object.getOwnPropertySymbols},"12d9":function(e,t,n){var r=n("f30e"),o=/#|\.prototype\./,i=function(e,t){var n=s[a(e)];return n==u||n!=l&&("function"==typeof t?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},s=i.data={},l=i.NATIVE="N",u=i.POLYFILL="P";e.exports=i},"143b":function(e,t,n){"use strict";var r,o,i,a=n("90a7"),s=n("2ba5"),l=n("f28d"),u=n("57c4"),c=n("e17a"),f=u("iterator"),d=!1,p=function(){return this};[].keys&&(i=[].keys(),"next"in i?(o=a(a(i)),o!==Object.prototype&&(r=o)):d=!0),void 0==r&&(r={}),c||l(r,f)||s(r,f,p),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:d}},"16e5":function(e,t,n){var r=n("02d0"),o=n("6807");e.exports=Object.keys||function(e){return r(e,o)}},1932:function(e,t,n){"use strict";t.__esModule=!0;n("d41f");t.default={mounted:function(){},methods:{getMigratingConfig:function(){return{props:{},events:{}}}}}},"1bee":function(e,t,n){"use strict"; -/*! - * vue-router v3.1.6 - * (c) 2020 Evan You - * @license MIT - */function r(e,t){0}function o(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}function i(e,t){return t instanceof e||t&&(t.name===e.name||t._name===e._name)}function a(e,t){for(var n in t)e[n]=t[n];return e}var s={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(e,t){var n=t.props,r=t.children,o=t.parent,i=t.data;i.routerView=!0;var s=o.$createElement,u=n.name,c=o.$route,f=o._routerViewCache||(o._routerViewCache={}),d=0,p=!1;while(o&&o._routerRoot!==o){var h=o.$vnode?o.$vnode.data:{};h.routerView&&d++,h.keepAlive&&o._directInactive&&o._inactive&&(p=!0),o=o.$parent}if(i.routerViewDepth=d,p){var v=f[u],m=v&&v.component;return m?(v.configProps&&l(m,i,v.route,v.configProps),s(m,i,r)):s()}var y=c.matched[d],g=y&&y.components[u];if(!y||!g)return f[u]=null,s();f[u]={component:g},i.registerRouteInstance=function(e,t){var n=y.instances[u];(t&&n!==e||!t&&n===e)&&(y.instances[u]=t)},(i.hook||(i.hook={})).prepatch=function(e,t){y.instances[u]=t.componentInstance},i.hook.init=function(e){e.data.keepAlive&&e.componentInstance&&e.componentInstance!==y.instances[u]&&(y.instances[u]=e.componentInstance)};var b=y.props&&y.props[u];return b&&(a(f[u],{route:c,configProps:b}),l(g,i,c,b)),s(g,i,r)}};function l(e,t,n,r){var o=t.props=u(n,r);if(o){o=t.props=a({},o);var i=t.attrs=t.attrs||{};for(var s in o)e.props&&s in e.props||(i[s]=o[s],delete o[s])}}function u(e,t){switch(typeof t){case"undefined":return;case"object":return t;case"function":return t(e);case"boolean":return t?e.params:void 0;default:0}}var c=/[!'()*]/g,f=function(e){return"%"+e.charCodeAt(0).toString(16)},d=/%2C/g,p=function(e){return encodeURIComponent(e).replace(c,f).replace(d,",")},h=decodeURIComponent;function v(e,t,n){void 0===t&&(t={});var r,o=n||m;try{r=o(e||"")}catch(a){r={}}for(var i in t)r[i]=t[i];return r}function m(e){var t={};return e=e.trim().replace(/^(\?|#|&)/,""),e?(e.split("&").forEach((function(e){var n=e.replace(/\+/g," ").split("="),r=h(n.shift()),o=n.length>0?h(n.join("=")):null;void 0===t[r]?t[r]=o:Array.isArray(t[r])?t[r].push(o):t[r]=[t[r],o]})),t):t}function y(e){var t=e?Object.keys(e).map((function(t){var n=e[t];if(void 0===n)return"";if(null===n)return p(t);if(Array.isArray(n)){var r=[];return n.forEach((function(e){void 0!==e&&(null===e?r.push(p(t)):r.push(p(t)+"="+p(e)))})),r.join("&")}return p(t)+"="+p(n)})).filter((function(e){return e.length>0})).join("&"):null;return t?"?"+t:""}var g=/\/?$/;function b(e,t,n,r){var o=r&&r.options.stringifyQuery,i=t.query||{};try{i=_(i)}catch(s){}var a={name:t.name||e&&e.name,meta:e&&e.meta||{},path:t.path||"/",hash:t.hash||"",query:i,params:t.params||{},fullPath:C(t,o),matched:e?w(e):[]};return n&&(a.redirectedFrom=C(n,o)),Object.freeze(a)}function _(e){if(Array.isArray(e))return e.map(_);if(e&&"object"===typeof e){var t={};for(var n in e)t[n]=_(e[n]);return t}return e}var x=b(null,{path:"/"});function w(e){var t=[];while(e)t.unshift(e),e=e.parent;return t}function C(e,t){var n=e.path,r=e.query;void 0===r&&(r={});var o=e.hash;void 0===o&&(o="");var i=t||y;return(n||"/")+i(r)+o}function S(e,t){return t===x?e===t:!!t&&(e.path&&t.path?e.path.replace(g,"")===t.path.replace(g,"")&&e.hash===t.hash&&O(e.query,t.query):!(!e.name||!t.name)&&(e.name===t.name&&e.hash===t.hash&&O(e.query,t.query)&&O(e.params,t.params)))}function O(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e===t;var n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every((function(n){var r=e[n],o=t[n];return"object"===typeof r&&"object"===typeof o?O(r,o):String(r)===String(o)}))}function E(e,t){return 0===e.path.replace(g,"/").indexOf(t.path.replace(g,"/"))&&(!t.hash||e.hash===t.hash)&&k(e.query,t.query)}function k(e,t){for(var n in t)if(!(n in e))return!1;return!0}function $(e,t,n){var r=e.charAt(0);if("/"===r)return e;if("?"===r||"#"===r)return t+e;var o=t.split("/");n&&o[o.length-1]||o.pop();for(var i=e.replace(/^\//,"").split("/"),a=0;a=0&&(t=e.slice(r),e=e.slice(0,r));var o=e.indexOf("?");return o>=0&&(n=e.slice(o+1),e=e.slice(0,o)),{path:e,query:n,hash:t}}function A(e){return e.replace(/\/\//g,"/")}var T=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)},M=J,P=F,L=H,N=B,R=Y,I=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function F(e,t){var n,r=[],o=0,i=0,a="",s=t&&t.delimiter||"/";while(null!=(n=I.exec(e))){var l=n[0],u=n[1],c=n.index;if(a+=e.slice(i,c),i=c+l.length,u)a+=u[1];else{var f=e[i],d=n[2],p=n[3],h=n[4],v=n[5],m=n[6],y=n[7];a&&(r.push(a),a="");var g=null!=d&&null!=f&&f!==d,b="+"===m||"*"===m,_="?"===m||"*"===m,x=n[2]||s,w=h||v;r.push({name:p||o++,prefix:d||"",delimiter:x,optional:_,repeat:b,partial:g,asterisk:!!y,pattern:w?V(w):y?".*":"[^"+W(x)+"]+?"})}}return i1||!x.length)return 0===x.length?e():e("span",{},x)}if("a"===this.tag)_.on=g,_.attrs={href:l};else{var w=se(this.$slots.default);if(w){w.isStatic=!1;var C=w.data=a({},w.data);for(var O in C.on=C.on||{},C.on){var k=C.on[O];O in g&&(C.on[O]=Array.isArray(k)?k:[k])}for(var $ in g)$ in C.on?C.on[$].push(g[$]):C.on[$]=y;var j=w.data.attrs=a({},w.data.attrs);j.href=l}else _.on=g}return e(this.tag,_,this.$slots.default)}};function ae(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&(void 0===e.button||0===e.button)){if(e.currentTarget&&e.currentTarget.getAttribute){var t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function se(e){if(e)for(var t,n=0;n-1&&(s.params[d]=n.params[d]);return s.path=Q(u.path,s.params,'named route "'+l+'"'),c(u,s,a)}if(s.path){s.params={};for(var p=0;p=e.length?n():e[o]?t(e[o],(function(){r(o+1)})):r(o+1)};r(0)}function Fe(e){return function(t,n,r){var i=!1,a=0,s=null;He(e,(function(e,t,n,l){if("function"===typeof e&&void 0===e.cid){i=!0,a++;var u,c=We((function(t){Be(t)&&(t=t.default),e.resolved="function"===typeof t?t:te.extend(t),n.components[l]=t,a--,a<=0&&r()})),f=We((function(e){var t="Failed to resolve async component "+l+": "+e;s||(s=o(e)?e:new Error(t),r(s))}));try{u=e(c,f)}catch(p){f(p)}if(u)if("function"===typeof u.then)u.then(c,f);else{var d=u.component;d&&"function"===typeof d.then&&d.then(c,f)}}})),i||r()}}function He(e,t){return ze(e.map((function(e){return Object.keys(e.components).map((function(n){return t(e.components[n],e.instances[n],e,n)}))})))}function ze(e){return Array.prototype.concat.apply([],e)}var De="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag;function Be(e){return e.__esModule||De&&"Module"===e[Symbol.toStringTag]}function We(e){var t=!1;return function(){var n=[],r=arguments.length;while(r--)n[r]=arguments[r];if(!t)return t=!0,e.apply(this,n)}}var Ve=function(e){function t(t){e.call(this),this.name=this._name="NavigationDuplicated",this.message='Navigating to current location ("'+t.fullPath+'") is not allowed',Object.defineProperty(this,"stack",{value:(new e).stack,writable:!0,configurable:!0})}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t}(Error);Ve._name="NavigationDuplicated";var Ue=function(e,t){this.router=e,this.base=qe(t),this.current=x,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function qe(e){if(!e)if(ue){var t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^https?:\/\/[^\/]+/,"")}else e="/";return"/"!==e.charAt(0)&&(e="/"+e),e.replace(/\/$/,"")}function Ke(e,t){var n,r=Math.max(e.length,t.length);for(n=0;n-1?decodeURI(e.slice(0,r))+e.slice(r):decodeURI(e)}else e=decodeURI(e.slice(0,n))+e.slice(n);return e}function lt(e){var t=window.location.href,n=t.indexOf("#"),r=n>=0?t.slice(0,n):t;return r+"#"+e}function ut(e){Le?Ne(lt(e)):window.location.hash=e}function ct(e){Le?Re(lt(e)):window.location.replace(lt(e))}var ft=function(e){function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.push=function(e,t,n){var r=this;this.transitionTo(e,(function(e){r.stack=r.stack.slice(0,r.index+1).concat(e),r.index++,t&&t(e)}),n)},t.prototype.replace=function(e,t,n){var r=this;this.transitionTo(e,(function(e){r.stack=r.stack.slice(0,r.index).concat(e),t&&t(e)}),n)},t.prototype.go=function(e){var t=this,n=this.index+e;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){t.index=n,t.updateRoute(r)}),(function(e){i(Ve,e)&&(t.index=n)}))}},t.prototype.getCurrentLocation=function(){var e=this.stack[this.stack.length-1];return e?e.fullPath:"/"},t.prototype.ensureURL=function(){},t}(Ue),dt=function(e){void 0===e&&(e={}),this.app=null,this.apps=[],this.options=e,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=he(e.routes||[],this);var t=e.mode||"hash";switch(this.fallback="history"===t&&!Le&&!1!==e.fallback,this.fallback&&(t="hash"),ue||(t="abstract"),this.mode=t,t){case"history":this.history=new nt(this,e.base);break;case"hash":this.history=new ot(this,e.base,this.fallback);break;case"abstract":this.history=new ft(this,e.base);break;default:0}},pt={currentRoute:{configurable:!0}};function ht(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function vt(e,t,n){var r="hash"===n?"#"+t:t;return e?A(e+"/"+r):r}dt.prototype.match=function(e,t,n){return this.matcher.match(e,t,n)},pt.currentRoute.get=function(){return this.history&&this.history.current},dt.prototype.init=function(e){var t=this;if(this.apps.push(e),e.$once("hook:destroyed",(function(){var n=t.apps.indexOf(e);n>-1&&t.apps.splice(n,1),t.app===e&&(t.app=t.apps[0]||null)})),!this.app){this.app=e;var n=this.history;if(n instanceof nt)n.transitionTo(n.getCurrentLocation());else if(n instanceof ot){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen((function(e){t.apps.forEach((function(t){t._route=e}))}))}},dt.prototype.beforeEach=function(e){return ht(this.beforeHooks,e)},dt.prototype.beforeResolve=function(e){return ht(this.resolveHooks,e)},dt.prototype.afterEach=function(e){return ht(this.afterHooks,e)},dt.prototype.onReady=function(e,t){this.history.onReady(e,t)},dt.prototype.onError=function(e){this.history.onError(e)},dt.prototype.push=function(e,t,n){var r=this;if(!t&&!n&&"undefined"!==typeof Promise)return new Promise((function(t,n){r.history.push(e,t,n)}));this.history.push(e,t,n)},dt.prototype.replace=function(e,t,n){var r=this;if(!t&&!n&&"undefined"!==typeof Promise)return new Promise((function(t,n){r.history.replace(e,t,n)}));this.history.replace(e,t,n)},dt.prototype.go=function(e){this.history.go(e)},dt.prototype.back=function(){this.go(-1)},dt.prototype.forward=function(){this.go(1)},dt.prototype.getMatchedComponents=function(e){var t=e?e.matched?e:this.resolve(e).route:this.currentRoute;return t?[].concat.apply([],t.matched.map((function(e){return Object.keys(e.components).map((function(t){return e.components[t]}))}))):[]},dt.prototype.resolve=function(e,t,n){t=t||this.history.current;var r=ee(e,t,n,this),o=this.match(r,t),i=o.redirectedFrom||o.fullPath,a=this.history.base,s=vt(a,i,this.mode);return{location:r,route:o,href:s,normalizedTo:r,resolved:o}},dt.prototype.addRoutes=function(e){this.matcher.addRoutes(e),this.history.current!==x&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(dt.prototype,pt),dt.install=le,dt.version="3.1.6",ue&&window.Vue&&window.Vue.use(dt),t["a"]=dt},"1ce0":function(e,t,n){},"1f4f":function(e,t,n){"use strict";t.__esModule=!0;var r=n("6406");t.default={methods:{t:function(){for(var e=arguments.length,t=Array(e),n=0;n1&&console.warn("WARNING: the given `parent` query("+t.parent+") matched more than one element, the first one will be used"),0===a.length)throw"ERROR: the given `parent` doesn't exists!";a=a[0]}return a.length>1&&a instanceof Element===!1&&(console.warn("WARNING: you have passed as parent a list of elements, the first one will be used"),a=a[0]),a.appendChild(o),o;function s(e,t){t.forEach((function(t){e.classList.add(t)}))}function l(e,t){t.forEach((function(t){e.setAttribute(t.split(":")[0],t.split(":")[1]||"")}))}},n.prototype._getPosition=function(e,t){var n=l(t);if(this._options.forceAbsolute)return"absolute";var r=c(t,n);return r?"fixed":"absolute"},n.prototype._getOffsets=function(e,t,n){n=n.split("-")[0];var o={};o.position=this.state.position;var i="fixed"===o.position,a=v(t,l(e),i),s=r(e);return-1!==["right","left"].indexOf(n)?(o.top=a.top+a.height/2-s.height/2,o.left="left"===n?a.left-s.width:a.right):(o.left=a.left+a.width/2-s.width/2,o.top="top"===n?a.top-s.height:a.bottom),o.width=s.width,o.height=s.height,{popper:o,reference:a}},n.prototype._setupEventListeners=function(){if(this.state.updateBound=this.update.bind(this),e.addEventListener("resize",this.state.updateBound),"window"!==this._options.boundariesElement){var t=u(this._reference);t!==e.document.body&&t!==e.document.documentElement||(t=e),t.addEventListener("scroll",this.state.updateBound),this.state.scrollTarget=t}},n.prototype._removeEventListeners=function(){e.removeEventListener("resize",this.state.updateBound),"window"!==this._options.boundariesElement&&this.state.scrollTarget&&(this.state.scrollTarget.removeEventListener("scroll",this.state.updateBound),this.state.scrollTarget=null),this.state.updateBound=null},n.prototype._getBoundaries=function(t,n,r){var o,i,a={};if("window"===r){var s=e.document.body,c=e.document.documentElement;i=Math.max(s.scrollHeight,s.offsetHeight,c.clientHeight,c.scrollHeight,c.offsetHeight),o=Math.max(s.scrollWidth,s.offsetWidth,c.clientWidth,c.scrollWidth,c.offsetWidth),a={top:0,right:o,bottom:i,left:0}}else if("viewport"===r){var f=l(this._popper),d=u(this._popper),h=p(f),v=function(e){return e==document.body?Math.max(document.documentElement.scrollTop,document.body.scrollTop):e.scrollTop},m=function(e){return e==document.body?Math.max(document.documentElement.scrollLeft,document.body.scrollLeft):e.scrollLeft},y="fixed"===t.offsets.popper.position?0:v(d),g="fixed"===t.offsets.popper.position?0:m(d);a={top:0-(h.top-y),right:e.document.documentElement.clientWidth-(h.left-g),bottom:e.document.documentElement.clientHeight-(h.top-y),left:0-(h.left-g)}}else a=l(this._popper)===r?{top:0,left:0,right:r.clientWidth,bottom:r.clientHeight}:p(r);return a.left+=n,a.right-=n,a.top=a.top+n,a.bottom=a.bottom-n,a},n.prototype.runModifiers=function(e,t,n){var r=t.slice();return void 0!==n&&(r=this._options.modifiers.slice(0,a(this._options.modifiers,n))),r.forEach(function(t){d(t)&&(e=t.call(this,e))}.bind(this)),e},n.prototype.isModifierRequired=function(e,t){var n=a(this._options.modifiers,e);return!!this._options.modifiers.slice(0,n).filter((function(e){return e===t})).length},n.prototype.modifiers={},n.prototype.modifiers.applyStyle=function(e){var t,n={position:e.offsets.popper.position},r=Math.round(e.offsets.popper.left),o=Math.round(e.offsets.popper.top);return this._options.gpuAcceleration&&(t=m("transform"))?(n[t]="translate3d("+r+"px, "+o+"px, 0)",n.top=0,n.left=0):(n.left=r,n.top=o),Object.assign(n,e.styles),f(this._popper,n),this._popper.setAttribute("x-placement",e.placement),this.isModifierRequired(this.modifiers.applyStyle,this.modifiers.arrow)&&e.offsets.arrow&&f(e.arrowElement,e.offsets.arrow),e},n.prototype.modifiers.shift=function(e){var t=e.placement,n=t.split("-")[0],r=t.split("-")[1];if(r){var o=e.offsets.reference,a=i(e.offsets.popper),s={y:{start:{top:o.top},end:{top:o.top+o.height-a.height}},x:{start:{left:o.left},end:{left:o.left+o.width-a.width}}},l=-1!==["bottom","top"].indexOf(n)?"x":"y";e.offsets.popper=Object.assign(a,s[l][r])}return e},n.prototype.modifiers.preventOverflow=function(e){var t=this._options.preventOverflowOrder,n=i(e.offsets.popper),r={left:function(){var t=n.left;return n.lefte.boundaries.right&&(t=Math.min(n.left,e.boundaries.right-n.width)),{left:t}},top:function(){var t=n.top;return n.tope.boundaries.bottom&&(t=Math.min(n.top,e.boundaries.bottom-n.height)),{top:t}}};return t.forEach((function(t){e.offsets.popper=Object.assign(n,r[t]())})),e},n.prototype.modifiers.keepTogether=function(e){var t=i(e.offsets.popper),n=e.offsets.reference,r=Math.floor;return t.rightr(n.right)&&(e.offsets.popper.left=r(n.right)),t.bottomr(n.bottom)&&(e.offsets.popper.top=r(n.bottom)),e},n.prototype.modifiers.flip=function(e){if(!this.isModifierRequired(this.modifiers.flip,this.modifiers.preventOverflow))return console.warn("WARNING: preventOverflow modifier is required by flip modifier in order to work, be sure to include it before flip!"),e;if(e.flipped&&e.placement===e._originalPlacement)return e;var t=e.placement.split("-")[0],n=o(t),r=e.placement.split("-")[1]||"",a=[];return a="flip"===this._options.flipBehavior?[t,n]:this._options.flipBehavior,a.forEach(function(s,l){if(t===s&&a.length!==l+1){t=e.placement.split("-")[0],n=o(t);var u=i(e.offsets.popper),c=-1!==["right","bottom"].indexOf(t);(c&&Math.floor(e.offsets.reference[t])>Math.floor(u[n])||!c&&Math.floor(e.offsets.reference[t])s[p]&&(e.offsets.popper[f]+=l[f]+h-s[p]);var v=l[f]+(n||l[c]/2-h/2),m=v-s[f];return m=Math.max(Math.min(s[c]-h-8,m),8),o[f]=m,o[d]="",e.offsets.arrow=o,e.arrowElement=t,e},Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:function(e){if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var t=Object(e),n=1;nn},ie64:function(){return b.ie()&&d},firefox:function(){return g()||r},opera:function(){return g()||o},webkit:function(){return g()||i},safari:function(){return b.webkit()},chrome:function(){return g()||a},windows:function(){return g()||u},osx:function(){return g()||l},linux:function(){return g()||c},iphone:function(){return g()||p},mobile:function(){return g()||p||h||f||m},nativeApp:function(){return g()||v},android:function(){return g()||f},ipad:function(){return g()||h}};e.exports=b},"2ba5":function(e,t,n){var r=n("7a23"),o=n("c223"),i=n("aec8");e.exports=r?function(e,t,n){return o.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},"2bba":function(e,t,n){var r=n("ac83");e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(a){var i=e["return"];throw void 0!==i&&r(i.call(e)),a}}},3109:function(e,t,n){var r=n("d5dc"),o=n("527d"),i=r.WeakMap;e.exports="function"===typeof i&&/native code/.test(o(i))},3193:function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},"354c":function(e,t,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);t.f=i?function(e){var t=o(this,e);return!!t&&t.enumerable}:r},"375c":function(e,t,n){},"3a20":function(e,t,n){var r=n("efd1"),o=n("3d8a"),i=n("3f8e");r||o(Object.prototype,"toString",i,{unsafe:!0})},"3c84":function(e,t,n){"use strict";t.__esModule=!0,t.isInContainer=t.getScrollContainer=t.isScroll=t.getStyle=t.once=t.off=t.on=void 0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.hasClass=v,t.addClass=m,t.removeClass=y,t.setStyle=b;var o=n("0261"),i=a(o);function a(e){return e&&e.__esModule?e:{default:e}}var s=i.default.prototype.$isServer,l=/([\:\-\_]+(.))/g,u=/^moz([A-Z])/,c=s?0:Number(document.documentMode),f=function(e){return(e||"").replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g,"")},d=function(e){return e.replace(l,(function(e,t,n,r){return r?n.toUpperCase():n})).replace(u,"Moz$1")},p=t.on=function(){return!s&&document.addEventListener?function(e,t,n){e&&t&&n&&e.addEventListener(t,n,!1)}:function(e,t,n){e&&t&&n&&e.attachEvent("on"+t,n)}}(),h=t.off=function(){return!s&&document.removeEventListener?function(e,t,n){e&&t&&e.removeEventListener(t,n,!1)}:function(e,t,n){e&&t&&e.detachEvent("on"+t,n)}}();t.once=function(e,t,n){var r=function r(){n&&n.apply(this,arguments),h(e,t,r)};p(e,t,r)};function v(e,t){if(!e||!t)return!1;if(-1!==t.indexOf(" "))throw new Error("className should not contain space.");return e.classList?e.classList.contains(t):(" "+e.className+" ").indexOf(" "+t+" ")>-1}function m(e,t){if(e){for(var n=e.className,r=(t||"").split(" "),o=0,i=r.length;or.top&&n.right>r.left&&n.left2&&void 0!==arguments[2]?arguments[2]:300,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!e||!t)throw new Error("instance & callback is required");var o=!1,i=function(){o||(o=!0,t&&t.apply(null,arguments))};r?e.$once("after-leave",i):e.$on("after-leave",i),setTimeout((function(){i()}),n+100)}},"3d8a":function(e,t,n){var r=n("d5dc"),o=n("2ba5"),i=n("f28d"),a=n("200e"),s=n("527d"),l=n("d0e2"),u=l.get,c=l.enforce,f=String(String).split("String");(e.exports=function(e,t,n,s){var l=!!s&&!!s.unsafe,u=!!s&&!!s.enumerable,d=!!s&&!!s.noTargetGet;"function"==typeof n&&("string"!=typeof t||i(n,"name")||o(n,"name",t),c(n).source=f.join("string"==typeof t?t:"")),e!==r?(l?!d&&e[t]&&(u=!0):delete e[t],u?e[t]=n:o(e,t,n)):u?e[t]=n:a(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&u(this).source||s(this)}))},"3f5d":function(e,t,n){"use strict";var r=!("undefined"===typeof window||!window.document||!window.document.createElement),o={canUseDOM:r,canUseWorkers:"undefined"!==typeof Worker,canUseEventListeners:r&&!(!window.addEventListener&&!window.attachEvent),canUseViewport:r&&!!window.screen,isInWorker:!r};e.exports=o},"3f8e":function(e,t,n){"use strict";var r=n("efd1"),o=n("9552");e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},"40d4":function(e,t){e.exports=function(e,t,n){if(!(e instanceof t))throw TypeError("Incorrect "+(n?n+" ":"")+"invocation");return e}},"417f":function(e,t,n){var r=n("3d8a");e.exports=function(e,t,n){for(var o in t)r(e,o,t[o],n);return e}},4301:function(e,t,n){var r=n("ac83"),o=n("d68d"),i=n("df22");e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e),a=n.resolve;return a(t),n.promise}},4445:function(e,t,n){var r=n("4ccd");e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},4888:function(e,t){e.exports={}},"4aef":function(e,t,n){var r=n("7a23"),o=n("354c"),i=n("aec8"),a=n("8c47"),s=n("7dc7"),l=n("f28d"),u=n("88b4"),c=Object.getOwnPropertyDescriptor;t.f=r?c:function(e,t){if(e=a(e),t=s(t,!0),u)try{return c(e,t)}catch(n){}if(l(e,t))return i(!o.f.call(e,t),e[t])}},"4ccd":function(e,t,n){var r=n("f30e");e.exports=!!Object.getOwnPropertySymbols&&!r((function(){return!String(Symbol())}))},"4ce0":function(e,t,n){var r=n("ac83"),o=n("da66");e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,e.call(n,[]),t=n instanceof Array}catch(i){}return function(n,i){return r(n),o(i),t?e.call(n,i):n.__proto__=i,n}}():void 0)},"4d52":function(e,t,n){var r=n("f880"),o=n("9db6"),i=r("keys");e.exports=function(e){return i[e]||(i[e]=o(e))}},"4dbf":function(e,t,n){},"4eda":function(e,t,n){"use strict";t.__esModule=!0,t.PopupManager=void 0;var r=n("0261"),o=d(r),i=n("e93e"),a=d(i),s=n("7b6d"),l=d(s),u=n("e536"),c=d(u),f=n("3c84");function d(e){return e&&e.__esModule?e:{default:e}}var p=1,h=void 0;t.default={props:{visible:{type:Boolean,default:!1},openDelay:{},closeDelay:{},zIndex:{},modal:{type:Boolean,default:!1},modalFade:{type:Boolean,default:!0},modalClass:{},modalAppendToBody:{type:Boolean,default:!1},lockScroll:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!1},closeOnClickModal:{type:Boolean,default:!1}},beforeMount:function(){this._popupId="popup-"+p++,l.default.register(this._popupId,this)},beforeDestroy:function(){l.default.deregister(this._popupId),l.default.closeModal(this._popupId),this.restoreBodyStyle()},data:function(){return{opened:!1,bodyPaddingRight:null,computedBodyPaddingRight:0,withoutHiddenClass:!0,rendered:!1}},watch:{visible:function(e){var t=this;if(e){if(this._opening)return;this.rendered?this.open():(this.rendered=!0,o.default.nextTick((function(){t.open()})))}else this.close()}},methods:{open:function(e){var t=this;this.rendered||(this.rendered=!0);var n=(0,a.default)({},this.$props||this,e);this._closeTimer&&(clearTimeout(this._closeTimer),this._closeTimer=null),clearTimeout(this._openTimer);var r=Number(n.openDelay);r>0?this._openTimer=setTimeout((function(){t._openTimer=null,t.doOpen(n)}),r):this.doOpen(n)},doOpen:function(e){if(!this.$isServer&&(!this.willOpen||this.willOpen())&&!this.opened){this._opening=!0;var t=this.$el,n=e.modal,r=e.zIndex;if(r&&(l.default.zIndex=r),n&&(this._closing&&(l.default.closeModal(this._popupId),this._closing=!1),l.default.openModal(this._popupId,l.default.nextZIndex(),this.modalAppendToBody?void 0:t,e.modalClass,e.modalFade),e.lockScroll)){this.withoutHiddenClass=!(0,f.hasClass)(document.body,"el-popup-parent--hidden"),this.withoutHiddenClass&&(this.bodyPaddingRight=document.body.style.paddingRight,this.computedBodyPaddingRight=parseInt((0,f.getStyle)(document.body,"paddingRight"),10)),h=(0,c.default)();var o=document.documentElement.clientHeight0&&(o||"scroll"===i)&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.computedBodyPaddingRight+h+"px"),(0,f.addClass)(document.body,"el-popup-parent--hidden")}"static"===getComputedStyle(t).position&&(t.style.position="absolute"),t.style.zIndex=l.default.nextZIndex(),this.opened=!0,this.onOpen&&this.onOpen(),this.doAfterOpen()}},doAfterOpen:function(){this._opening=!1},close:function(){var e=this;if(!this.willClose||this.willClose()){null!==this._openTimer&&(clearTimeout(this._openTimer),this._openTimer=null),clearTimeout(this._closeTimer);var t=Number(this.closeDelay);t>0?this._closeTimer=setTimeout((function(){e._closeTimer=null,e.doClose()}),t):this.doClose()}},doClose:function(){this._closing=!0,this.onClose&&this.onClose(),this.lockScroll&&setTimeout(this.restoreBodyStyle,200),this.opened=!1,this.doAfterClose()},doAfterClose:function(){l.default.closeModal(this._popupId),this._closing=!1},restoreBodyStyle:function(){this.modal&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.bodyPaddingRight,(0,f.removeClass)(document.body,"el-popup-parent--hidden")),this.withoutHiddenClass=!0}}},t.PopupManager=l.default},"4f91":function(e,t,n){"use strict";t.__esModule=!0;var r=r||{};r.Utils=r.Utils||{},r.Utils.focusFirstDescendant=function(e){for(var t=0;t=0;t--){var n=e.childNodes[t];if(r.Utils.attemptFocus(n)||r.Utils.focusLastDescendant(n))return!0}return!1},r.Utils.attemptFocus=function(e){if(!r.Utils.isFocusable(e))return!1;r.Utils.IgnoreUtilFocusChanges=!0;try{e.focus()}catch(t){}return r.Utils.IgnoreUtilFocusChanges=!1,document.activeElement===e},r.Utils.isFocusable=function(e){if(e.tabIndex>0||0===e.tabIndex&&null!==e.getAttribute("tabIndex"))return!0;if(e.disabled)return!1;switch(e.nodeName){case"A":return!!e.href&&"ignore"!==e.rel;case"INPUT":return"hidden"!==e.type&&"file"!==e.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},r.Utils.triggerEvent=function(e,t){var n=void 0;n=/^mouse|click/.test(t)?"MouseEvents":/^key/.test(t)?"KeyboardEvent":"HTMLEvents";for(var r=document.createEvent(n),o=arguments.length,i=Array(o>2?o-2:0),a=2;al)o.f(e,n=r[l++],t[n]);return e}},5646:function(e,t,n){"use strict";var r=n("91fe"),o=n("ed51"),i=n("90a7"),a=n("4ce0"),s=n("94d7"),l=n("2ba5"),u=n("3d8a"),c=n("57c4"),f=n("e17a"),d=n("ed35"),p=n("143b"),h=p.IteratorPrototype,v=p.BUGGY_SAFARI_ITERATORS,m=c("iterator"),y="keys",g="values",b="entries",_=function(){return this};e.exports=function(e,t,n,c,p,x,w){o(n,t,c);var C,S,O,E=function(e){if(e===p&&T)return T;if(!v&&e in j)return j[e];switch(e){case y:return function(){return new n(this,e)};case g:return function(){return new n(this,e)};case b:return function(){return new n(this,e)}}return function(){return new n(this)}},k=t+" Iterator",$=!1,j=e.prototype,A=j[m]||j["@@iterator"]||p&&j[p],T=!v&&A||E(p),M="Array"==t&&j.entries||A;if(M&&(C=i(M.call(new e)),h!==Object.prototype&&C.next&&(f||i(C)===h||(a?a(C,h):"function"!=typeof C[m]&&l(C,m,_)),s(C,k,!0,!0),f&&(d[k]=_))),p==g&&A&&A.name!==g&&($=!0,T=function(){return A.call(this)}),f&&!w||j[m]===T||l(j,m,T),d[t]=T,p)if(S={values:E(g),keys:x?T:E(y),entries:E(b)},w)for(O in S)!v&&!$&&O in j||u(j,O,S[O]);else r({target:t,proto:!0,forced:v||$},S);return S}},5751:function(e,t,n){var r=n("57c4"),o=n("641d"),i=n("c223"),a=r("unscopables"),s=Array.prototype;void 0==s[a]&&i.f(s,a,{configurable:!0,value:o(null)}),e.exports=function(e){s[a][e]=!0}},"57c4":function(e,t,n){var r=n("d5dc"),o=n("f880"),i=n("f28d"),a=n("9db6"),s=n("4ccd"),l=n("4445"),u=o("wks"),c=r.Symbol,f=l?c:c&&c.withoutSetter||a;e.exports=function(e){return i(u,e)||(s&&i(c,e)?u[e]=c[e]:u[e]=f("Symbol."+e)),u[e]}},"5baf":function(e,t,n){"use strict";var r=function(e){return o(e)&&!i(e)};function o(e){return!!e&&"object"===typeof e}function i(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||l(e)}var a="function"===typeof Symbol&&Symbol.for,s=a?Symbol.for("react.element"):60103;function l(e){return e.$$typeof===s}function u(e){return Array.isArray(e)?[]:{}}function c(e,t){var n=t&&!0===t.clone;return n&&r(e)?p(u(e),e,t):e}function f(e,t,n){var o=e.slice();return t.forEach((function(t,i){"undefined"===typeof o[i]?o[i]=c(t,n):r(t)?o[i]=p(e[i],t,n):-1===e.indexOf(t)&&o.push(c(t,n))})),o}function d(e,t,n){var o={};return r(e)&&Object.keys(e).forEach((function(t){o[t]=c(e[t],n)})),Object.keys(t).forEach((function(i){r(t[i])&&e[i]?o[i]=p(e[i],t[i],n):o[i]=c(t[i],n)})),o}function p(e,t,n){var r=Array.isArray(t),o=Array.isArray(e),i=n||{arrayMerge:f},a=r===o;if(a){if(r){var s=i.arrayMerge||f;return s(e,t,n)}return d(e,t,n)}return c(t,n)}p.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce((function(e,n){return p(e,n,t)}))};var h=p;e.exports=h},"5c90":function(e,t){e.exports=function(e){try{return{error:!1,value:e()}}catch(t){return{error:!0,value:t}}}},"60f8":function(e,t){e.exports=function(e,t,n,r){var o,i=0;function a(){var a=this,s=Number(new Date)-i,l=arguments;function u(){i=Number(new Date),n.apply(a,l)}function c(){o=void 0}r&&!o&&u(),o&&clearTimeout(o),void 0===r&&s>e?u():!0!==t&&(o=setTimeout(r?c:u,void 0===r?e-s:e))}return("boolean"!==typeof t&&(r=n,n=t,t=void 0),a)}},"618d":function(e,t,n){"use strict";var r=n("91fe"),o=n("e17a"),i=n("644f"),a=n("f30e"),s=n("df50"),l=n("fb8e"),u=n("4301"),c=n("3d8a"),f=!!i&&a((function(){i.prototype["finally"].call({then:function(){}},(function(){}))}));r({target:"Promise",proto:!0,real:!0,forced:f},{finally:function(e){var t=l(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return u(t,e()).then((function(){return n}))}:e,n?function(n){return u(t,e()).then((function(){throw n}))}:e)}}),o||"function"!=typeof i||i.prototype["finally"]||c(i.prototype,"finally",s("Promise").prototype["finally"])},"623f":function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},"63ec":function(e,t,n){var r=n("60f8"),o=n("ca47");e.exports={throttle:r,debounce:o}},6406:function(e,t,n){"use strict";t.__esModule=!0,t.i18n=t.use=t.t=void 0;var r=n("83a6"),o=f(r),i=n("0261"),a=f(i),s=n("5baf"),l=f(s),u=n("88e7"),c=f(u);function f(e){return e&&e.__esModule?e:{default:e}}var d=(0,c.default)(a.default),p=o.default,h=!1,v=function(){var e=Object.getPrototypeOf(this||a.default).$t;if("function"===typeof e&&a.default.locale)return h||(h=!0,a.default.locale(a.default.config.lang,(0,l.default)(p,a.default.locale(a.default.config.lang)||{},{clone:!0}))),e.apply(this,arguments)},m=t.t=function(e,t){var n=v.apply(this,arguments);if(null!==n&&void 0!==n)return n;for(var r=e.split("."),o=p,i=0,a=r.length;i",d="<",p="prototype",h="script",v=c("IE_PROTO"),m=function(){},y=function(e){return d+h+f+e+d+"/"+h+f},g=function(e){e.write(y("")),e.close();var t=e.parentWindow.Object;return e=null,t},b=function(){var e,t=u("iframe"),n="java"+h+":";return t.style.display="none",l.appendChild(t),t.src=String(n),e=t.contentWindow.document,e.open(),e.write(y("document.F=Object")),e.close(),e.F},_=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(t){}_=r?g(r):b();var e=a.length;while(e--)delete _[p][a[e]];return _()};s[v]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(m[p]=o(e),n=new m,m[p]=null,n[v]=e):n=_(),void 0===t?n:i(n,t)}},"644f":function(e,t,n){var r=n("d5dc");e.exports=r.Promise},"64d6":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=124)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},124:function(e,t,n){"use strict";n.r(t);var r,o,i={name:"ElTag",props:{text:String,closable:Boolean,type:String,hit:Boolean,disableTransitions:Boolean,color:String,size:String,effect:{type:String,default:"light",validator:function(e){return-1!==["dark","light","plain"].indexOf(e)}}},methods:{handleClose:function(e){e.stopPropagation(),this.$emit("close",e)},handleClick:function(e){this.$emit("click",e)}},computed:{tagSize:function(){return this.size||(this.$ELEMENT||{}).size}},render:function(e){var t=this.type,n=this.tagSize,r=this.hit,o=this.effect,i=["el-tag",t?"el-tag--"+t:"",n?"el-tag--"+n:"",o?"el-tag--"+o:"",r&&"is-hit"],a=e("span",{class:i,style:{backgroundColor:this.color},on:{click:this.handleClick}},[this.$slots.default,this.closable&&e("i",{class:"el-tag__close el-icon-close",on:{click:this.handleClose}})]);return this.disableTransitions?a:e("transition",{attrs:{name:"el-zoom-in-center"}},[a])}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/tag/src/tag.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},"65af":function(e,t,n){var r=n("02d0"),o=n("6807"),i=o.concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},"66b2":function(e,t,n){var r=n("ac83"),o=n("0532"),i=n("684e"),a=n("0b29"),s=n("e28b"),l=n("2bba"),u=function(e,t){this.stopped=e,this.result=t},c=e.exports=function(e,t,n,c,f){var d,p,h,v,m,y,g,b=a(t,n,c?2:1);if(f)d=e;else{if(p=s(e),"function"!=typeof p)throw TypeError("Target is not iterable");if(o(p)){for(h=0,v=i(e.length);v>h;h++)if(m=c?b(r(g=e[h])[0],g[1]):b(e[h]),m&&m instanceof u)return m;return new u(!1)}d=p.call(e)}y=d.next;while(!(g=y.call(d)).done)if(m=l(d,b,g.value,c),"object"==typeof m&&m&&m instanceof u)return m;return new u(!1)};c.stop=function(e){return new u(!0,e)}},"67ea":function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},6807:function(e,t){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"684e":function(e,t,n){var r=n("f240"),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},"6be9":function(e,t,n){var r=n("8c47"),o=n("684e"),i=n("0192"),a=function(e){return function(t,n,a){var s,l=r(t),u=o(l.length),c=i(a,u);if(e&&n!=n){while(u>c)if(s=l[c++],s!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===n)return e||c||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},"6dcf":function(e,t,n){var r,o,i,a=n("d5dc"),s=n("f30e"),l=n("67ea"),u=n("0b29"),c=n("c49e"),f=n("032e"),d=n("c044"),p=a.location,h=a.setImmediate,v=a.clearImmediate,m=a.process,y=a.MessageChannel,g=a.Dispatch,b=0,_={},x="onreadystatechange",w=function(e){if(_.hasOwnProperty(e)){var t=_[e];delete _[e],t()}},C=function(e){return function(){w(e)}},S=function(e){w(e.data)},O=function(e){a.postMessage(e+"",p.protocol+"//"+p.host)};h&&v||(h=function(e){var t=[],n=1;while(arguments.length>n)t.push(arguments[n++]);return _[++b]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},r(b),b},v=function(e){delete _[e]},"process"==l(m)?r=function(e){m.nextTick(C(e))}:g&&g.now?r=function(e){g.now(C(e))}:y&&!d?(o=new y,i=o.port2,o.port1.onmessage=S,r=u(i.postMessage,i,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||s(O)?r=x in f("script")?function(e){c.appendChild(f("script"))[x]=function(){c.removeChild(this),w(e)}}:function(e){setTimeout(C(e),0)}:(r=O,a.addEventListener("message",S,!1))),e.exports={set:h,clear:v}},"70d0":function(e,t,n){},7267:function(e,t,n){"use strict";var r=n("3d8a"),o=n("ac83"),i=n("f30e"),a=n("0618"),s="toString",l=RegExp.prototype,u=l[s],c=i((function(){return"/a/b"!=u.call({source:"a",flags:"b"})})),f=u.name!=s;(c||f)&&r(RegExp.prototype,s,(function(){var e=o(this),t=String(e.source),n=e.flags,r=String(void 0===n&&e instanceof RegExp&&!("flags"in l)?a.call(e):n);return"/"+t+"/"+r}),{unsafe:!0})},"76ab":function(e,t,n){"use strict";var r=n("2ae1"),o=n("2895"),i=10,a=40,s=800;function l(e){var t=0,n=0,r=0,o=0;return"detail"in e&&(n=e.detail),"wheelDelta"in e&&(n=-e.wheelDelta/120),"wheelDeltaY"in e&&(n=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(t=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(t=n,n=0),r=t*i,o=n*i,"deltaY"in e&&(o=e.deltaY),"deltaX"in e&&(r=e.deltaX),(r||o)&&e.deltaMode&&(1==e.deltaMode?(r*=a,o*=a):(r*=s,o*=s)),r&&!t&&(t=r<1?-1:1),o&&!n&&(n=o<1?-1:1),{spinX:t,spinY:n,pixelX:r,pixelY:o}}l.getEventType=function(){return r.firefox()?"DOMMouseScroll":o("wheel")?"wheel":"mousewheel"},e.exports=l},"77e5":function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(r){"object"===typeof window&&(n=window)}e.exports=n},"785d":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=75)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},15:function(e,t){e.exports=n("4eda")},23:function(e,t){e.exports=n("bee9")},7:function(e,t){e.exports=n("0261")},75:function(e,t,n){"use strict";n.r(t);var r=n(7),o=n.n(r),i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"el-message-fade"},on:{"after-leave":e.handleAfterLeave}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],class:["el-message",e.type&&!e.iconClass?"el-message--"+e.type:"",e.center?"is-center":"",e.showClose?"is-closable":"",e.customClass],style:e.positionStyle,attrs:{role:"alert"},on:{mouseenter:e.clearTimer,mouseleave:e.startTimer}},[e.iconClass?n("i",{class:e.iconClass}):n("i",{class:e.typeClass}),e._t("default",[e.dangerouslyUseHTMLString?n("p",{staticClass:"el-message__content",domProps:{innerHTML:e._s(e.message)}}):n("p",{staticClass:"el-message__content"},[e._v(e._s(e.message))])]),e.showClose?n("i",{staticClass:"el-message__closeBtn el-icon-close",on:{click:e.close}}):e._e()],2)])},a=[];i._withStripped=!0;var s={success:"success",info:"info",warning:"warning",error:"error"},l={data:function(){return{visible:!1,message:"",duration:3e3,type:"info",iconClass:"",customClass:"",onClose:null,showClose:!1,closed:!1,verticalOffset:20,timer:null,dangerouslyUseHTMLString:!1,center:!1}},computed:{typeClass:function(){return this.type&&!this.iconClass?"el-message__icon el-icon-"+s[this.type]:""},positionStyle:function(){return{top:this.verticalOffset+"px"}}},watch:{closed:function(e){e&&(this.visible=!1)}},methods:{handleAfterLeave:function(){this.$destroy(!0),this.$el.parentNode.removeChild(this.$el)},close:function(){this.closed=!0,"function"===typeof this.onClose&&this.onClose(this)},clearTimer:function(){clearTimeout(this.timer)},startTimer:function(){var e=this;this.duration>0&&(this.timer=setTimeout((function(){e.closed||e.close()}),this.duration))},keydown:function(e){27===e.keyCode&&(this.closed||this.close())}},mounted:function(){this.startTimer(),document.addEventListener("keydown",this.keydown)},beforeDestroy:function(){document.removeEventListener("keydown",this.keydown)}},u=l,c=n(0),f=Object(c["a"])(u,i,a,!1,null,null,null);f.options.__file="packages/message/src/main.vue";var d=f.exports,p=n(15),h=n(23),v=o.a.extend(d),m=void 0,y=[],g=1,b=function e(t){if(!o.a.prototype.$isServer){t=t||{},"string"===typeof t&&(t={message:t});var n=t.onClose,r="message_"+g++;t.onClose=function(){e.close(r,n)},m=new v({data:t}),m.id=r,Object(h["isVNode"])(m.message)&&(m.$slots.default=[m.message],m.message=null),m.$mount(),document.body.appendChild(m.$el);var i=t.offset||20;return y.forEach((function(e){i+=e.$el.offsetHeight+16})),m.verticalOffset=i,m.visible=!0,m.$el.style.zIndex=p["PopupManager"].nextZIndex(),y.push(m),m}};["success","warning","info","error"].forEach((function(e){b[e]=function(t){return"string"===typeof t&&(t={message:t}),t.type=e,b(t)}})),b.close=function(e,t){for(var n=y.length,r=-1,o=void 0,i=0;iy.length-1))for(var a=r;a=0;e--)y[e].close()};var _=b;t["default"]=_}})},"7a23":function(e,t,n){var r=n("f30e");e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},"7b6d":function(e,t,n){"use strict";t.__esModule=!0;var r=n("0261"),o=a(r),i=n("3c84");function a(e){return e&&e.__esModule?e:{default:e}}var s=!1,l=!1,u=void 0,c=function(){if(!o.default.prototype.$isServer){var e=d.modalDom;return e?s=!0:(s=!1,e=document.createElement("div"),d.modalDom=e,e.addEventListener("touchmove",(function(e){e.preventDefault(),e.stopPropagation()})),e.addEventListener("click",(function(){d.doOnModalClick&&d.doOnModalClick()}))),e}},f={},d={modalFade:!0,getInstance:function(e){return f[e]},register:function(e,t){e&&t&&(f[e]=t)},deregister:function(e){e&&(f[e]=null,delete f[e])},nextZIndex:function(){return d.zIndex++},modalStack:[],doOnModalClick:function(){var e=d.modalStack[d.modalStack.length-1];if(e){var t=d.getInstance(e.id);t&&t.closeOnClickModal&&t.close()}},openModal:function(e,t,n,r,a){if(!o.default.prototype.$isServer&&e&&void 0!==t){this.modalFade=a;for(var l=this.modalStack,u=0,f=l.length;u0){var r=t[t.length-1];if(r.id===e){if(r.modalClass){var o=r.modalClass.trim().split(/\s+/);o.forEach((function(e){return(0,i.removeClass)(n,e)}))}t.pop(),t.length>0&&(n.style.zIndex=t[t.length-1].zIndex)}else for(var a=t.length-1;a>=0;a--)if(t[a].id===e){t.splice(a,1);break}}0===t.length&&(this.modalFade&&(0,i.addClass)(n,"v-modal-leave"),setTimeout((function(){0===t.length&&(n.parentNode&&n.parentNode.removeChild(n),n.style.display="none",d.modalDom=void 0),(0,i.removeClass)(n,"v-modal-leave")}),200))}};Object.defineProperty(d,"zIndex",{configurable:!0,get:function(){return l||(u=u||(o.default.prototype.$ELEMENT||{}).zIndex||2e3,l=!0),u},set:function(e){u=e}});var p=function(){if(!o.default.prototype.$isServer&&d.modalStack.length>0){var e=d.modalStack[d.modalStack.length-1];if(!e)return;var t=d.getInstance(e.id);return t}};o.default.prototype.$isServer||window.addEventListener("keydown",(function(e){if(27===e.keyCode){var t=p();t&&t.closeOnPressEscape&&(t.handleClose?t.handleClose():t.handleAction?t.handleAction("cancel"):t.close())}})),t.default=d},"7dc7":function(e,t,n){var r=n("d68d");e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},"83a6":function(e,t,n){"use strict";t.__esModule=!0,t.default={el:{colorpicker:{confirm:"确定",clear:"清空"},datepicker:{now:"此刻",today:"今天",cancel:"取消",clear:"清空",confirm:"确定",selectDate:"选择日期",selectTime:"选择时间",startDate:"开始日期",startTime:"开始时间",endDate:"结束日期",endTime:"结束时间",prevYear:"前一年",nextYear:"后一年",prevMonth:"上个月",nextMonth:"下个月",year:"年",month1:"1 月",month2:"2 月",month3:"3 月",month4:"4 月",month5:"5 月",month6:"6 月",month7:"7 月",month8:"8 月",month9:"9 月",month10:"10 月",month11:"11 月",month12:"12 月",weeks:{sun:"日",mon:"一",tue:"二",wed:"三",thu:"四",fri:"五",sat:"六"},months:{jan:"一月",feb:"二月",mar:"三月",apr:"四月",may:"五月",jun:"六月",jul:"七月",aug:"八月",sep:"九月",oct:"十月",nov:"十一月",dec:"十二月"}},select:{loading:"加载中",noMatch:"无匹配数据",noData:"无数据",placeholder:"请选择"},cascader:{noMatch:"无匹配数据",loading:"加载中",placeholder:"请选择",noData:"暂无数据"},pagination:{goto:"前往",pagesize:"条/页",total:"共 {total} 条",pageClassifier:"页"},messagebox:{title:"提示",confirm:"确定",cancel:"取消",error:"输入的数据不合法!"},upload:{deleteTip:"按 delete 键可删除",delete:"删除",preview:"查看图片",continue:"继续上传"},table:{emptyText:"暂无数据",confirmFilter:"筛选",resetFilter:"重置",clearFilter:"全部",sumText:"合计"},tree:{emptyText:"暂无数据"},transfer:{noMatch:"无匹配数据",noData:"无数据",titles:["列表 1","列表 2"],filterPlaceholder:"请输入搜索内容",noCheckedFormat:"共 {total} 项",hasCheckedFormat:"已选 {checked}/{total} 项"},image:{error:"加载失败"},pageHeader:{title:"返回"},popconfirm:{confirmButtonText:"确定",cancelButtonText:"取消"}}}},8508:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=83)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},4:function(e,t){e.exports=n("b4e0")},83:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("label",{staticClass:"el-checkbox",class:[e.border&&e.checkboxSize?"el-checkbox--"+e.checkboxSize:"",{"is-disabled":e.isDisabled},{"is-bordered":e.border},{"is-checked":e.isChecked}],attrs:{id:e.id}},[n("span",{staticClass:"el-checkbox__input",class:{"is-disabled":e.isDisabled,"is-checked":e.isChecked,"is-indeterminate":e.indeterminate,"is-focus":e.focus},attrs:{tabindex:!!e.indeterminate&&0,role:!!e.indeterminate&&"checkbox","aria-checked":!!e.indeterminate&&"mixed"}},[n("span",{staticClass:"el-checkbox__inner"}),e.trueLabel||e.falseLabel?n("input",{directives:[{name:"model",rawName:"v-model",value:e.model,expression:"model"}],staticClass:"el-checkbox__original",attrs:{type:"checkbox","aria-hidden":e.indeterminate?"true":"false",name:e.name,disabled:e.isDisabled,"true-value":e.trueLabel,"false-value":e.falseLabel},domProps:{checked:Array.isArray(e.model)?e._i(e.model,null)>-1:e._q(e.model,e.trueLabel)},on:{change:[function(t){var n=e.model,r=t.target,o=r.checked?e.trueLabel:e.falseLabel;if(Array.isArray(n)){var i=null,a=e._i(n,i);r.checked?a<0&&(e.model=n.concat([i])):a>-1&&(e.model=n.slice(0,a).concat(n.slice(a+1)))}else e.model=o},e.handleChange],focus:function(t){e.focus=!0},blur:function(t){e.focus=!1}}}):n("input",{directives:[{name:"model",rawName:"v-model",value:e.model,expression:"model"}],staticClass:"el-checkbox__original",attrs:{type:"checkbox","aria-hidden":e.indeterminate?"true":"false",disabled:e.isDisabled,name:e.name},domProps:{value:e.label,checked:Array.isArray(e.model)?e._i(e.model,e.label)>-1:e.model},on:{change:[function(t){var n=e.model,r=t.target,o=!!r.checked;if(Array.isArray(n)){var i=e.label,a=e._i(n,i);r.checked?a<0&&(e.model=n.concat([i])):a>-1&&(e.model=n.slice(0,a).concat(n.slice(a+1)))}else e.model=o},e.handleChange],focus:function(t){e.focus=!0},blur:function(t){e.focus=!1}}})]),e.$slots.default||e.label?n("span",{staticClass:"el-checkbox__label"},[e._t("default"),e.$slots.default?e._e():[e._v(e._s(e.label))]],2):e._e()])},o=[];r._withStripped=!0;var i=n(4),a=n.n(i),s={name:"ElCheckbox",mixins:[a.a],inject:{elForm:{default:""},elFormItem:{default:""}},componentName:"ElCheckbox",data:function(){return{selfModel:!1,focus:!1,isLimitExceeded:!1}},computed:{model:{get:function(){return this.isGroup?this.store:void 0!==this.value?this.value:this.selfModel},set:function(e){this.isGroup?(this.isLimitExceeded=!1,void 0!==this._checkboxGroup.min&&e.lengththis._checkboxGroup.max&&(this.isLimitExceeded=!0),!1===this.isLimitExceeded&&this.dispatch("ElCheckboxGroup","input",[e])):(this.$emit("input",e),this.selfModel=e)}},isChecked:function(){return"[object Boolean]"==={}.toString.call(this.model)?this.model:Array.isArray(this.model)?this.model.indexOf(this.label)>-1:null!==this.model&&void 0!==this.model?this.model===this.trueLabel:void 0},isGroup:function(){var e=this.$parent;while(e){if("ElCheckboxGroup"===e.$options.componentName)return this._checkboxGroup=e,!0;e=e.$parent}return!1},store:function(){return this._checkboxGroup?this._checkboxGroup.value:this.value},isLimitDisabled:function(){var e=this._checkboxGroup,t=e.max,n=e.min;return!(!t&&!n)&&this.model.length>=t&&!this.isChecked||this.model.length<=n&&this.isChecked},isDisabled:function(){return this.isGroup?this._checkboxGroup.disabled||this.disabled||(this.elForm||{}).disabled||this.isLimitDisabled:this.disabled||(this.elForm||{}).disabled},_elFormItemSize:function(){return(this.elFormItem||{}).elFormItemSize},checkboxSize:function(){var e=this.size||this._elFormItemSize||(this.$ELEMENT||{}).size;return this.isGroup&&this._checkboxGroup.checkboxGroupSize||e}},props:{value:{},label:{},indeterminate:Boolean,disabled:Boolean,checked:Boolean,name:String,trueLabel:[String,Number],falseLabel:[String,Number],id:String,controls:String,border:Boolean,size:String},methods:{addToStore:function(){Array.isArray(this.model)&&-1===this.model.indexOf(this.label)?this.model.push(this.label):this.model=this.trueLabel||!0},handleChange:function(e){var t=this;if(!this.isLimitExceeded){var n=void 0;n=e.target.checked?void 0===this.trueLabel||this.trueLabel:void 0!==this.falseLabel&&this.falseLabel,this.$emit("change",n,e),this.$nextTick((function(){t.isGroup&&t.dispatch("ElCheckboxGroup","change",[t._checkboxGroup.value])}))}}},created:function(){this.checked&&this.addToStore()},mounted:function(){this.indeterminate&&this.$el.setAttribute("aria-controls",this.controls)},watch:{value:function(e){this.dispatch("ElFormItem","el.form.change",e)}}},l=s,u=n(0),c=Object(u["a"])(l,r,o,!1,null,null,null);c.options.__file="packages/checkbox/src/checkbox.vue";var f=c.exports;f.install=function(e){e.component(f.name,f)};t["default"]=f}})},8876:function(e,t,n){"use strict";(function(e){ -/** - * vuex v3.1.3 - * (c) 2020 Evan You - * @license MIT - */ -function n(e){var t=Number(e.version.split(".")[0]);if(t>=2)e.mixin({beforeCreate:r});else{var n=e.prototype._init;e.prototype._init=function(e){void 0===e&&(e={}),e.init=e.init?[r].concat(e.init):r,n.call(this,e)}}function r(){var e=this.$options;e.store?this.$store="function"===typeof e.store?e.store():e.store:e.parent&&e.parent.$store&&(this.$store=e.parent.$store)}}var r="undefined"!==typeof window?window:"undefined"!==typeof e?e:{},o=r.__VUE_DEVTOOLS_GLOBAL_HOOK__;function i(e){o&&(e._devtoolHook=o,o.emit("vuex:init",e),o.on("vuex:travel-to-state",(function(t){e.replaceState(t)})),e.subscribe((function(e,t){o.emit("vuex:mutation",e,t)})))}function a(e,t){Object.keys(e).forEach((function(n){return t(e[n],n)}))}function s(e){return null!==e&&"object"===typeof e}function l(e){return e&&"function"===typeof e.then}function u(e,t){return function(){return e(t)}}var c=function(e,t){this.runtime=t,this._children=Object.create(null),this._rawModule=e;var n=e.state;this.state=("function"===typeof n?n():n)||{}},f={namespaced:{configurable:!0}};f.namespaced.get=function(){return!!this._rawModule.namespaced},c.prototype.addChild=function(e,t){this._children[e]=t},c.prototype.removeChild=function(e){delete this._children[e]},c.prototype.getChild=function(e){return this._children[e]},c.prototype.update=function(e){this._rawModule.namespaced=e.namespaced,e.actions&&(this._rawModule.actions=e.actions),e.mutations&&(this._rawModule.mutations=e.mutations),e.getters&&(this._rawModule.getters=e.getters)},c.prototype.forEachChild=function(e){a(this._children,e)},c.prototype.forEachGetter=function(e){this._rawModule.getters&&a(this._rawModule.getters,e)},c.prototype.forEachAction=function(e){this._rawModule.actions&&a(this._rawModule.actions,e)},c.prototype.forEachMutation=function(e){this._rawModule.mutations&&a(this._rawModule.mutations,e)},Object.defineProperties(c.prototype,f);var d=function(e){this.register([],e,!1)};function p(e,t,n){if(t.update(n),n.modules)for(var r in n.modules){if(!t.getChild(r))return void 0;p(e.concat(r),t.getChild(r),n.modules[r])}}d.prototype.get=function(e){return e.reduce((function(e,t){return e.getChild(t)}),this.root)},d.prototype.getNamespace=function(e){var t=this.root;return e.reduce((function(e,n){return t=t.getChild(n),e+(t.namespaced?n+"/":"")}),"")},d.prototype.update=function(e){p([],this.root,e)},d.prototype.register=function(e,t,n){var r=this;void 0===n&&(n=!0);var o=new c(t,n);if(0===e.length)this.root=o;else{var i=this.get(e.slice(0,-1));i.addChild(e[e.length-1],o)}t.modules&&a(t.modules,(function(t,o){r.register(e.concat(o),t,n)}))},d.prototype.unregister=function(e){var t=this.get(e.slice(0,-1)),n=e[e.length-1];t.getChild(n).runtime&&t.removeChild(n)};var h;var v=function(e){var t=this;void 0===e&&(e={}),!h&&"undefined"!==typeof window&&window.Vue&&j(window.Vue);var n=e.plugins;void 0===n&&(n=[]);var r=e.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new d(e),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new h,this._makeLocalGettersCache=Object.create(null);var o=this,a=this,s=a.dispatch,l=a.commit;this.dispatch=function(e,t){return s.call(o,e,t)},this.commit=function(e,t,n){return l.call(o,e,t,n)},this.strict=r;var u=this._modules.root.state;_(this,u,[],this._modules.root),b(this,u),n.forEach((function(e){return e(t)}));var c=void 0!==e.devtools?e.devtools:h.config.devtools;c&&i(this)},m={state:{configurable:!0}};function y(e,t){return t.indexOf(e)<0&&t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function g(e,t){e._actions=Object.create(null),e._mutations=Object.create(null),e._wrappedGetters=Object.create(null),e._modulesNamespaceMap=Object.create(null);var n=e.state;_(e,n,[],e._modules.root,!0),b(e,n,t)}function b(e,t,n){var r=e._vm;e.getters={},e._makeLocalGettersCache=Object.create(null);var o=e._wrappedGetters,i={};a(o,(function(t,n){i[n]=u(t,e),Object.defineProperty(e.getters,n,{get:function(){return e._vm[n]},enumerable:!0})}));var s=h.config.silent;h.config.silent=!0,e._vm=new h({data:{$$state:t},computed:i}),h.config.silent=s,e.strict&&E(e),r&&(n&&e._withCommit((function(){r._data.$$state=null})),h.nextTick((function(){return r.$destroy()})))}function _(e,t,n,r,o){var i=!n.length,a=e._modules.getNamespace(n);if(r.namespaced&&(e._modulesNamespaceMap[a],e._modulesNamespaceMap[a]=r),!i&&!o){var s=k(t,n.slice(0,-1)),l=n[n.length-1];e._withCommit((function(){h.set(s,l,r.state)}))}var u=r.context=x(e,a,n);r.forEachMutation((function(t,n){var r=a+n;C(e,r,t,u)})),r.forEachAction((function(t,n){var r=t.root?n:a+n,o=t.handler||t;S(e,r,o,u)})),r.forEachGetter((function(t,n){var r=a+n;O(e,r,t,u)})),r.forEachChild((function(r,i){_(e,t,n.concat(i),r,o)}))}function x(e,t,n){var r=""===t,o={dispatch:r?e.dispatch:function(n,r,o){var i=$(n,r,o),a=i.payload,s=i.options,l=i.type;return s&&s.root||(l=t+l),e.dispatch(l,a)},commit:r?e.commit:function(n,r,o){var i=$(n,r,o),a=i.payload,s=i.options,l=i.type;s&&s.root||(l=t+l),e.commit(l,a,s)}};return Object.defineProperties(o,{getters:{get:r?function(){return e.getters}:function(){return w(e,t)}},state:{get:function(){return k(e.state,n)}}}),o}function w(e,t){if(!e._makeLocalGettersCache[t]){var n={},r=t.length;Object.keys(e.getters).forEach((function(o){if(o.slice(0,r)===t){var i=o.slice(r);Object.defineProperty(n,i,{get:function(){return e.getters[o]},enumerable:!0})}})),e._makeLocalGettersCache[t]=n}return e._makeLocalGettersCache[t]}function C(e,t,n,r){var o=e._mutations[t]||(e._mutations[t]=[]);o.push((function(t){n.call(e,r.state,t)}))}function S(e,t,n,r){var o=e._actions[t]||(e._actions[t]=[]);o.push((function(t){var o=n.call(e,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:e.getters,rootState:e.state},t);return l(o)||(o=Promise.resolve(o)),e._devtoolHook?o.catch((function(t){throw e._devtoolHook.emit("vuex:error",t),t})):o}))}function O(e,t,n,r){e._wrappedGetters[t]||(e._wrappedGetters[t]=function(e){return n(r.state,r.getters,e.state,e.getters)})}function E(e){e._vm.$watch((function(){return this._data.$$state}),(function(){0}),{deep:!0,sync:!0})}function k(e,t){return t.reduce((function(e,t){return e[t]}),e)}function $(e,t,n){return s(e)&&e.type&&(n=t,t=e,e=e.type),{type:e,payload:t,options:n}}function j(e){h&&e===h||(h=e,n(h))}m.state.get=function(){return this._vm._data.$$state},m.state.set=function(e){0},v.prototype.commit=function(e,t,n){var r=this,o=$(e,t,n),i=o.type,a=o.payload,s=(o.options,{type:i,payload:a}),l=this._mutations[i];l&&(this._withCommit((function(){l.forEach((function(e){e(a)}))})),this._subscribers.slice().forEach((function(e){return e(s,r.state)})))},v.prototype.dispatch=function(e,t){var n=this,r=$(e,t),o=r.type,i=r.payload,a={type:o,payload:i},s=this._actions[o];if(s){try{this._actionSubscribers.slice().filter((function(e){return e.before})).forEach((function(e){return e.before(a,n.state)}))}catch(u){0}var l=s.length>1?Promise.all(s.map((function(e){return e(i)}))):s[0](i);return l.then((function(e){try{n._actionSubscribers.filter((function(e){return e.after})).forEach((function(e){return e.after(a,n.state)}))}catch(u){0}return e}))}},v.prototype.subscribe=function(e){return y(e,this._subscribers)},v.prototype.subscribeAction=function(e){var t="function"===typeof e?{before:e}:e;return y(t,this._actionSubscribers)},v.prototype.watch=function(e,t,n){var r=this;return this._watcherVM.$watch((function(){return e(r.state,r.getters)}),t,n)},v.prototype.replaceState=function(e){var t=this;this._withCommit((function(){t._vm._data.$$state=e}))},v.prototype.registerModule=function(e,t,n){void 0===n&&(n={}),"string"===typeof e&&(e=[e]),this._modules.register(e,t),_(this,this.state,e,this._modules.get(e),n.preserveState),b(this,this.state)},v.prototype.unregisterModule=function(e){var t=this;"string"===typeof e&&(e=[e]),this._modules.unregister(e),this._withCommit((function(){var n=k(t.state,e.slice(0,-1));h.delete(n,e[e.length-1])})),g(this)},v.prototype.hotUpdate=function(e){this._modules.update(e),g(this,!0)},v.prototype._withCommit=function(e){var t=this._committing;this._committing=!0,e(),this._committing=t},Object.defineProperties(v.prototype,m);var A=I((function(e,t){var n={};return N(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=this.$store.state,n=this.$store.getters;if(e){var r=F(this.$store,"mapState",e);if(!r)return;t=r.context.state,n=r.context.getters}return"function"===typeof o?o.call(this,t,n):t[o]},n[r].vuex=!0})),n})),T=I((function(e,t){var n={};return N(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=[],n=arguments.length;while(n--)t[n]=arguments[n];var r=this.$store.commit;if(e){var i=F(this.$store,"mapMutations",e);if(!i)return;r=i.context.commit}return"function"===typeof o?o.apply(this,[r].concat(t)):r.apply(this.$store,[o].concat(t))}})),n})),M=I((function(e,t){var n={};return N(t).forEach((function(t){var r=t.key,o=t.val;o=e+o,n[r]=function(){if(!e||F(this.$store,"mapGetters",e))return this.$store.getters[o]},n[r].vuex=!0})),n})),P=I((function(e,t){var n={};return N(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=[],n=arguments.length;while(n--)t[n]=arguments[n];var r=this.$store.dispatch;if(e){var i=F(this.$store,"mapActions",e);if(!i)return;r=i.context.dispatch}return"function"===typeof o?o.apply(this,[r].concat(t)):r.apply(this.$store,[o].concat(t))}})),n})),L=function(e){return{mapState:A.bind(null,e),mapGetters:M.bind(null,e),mapMutations:T.bind(null,e),mapActions:P.bind(null,e)}};function N(e){return R(e)?Array.isArray(e)?e.map((function(e){return{key:e,val:e}})):Object.keys(e).map((function(t){return{key:t,val:e[t]}})):[]}function R(e){return Array.isArray(e)||s(e)}function I(e){return function(t,n){return"string"!==typeof t?(n=t,t=""):"/"!==t.charAt(t.length-1)&&(t+="/"),e(t,n)}}function F(e,t,n){var r=e._modulesNamespaceMap[n];return r}var H={Store:v,install:j,version:"3.1.3",mapState:A,mapMutations:T,mapGetters:M,mapActions:P,createNamespacedHelpers:L};t["a"]=H}).call(this,n("77e5"))},"88b4":function(e,t,n){var r=n("7a23"),o=n("f30e"),i=n("032e");e.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},"88e7":function(e,t,n){"use strict";t.__esModule=!0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.default=function(e){function t(e){for(var t=arguments.length,n=Array(t>1?t-1:0),a=1;a1&&void 0!==arguments[1]?arguments[1]:1,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;u||(u=document.createElement("textarea"),document.body.appendChild(u));var r=d(e),o=r.paddingSize,i=r.borderSize,a=r.boxSizing,s=r.contextStyle;u.setAttribute("style",s+";"+c),u.value=e.value||e.placeholder||"";var l=u.scrollHeight,f={};"border-box"===a?l+=i:"content-box"===a&&(l-=o),u.value="";var p=u.scrollHeight-o;if(null!==t){var h=p*t;"border-box"===a&&(h=h+o+i),l=Math.max(h,l),f.minHeight=h+"px"}if(null!==n){var v=p*n;"border-box"===a&&(v=v+o+i),l=Math.min(v,l)}return f.height=l+"px",u.parentNode&&u.parentNode.removeChild(u),u=null,f}var h=n(9),v=n.n(h),m=n(21),y={name:"ElInput",componentName:"ElInput",mixins:[a.a,l.a],inheritAttrs:!1,inject:{elForm:{default:""},elFormItem:{default:""}},data:function(){return{textareaCalcStyle:{},hovering:!1,focused:!1,isComposing:!1,passwordVisible:!1}},props:{value:[String,Number],size:String,resize:String,form:String,disabled:Boolean,readonly:Boolean,type:{type:String,default:"text"},autosize:{type:[Boolean,Object],default:!1},autocomplete:{type:String,default:"off"},autoComplete:{type:String,validator:function(e){return!0}},validateEvent:{type:Boolean,default:!0},suffixIcon:String,prefixIcon:String,label:String,clearable:{type:Boolean,default:!1},showPassword:{type:Boolean,default:!1},showWordLimit:{type:Boolean,default:!1},tabindex:String},computed:{_elFormItemSize:function(){return(this.elFormItem||{}).elFormItemSize},validateState:function(){return this.elFormItem?this.elFormItem.validateState:""},needStatusIcon:function(){return!!this.elForm&&this.elForm.statusIcon},validateIcon:function(){return{validating:"el-icon-loading",success:"el-icon-circle-check",error:"el-icon-circle-close"}[this.validateState]},textareaStyle:function(){return v()({},this.textareaCalcStyle,{resize:this.resize})},inputSize:function(){return this.size||this._elFormItemSize||(this.$ELEMENT||{}).size},inputDisabled:function(){return this.disabled||(this.elForm||{}).disabled},nativeInputValue:function(){return null===this.value||void 0===this.value?"":String(this.value)},showClear:function(){return this.clearable&&!this.inputDisabled&&!this.readonly&&this.nativeInputValue&&(this.focused||this.hovering)},showPwdVisible:function(){return this.showPassword&&!this.inputDisabled&&!this.readonly&&(!!this.nativeInputValue||this.focused)},isWordLimitVisible:function(){return this.showWordLimit&&this.$attrs.maxlength&&("text"===this.type||"textarea"===this.type)&&!this.inputDisabled&&!this.readonly&&!this.showPassword},upperLimit:function(){return this.$attrs.maxlength},textLength:function(){return"number"===typeof this.value?String(this.value).length:(this.value||"").length},inputExceed:function(){return this.isWordLimitVisible&&this.textLength>this.upperLimit}},watch:{value:function(e){this.$nextTick(this.resizeTextarea),this.validateEvent&&this.dispatch("ElFormItem","el.form.change",[e])},nativeInputValue:function(){this.setNativeInputValue()},type:function(){var e=this;this.$nextTick((function(){e.setNativeInputValue(),e.resizeTextarea(),e.updateIconOffset()}))}},methods:{focus:function(){this.getInput().focus()},blur:function(){this.getInput().blur()},getMigratingConfig:function(){return{props:{icon:"icon is removed, use suffix-icon / prefix-icon instead.","on-icon-click":"on-icon-click is removed."},events:{click:"click is removed."}}},handleBlur:function(e){this.focused=!1,this.$emit("blur",e),this.validateEvent&&this.dispatch("ElFormItem","el.form.blur",[this.value])},select:function(){this.getInput().select()},resizeTextarea:function(){if(!this.$isServer){var e=this.autosize,t=this.type;if("textarea"===t)if(e){var n=e.minRows,r=e.maxRows;this.textareaCalcStyle=p(this.$refs.textarea,n,r)}else this.textareaCalcStyle={minHeight:p(this.$refs.textarea).minHeight}}},setNativeInputValue:function(){var e=this.getInput();e&&e.value!==this.nativeInputValue&&(e.value=this.nativeInputValue)},handleFocus:function(e){this.focused=!0,this.$emit("focus",e)},handleCompositionStart:function(){this.isComposing=!0},handleCompositionUpdate:function(e){var t=e.target.value,n=t[t.length-1]||"";this.isComposing=!Object(m["isKorean"])(n)},handleCompositionEnd:function(e){this.isComposing&&(this.isComposing=!1,this.handleInput(e))},handleInput:function(e){this.isComposing||e.target.value!==this.nativeInputValue&&(this.$emit("input",e.target.value),this.$nextTick(this.setNativeInputValue))},handleChange:function(e){this.$emit("change",e.target.value)},calcIconOffset:function(e){var t=[].slice.call(this.$el.querySelectorAll(".el-input__"+e)||[]);if(t.length){for(var n=null,r=0;r-1?"center "+n:n+" center"}},appendArrow:function(e){var t=void 0;if(!this.appended){for(var n in this.appended=!0,e.attributes)if(/^_v-/.test(e.attributes[n].name)){t=e.attributes[n].name;break}var r=document.createElement("div");t&&r.setAttribute(t,""),r.setAttribute("x-arrow",""),r.className="popper__arrow",e.appendChild(r)}}},beforeDestroy:function(){this.doDestroy(!0),this.popperElm&&this.popperElm.parentNode===document.body&&(this.popperElm.removeEventListener("click",l),document.body.removeChild(this.popperElm))},deactivated:function(){this.$options.beforeDestroy[0].call(this)}}},"9db6":function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++n+r).toString(36)}},"9fe6":function(e,t,n){"use strict";t.__esModule=!0,t.removeResizeListener=t.addResizeListener=void 0;var r=n("0655"),o=i(r);function i(e){return e&&e.__esModule?e:{default:e}}var a="undefined"===typeof window,s=function(e){var t=e,n=Array.isArray(t),r=0;for(t=n?t:t[Symbol.iterator]();;){var o;if(n){if(r>=t.length)break;o=t[r++]}else{if(r=t.next(),r.done)break;o=r.value}var i=o,a=i.target.__resizeListeners__||[];a.length&&a.forEach((function(e){e()}))}};t.addResizeListener=function(e,t){a||(e.__resizeListeners__||(e.__resizeListeners__=[],e.__ro__=new o.default(s),e.__ro__.observe(e)),e.__resizeListeners__.push(t))},t.removeResizeListener=function(e,t){e&&e.__resizeListeners__&&(e.__resizeListeners__.splice(e.__resizeListeners__.indexOf(t),1),e.__resizeListeners__.length||e.__ro__.disconnect())}},a406:function(e,t,n){var r=n("df50");e.exports=r("navigator","userAgent")||""},a464:function(e,t,n){},a7d9:function(e,t,n){var r=n("d5dc");e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},a867:function(e,t,n){"use strict";var r=n("df50"),o=n("c223"),i=n("57c4"),a=n("7a23"),s=i("species");e.exports=function(e){var t=r(e),n=o.f;a&&t&&!t[s]&&n(t,s,{configurable:!0,get:function(){return this}})}},a9f2:function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},ab77:function(e,t,n){},ac83:function(e,t,n){var r=n("d68d");e.exports=function(e){if(!r(e))throw TypeError(String(e)+" is not an object");return e}},aec8:function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},b3d4:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=110)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},110:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"el-breadcrumb",attrs:{"aria-label":"Breadcrumb",role:"navigation"}},[e._t("default")],2)},o=[];r._withStripped=!0;var i={name:"ElBreadcrumb",props:{separator:{type:String,default:"/"},separatorClass:{type:String,default:""}},provide:function(){return{elBreadcrumb:this}},mounted:function(){var e=this.$el.querySelectorAll(".el-breadcrumb__item");e.length&&e[e.length-1].setAttribute("aria-current","page")}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/breadcrumb/src/breadcrumb.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},b3d8:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=56)}([function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},,function(e,t){e.exports=n("3c84")},function(e,t){e.exports=n("d41f")},,function(e,t){e.exports=n("9bb9")},function(e,t){e.exports=n("1f4f")},function(e,t){e.exports=n("0261")},function(e,t,n){"use strict";n.d(t,"b",(function(){return i})),n.d(t,"i",(function(){return s})),n.d(t,"d",(function(){return l})),n.d(t,"e",(function(){return u})),n.d(t,"c",(function(){return c})),n.d(t,"g",(function(){return f})),n.d(t,"f",(function(){return d})),n.d(t,"h",(function(){return h})),n.d(t,"l",(function(){return v})),n.d(t,"k",(function(){return m})),n.d(t,"j",(function(){return y})),n.d(t,"a",(function(){return g})),n.d(t,"m",(function(){return b})),n.d(t,"n",(function(){return _}));var r=n(3),o="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(e){var t=e.target;while(t&&"HTML"!==t.tagName.toUpperCase()){if("TD"===t.tagName.toUpperCase())return t;t=t.parentNode}return null},a=function(e){return null!==e&&"object"===("undefined"===typeof e?"undefined":o(e))},s=function(e,t,n,o,i){if(!t&&!o&&(!i||Array.isArray(i)&&!i.length))return e;n="string"===typeof n?"descending"===n?-1:1:n&&n<0?-1:1;var s=o?null:function(n,o){return i?(Array.isArray(i)||(i=[i]),i.map((function(t){return"string"===typeof t?Object(r["getValueByPath"])(n,t):t(n,o,e)}))):("$key"!==t&&a(n)&&"$value"in n&&(n=n.$value),[a(n)?Object(r["getValueByPath"])(n,t):n])},l=function(e,t){if(o)return o(e.value,t.value);for(var n=0,r=e.key.length;nt.key[n])return 1}return 0};return e.map((function(e,t){return{value:e,index:t,key:s?s(e,t):null}})).sort((function(e,t){var r=l(e,t);return r||(r=e.index-t.index),r*n})).map((function(e){return e.value}))},l=function(e,t){var n=null;return e.columns.forEach((function(e){e.id===t&&(n=e)})),n},u=function(e,t){for(var n=null,r=0;r2&&void 0!==arguments[2]?arguments[2]:"children",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"hasChildren",o=function(e){return!(Array.isArray(e)&&e.length)};function i(e,a,s){t(e,a,s),a.forEach((function(e){if(e[r])t(e,null,s+1);else{var a=e[n];o(a)||i(e,a,s+1)}}))}e.forEach((function(e){if(e[r])t(e,null,0);else{var a=e[n];o(a)||i(e,a,0)}}))}},function(e,t){e.exports=n("e93e")},,function(e,t){e.exports=n("1932")},function(e,t){e.exports=n("b451")},,function(e,t){e.exports=n("8afa")},function(e,t){e.exports=n("4eda")},function(e,t){e.exports=n("9fe6")},function(e,t){e.exports=n("ca47")},function(e,t){e.exports=n("8508")},,,,,,,,,,,function(e,t){e.exports=n("df62")},,,,,,,,,,function(e,t){e.exports=n("e536")},function(e,t){e.exports=n("4fd9")},,,function(e,t){e.exports=n("63ec")},,,function(e,t){e.exports=n("546a")},,,,,,,,,,function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"el-table",class:[{"el-table--fit":e.fit,"el-table--striped":e.stripe,"el-table--border":e.border||e.isGroup,"el-table--hidden":e.isHidden,"el-table--group":e.isGroup,"el-table--fluid-height":e.maxHeight,"el-table--scrollable-x":e.layout.scrollX,"el-table--scrollable-y":e.layout.scrollY,"el-table--enable-row-hover":!e.store.states.isComplex,"el-table--enable-row-transition":0!==(e.store.states.data||[]).length&&(e.store.states.data||[]).length<100},e.tableSize?"el-table--"+e.tableSize:""],on:{mouseleave:function(t){e.handleMouseLeave(t)}}},[n("div",{ref:"hiddenColumns",staticClass:"hidden-columns"},[e._t("default")],2),e.showHeader?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleHeaderFooterMousewheel,expression:"handleHeaderFooterMousewheel"}],ref:"headerWrapper",staticClass:"el-table__header-wrapper"},[n("table-header",{ref:"tableHeader",style:{width:e.layout.bodyWidth?e.layout.bodyWidth+"px":""},attrs:{store:e.store,border:e.border,"default-sort":e.defaultSort}})],1):e._e(),n("div",{ref:"bodyWrapper",staticClass:"el-table__body-wrapper",class:[e.layout.scrollX?"is-scrolling-"+e.scrollPosition:"is-scrolling-none"],style:[e.bodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{context:e.context,store:e.store,stripe:e.stripe,"row-class-name":e.rowClassName,"row-style":e.rowStyle,highlight:e.highlightCurrentRow}}),e.data&&0!==e.data.length?e._e():n("div",{ref:"emptyBlock",staticClass:"el-table__empty-block",style:e.emptyBlockStyle},[n("span",{staticClass:"el-table__empty-text"},[e._t("empty",[e._v(e._s(e.emptyText||e.t("el.table.emptyText")))])],2)]),e.$slots.append?n("div",{ref:"appendWrapper",staticClass:"el-table__append-wrapper"},[e._t("append")],2):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"},{name:"mousewheel",rawName:"v-mousewheel",value:e.handleHeaderFooterMousewheel,expression:"handleHeaderFooterMousewheel"}],ref:"footerWrapper",staticClass:"el-table__footer-wrapper"},[n("table-footer",{style:{width:e.layout.bodyWidth?e.layout.bodyWidth+"px":""},attrs:{store:e.store,border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,"default-sort":e.defaultSort}})],1):e._e(),e.fixedColumns.length>0?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleFixedMousewheel,expression:"handleFixedMousewheel"}],ref:"fixedWrapper",staticClass:"el-table__fixed",style:[{width:e.layout.fixedWidth?e.layout.fixedWidth+"px":""},e.fixedHeight]},[e.showHeader?n("div",{ref:"fixedHeaderWrapper",staticClass:"el-table__fixed-header-wrapper"},[n("table-header",{ref:"fixedTableHeader",style:{width:e.bodyWidth},attrs:{fixed:"left",border:e.border,store:e.store}})],1):e._e(),n("div",{ref:"fixedBodyWrapper",staticClass:"el-table__fixed-body-wrapper",style:[{top:e.layout.headerHeight+"px"},e.fixedBodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{fixed:"left",store:e.store,stripe:e.stripe,highlight:e.highlightCurrentRow,"row-class-name":e.rowClassName,"row-style":e.rowStyle}}),e.$slots.append?n("div",{staticClass:"el-table__append-gutter",style:{height:e.layout.appendHeight+"px"}}):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"}],ref:"fixedFooterWrapper",staticClass:"el-table__fixed-footer-wrapper"},[n("table-footer",{style:{width:e.bodyWidth},attrs:{fixed:"left",border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,store:e.store}})],1):e._e()]):e._e(),e.rightFixedColumns.length>0?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleFixedMousewheel,expression:"handleFixedMousewheel"}],ref:"rightFixedWrapper",staticClass:"el-table__fixed-right",style:[{width:e.layout.rightFixedWidth?e.layout.rightFixedWidth+"px":"",right:e.layout.scrollY?(e.border?e.layout.gutterWidth:e.layout.gutterWidth||0)+"px":""},e.fixedHeight]},[e.showHeader?n("div",{ref:"rightFixedHeaderWrapper",staticClass:"el-table__fixed-header-wrapper"},[n("table-header",{ref:"rightFixedTableHeader",style:{width:e.bodyWidth},attrs:{fixed:"right",border:e.border,store:e.store}})],1):e._e(),n("div",{ref:"rightFixedBodyWrapper",staticClass:"el-table__fixed-body-wrapper",style:[{top:e.layout.headerHeight+"px"},e.fixedBodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{fixed:"right",store:e.store,stripe:e.stripe,"row-class-name":e.rowClassName,"row-style":e.rowStyle,highlight:e.highlightCurrentRow}}),e.$slots.append?n("div",{staticClass:"el-table__append-gutter",style:{height:e.layout.appendHeight+"px"}}):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"}],ref:"rightFixedFooterWrapper",staticClass:"el-table__fixed-footer-wrapper"},[n("table-footer",{style:{width:e.bodyWidth},attrs:{fixed:"right",border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,store:e.store}})],1):e._e()]):e._e(),e.rightFixedColumns.length>0?n("div",{ref:"rightFixedPatch",staticClass:"el-table__fixed-right-patch",style:{width:e.layout.scrollY?e.layout.gutterWidth+"px":"0",height:e.layout.headerHeight+"px"}}):e._e(),n("div",{directives:[{name:"show",rawName:"v-show",value:e.resizeProxyVisible,expression:"resizeProxyVisible"}],ref:"resizeProxy",staticClass:"el-table__column-resize-proxy"})])},o=[];r._withStripped=!0;var i=n(18),a=n.n(i),s=n(43),l=n(16),u=n(46),c=n.n(u),f="undefined"!==typeof navigator&&navigator.userAgent.toLowerCase().indexOf("firefox")>-1,d=function(e,t){e&&e.addEventListener&&e.addEventListener(f?"DOMMouseScroll":"mousewheel",(function(e){var n=c()(e);t&&t.apply(this,[e,n])}))},p={bind:function(e,t){d(e,t.value)}},h=n(6),v=n.n(h),m=n(11),y=n.n(m),g=n(7),b=n.n(g),_=n(9),x=n.n(_),w=n(8),C={data:function(){return{states:{defaultExpandAll:!1,expandRows:[]}}},methods:{updateExpandRows:function(){var e=this.states,t=e.data,n=void 0===t?[]:t,r=e.rowKey,o=e.defaultExpandAll,i=e.expandRows;if(o)this.states.expandRows=n.slice();else if(r){var a=Object(w["f"])(i,r);this.states.expandRows=n.reduce((function(e,t){var n=Object(w["g"])(t,r),o=a[n];return o&&e.push(t),e}),[])}else this.states.expandRows=[]},toggleRowExpansion:function(e,t){var n=Object(w["m"])(this.states.expandRows,e,t);n&&(this.table.$emit("expand-change",e,this.states.expandRows.slice()),this.scheduleLayout())},setExpandRowKeys:function(e){this.assertRowKey();var t=this.states,n=t.data,r=t.rowKey,o=Object(w["f"])(n,r);this.states.expandRows=e.reduce((function(e,t){var n=o[t];return n&&e.push(n.row),e}),[])},isRowExpanded:function(e){var t=this.states,n=t.expandRows,r=void 0===n?[]:n,o=t.rowKey;if(o){var i=Object(w["f"])(r,o);return!!i[Object(w["g"])(e,o)]}return-1!==r.indexOf(e)}}},S=n(3),O={data:function(){return{states:{_currentRowKey:null,currentRow:null}}},methods:{setCurrentRowKey:function(e){this.assertRowKey(),this.states._currentRowKey=e,this.setCurrentRowByKey(e)},restoreCurrentRowKey:function(){this.states._currentRowKey=null},setCurrentRowByKey:function(e){var t=this.states,n=t.data,r=void 0===n?[]:n,o=t.rowKey,i=null;o&&(i=Object(S["arrayFind"])(r,(function(t){return Object(w["g"])(t,o)===e}))),t.currentRow=i},updateCurrentRow:function(e){var t=this.states,n=this.table,r=t.currentRow;if(e&&e!==r)return t.currentRow=e,void n.$emit("current-change",e,r);!e&&r&&(t.currentRow=null,n.$emit("current-change",null,r))},updateCurrentRowData:function(){var e=this.states,t=this.table,n=e.rowKey,r=e._currentRowKey,o=e.data||[],i=e.currentRow;if(-1===o.indexOf(i)&&i){if(n){var a=Object(w["g"])(i,n);this.setCurrentRowByKey(a)}else e.currentRow=null;null===e.currentRow&&t.$emit("current-change",null,i)}else r&&(this.setCurrentRowByKey(r),this.restoreCurrentRowKey())}}},E=Object.assign||function(e){for(var t=1;t0&&t[0]&&"selection"===t[0].type&&!t[0].fixed&&(t[0].fixed=!0,e.fixedColumns.unshift(t[0]));var n=t.filter((function(e){return!e.fixed}));e.originColumns=[].concat(e.fixedColumns).concat(n).concat(e.rightFixedColumns);var r=j(n),o=j(e.fixedColumns),i=j(e.rightFixedColumns);e.leafColumnsLength=r.length,e.fixedLeafColumnsLength=o.length,e.rightFixedLeafColumnsLength=i.length,e.columns=[].concat(o).concat(r).concat(i),e.isComplex=e.fixedColumns.length>0||e.rightFixedColumns.length>0},scheduleLayout:function(e){e&&this.updateColumns(),this.table.debouncedUpdateLayout()},isSelected:function(e){var t=this.states.selection,n=void 0===t?[]:t;return n.indexOf(e)>-1},clearSelection:function(){var e=this.states;e.isAllSelected=!1;var t=e.selection;t.length&&(e.selection=[],this.table.$emit("selection-change",[]))},cleanSelection:function(){var e=this.states,t=e.data,n=e.rowKey,r=e.selection,o=void 0;if(n){o=[];var i=Object(w["f"])(r,n),a=Object(w["f"])(t,n);for(var s in i)i.hasOwnProperty(s)&&!a[s]&&o.push(i[s].row)}else o=r.filter((function(e){return-1===t.indexOf(e)}));if(o.length){var l=r.filter((function(e){return-1===o.indexOf(e)}));e.selection=l,this.table.$emit("selection-change",l.slice())}},toggleRowSelection:function(e,t){var n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=Object(w["m"])(this.states.selection,e,t);if(r){var o=(this.states.selection||[]).slice();n&&this.table.$emit("select",o,e),this.table.$emit("selection-change",o)}},_toggleAllSelection:function(){var e=this.states,t=e.data,n=void 0===t?[]:t,r=e.selection,o=e.selectOnIndeterminate?!e.isAllSelected:!(e.isAllSelected||r.length);e.isAllSelected=o;var i=!1;n.forEach((function(t,n){e.selectable?e.selectable.call(null,t,n)&&Object(w["m"])(r,t,o)&&(i=!0):Object(w["m"])(r,t,o)&&(i=!0)})),i&&this.table.$emit("selection-change",r?r.slice():[]),this.table.$emit("select-all",r)},updateSelectionByRowKey:function(){var e=this.states,t=e.selection,n=e.rowKey,r=e.data,o=Object(w["f"])(t,n);r.forEach((function(e){var r=Object(w["g"])(e,n),i=o[r];i&&(t[i.index]=e)}))},updateAllSelected:function(){var e=this.states,t=e.selection,n=e.rowKey,r=e.selectable,o=e.data||[];if(0!==o.length){var i=void 0;n&&(i=Object(w["f"])(t,n));for(var a=function(e){return i?!!i[Object(w["g"])(e,n)]:-1!==t.indexOf(e)},s=!0,l=0,u=0,c=o.length;u1?n-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:{};if(!e)throw new Error("Table is required.");var n=new T;return n.table=e,n.toggleAllSelection=P()(10,n._toggleAllSelection),Object.keys(t).forEach((function(e){n.states[e]=t[e]})),n}function N(e){var t={};return Object.keys(e).forEach((function(n){var r=e[n],o=void 0;"string"===typeof r?o=function(){return this.store.states[r]}:"function"===typeof r?o=function(){return r.call(this,this.store.states)}:console.error("invalid value type"),o&&(t[n]=o)})),t}var R=n(39),I=n.n(R);function F(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var H=function(){function e(t){for(var n in F(this,e),this.observers=[],this.table=null,this.store=null,this.columns=null,this.fit=!0,this.showHeader=!0,this.height=null,this.scrollX=!1,this.scrollY=!1,this.bodyWidth=null,this.fixedWidth=null,this.rightFixedWidth=null,this.tableHeight=null,this.headerHeight=44,this.appendHeight=0,this.footerHeight=44,this.viewportHeight=null,this.bodyHeight=null,this.fixedBodyHeight=null,this.gutterWidth=I()(),t)t.hasOwnProperty(n)&&(this[n]=t[n]);if(!this.table)throw new Error("table is required for Table Layout");if(!this.store)throw new Error("store is required for Table Layout")}return e.prototype.updateScrollY=function(){var e=this.height;if(null===e)return!1;var t=this.table.bodyWrapper;if(this.table.$el&&t){var n=t.querySelector(".el-table__body"),r=this.scrollY,o=n.offsetHeight>this.bodyHeight;return this.scrollY=o,r!==o}return!1},e.prototype.setHeight=function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"height";if(!b.a.prototype.$isServer){var r=this.table.$el;if(e=Object(w["j"])(e),this.height=e,!r&&(e||0===e))return b.a.nextTick((function(){return t.setHeight(e,n)}));"number"===typeof e?(r.style[n]=e+"px",this.updateElsHeight()):"string"===typeof e&&(r.style[n]=e,this.updateElsHeight())}},e.prototype.setMaxHeight=function(e){this.setHeight(e,"max-height")},e.prototype.getFlattenColumns=function(){var e=[],t=this.table.columns;return t.forEach((function(t){t.isColumnGroup?e.push.apply(e,t.columns):e.push(t)})),e},e.prototype.updateElsHeight=function(){var e=this;if(!this.table.$ready)return b.a.nextTick((function(){return e.updateElsHeight()}));var t=this.table.$refs,n=t.headerWrapper,r=t.appendWrapper,o=t.footerWrapper;if(this.appendHeight=r?r.offsetHeight:0,!this.showHeader||n){var i=n?n.querySelector(".el-table__header tr"):null,a=this.headerDisplayNone(i),s=this.headerHeight=this.showHeader?n.offsetHeight:0;if(this.showHeader&&!a&&n.offsetWidth>0&&(this.table.columns||[]).length>0&&s<2)return b.a.nextTick((function(){return e.updateElsHeight()}));var l=this.tableHeight=this.table.$el.clientHeight,u=this.footerHeight=o?o.offsetHeight:0;null!==this.height&&(this.bodyHeight=l-s-u+(o?1:0)),this.fixedBodyHeight=this.scrollX?this.bodyHeight-this.gutterWidth:this.bodyHeight;var c=!(this.store.states.data&&this.store.states.data.length);this.viewportHeight=this.scrollX?l-(c?0:this.gutterWidth):l,this.updateScrollY(),this.notifyObservers("scrollable")}},e.prototype.headerDisplayNone=function(e){if(!e)return!0;var t=e;while("DIV"!==t.tagName){if("none"===getComputedStyle(t).display)return!0;t=t.parentElement}return!1},e.prototype.updateColumnsWidth=function(){if(!b.a.prototype.$isServer){var e=this.fit,t=this.table.$el.clientWidth,n=0,r=this.getFlattenColumns(),o=r.filter((function(e){return"number"!==typeof e.width}));if(r.forEach((function(e){"number"===typeof e.width&&e.realWidth&&(e.realWidth=null)})),o.length>0&&e){r.forEach((function(e){n+=e.width||e.minWidth||80}));var i=this.scrollY?this.gutterWidth:0;if(n<=t-i){this.scrollX=!1;var a=t-i-n;if(1===o.length)o[0].realWidth=(o[0].minWidth||80)+a;else{var s=o.reduce((function(e,t){return e+(t.minWidth||80)}),0),l=a/s,u=0;o.forEach((function(e,t){if(0!==t){var n=Math.floor((e.minWidth||80)*l);u+=n,e.realWidth=(e.minWidth||80)+n}})),o[0].realWidth=(o[0].minWidth||80)+a-u}}else this.scrollX=!0,o.forEach((function(e){e.realWidth=e.minWidth}));this.bodyWidth=Math.max(n,t),this.table.resizeState.width=this.bodyWidth}else r.forEach((function(e){e.width||e.minWidth?e.realWidth=e.width||e.minWidth:e.realWidth=80,n+=e.realWidth})),this.scrollX=n>t,this.bodyWidth=n;var c=this.store.states.fixedColumns;if(c.length>0){var f=0;c.forEach((function(e){f+=e.realWidth||e.width})),this.fixedWidth=f}var d=this.store.states.rightFixedColumns;if(d.length>0){var p=0;d.forEach((function(e){p+=e.realWidth||e.width})),this.rightFixedWidth=p}this.notifyObservers("columns")}},e.prototype.addObserver=function(e){this.observers.push(e)},e.prototype.removeObserver=function(e){var t=this.observers.indexOf(e);-1!==t&&this.observers.splice(t,1)},e.prototype.notifyObservers=function(e){var t=this,n=this.observers;n.forEach((function(n){switch(e){case"columns":n.onColumnsChange(t);break;case"scrollable":n.onScrollableChange(t);break;default:throw new Error("Table Layout don't have event "+e+".")}}))},e}(),z=H,D=n(2),B=n(29),W=n.n(B),V={created:function(){this.tableLayout.addObserver(this)},destroyed:function(){this.tableLayout.removeObserver(this)},computed:{tableLayout:function(){var e=this.layout;if(!e&&this.table&&(e=this.table.layout),!e)throw new Error("Can not find table layout.");return e}},mounted:function(){this.onColumnsChange(this.tableLayout),this.onScrollableChange(this.tableLayout)},updated:function(){this.__updated__||(this.onColumnsChange(this.tableLayout),this.onScrollableChange(this.tableLayout),this.__updated__=!0)},methods:{onColumnsChange:function(e){var t=this.$el.querySelectorAll("colgroup > col");if(t.length){var n=e.getFlattenColumns(),r={};n.forEach((function(e){r[e.id]=e}));for(var o=0,i=t.length;o col[name=gutter]"),n=0,r=t.length;n=this.leftFixedLeafCount:"right"===this.fixed?e=this.columnsCount-this.rightFixedLeafCount},getSpan:function(e,t,n,r){var o=1,i=1,a=this.table.spanMethod;if("function"===typeof a){var s=a({row:e,column:t,rowIndex:n,columnIndex:r});Array.isArray(s)?(o=s[0],i=s[1]):"object"===("undefined"===typeof s?"undefined":U(s))&&(o=s.rowspan,i=s.colspan)}return{rowspan:o,colspan:i}},getRowStyle:function(e,t){var n=this.table.rowStyle;return"function"===typeof n?n.call(null,{row:e,rowIndex:t}):n||null},getRowClass:function(e,t){var n=["el-table__row"];this.table.highlightCurrentRow&&e===this.store.states.currentRow&&n.push("current-row"),this.stripe&&t%2===1&&n.push("el-table__row--striped");var r=this.table.rowClassName;return"string"===typeof r?n.push(r):"function"===typeof r&&n.push(r.call(null,{row:e,rowIndex:t})),this.store.states.expandRows.indexOf(e)>-1&&n.push("expanded"),n},getCellStyle:function(e,t,n,r){var o=this.table.cellStyle;return"function"===typeof o?o.call(null,{rowIndex:e,columnIndex:t,row:n,column:r}):o},getCellClass:function(e,t,n,r){var o=[r.id,r.align,r.className];this.isColumnHidden(t)&&o.push("is-hidden");var i=this.table.cellClassName;return"string"===typeof i?o.push(i):"function"===typeof i&&o.push(i.call(null,{rowIndex:e,columnIndex:t,row:n,column:r})),o.join(" ")},getColspanRealWidth:function(e,t,n){if(t<1)return e[n].realWidth;var r=e.map((function(e){var t=e.realWidth;return t})).slice(n,n+t);return r.reduce((function(e,t){return e+t}),-1)},handleCellMouseEnter:function(e,t){var n=this.table,r=Object(w["b"])(e);if(r){var o=Object(w["c"])(n,r),i=n.hoverState={cell:r,column:o,row:t};n.$emit("cell-mouse-enter",i.row,i.column,i.cell,e)}var a=e.target.querySelector(".cell");if(Object(D["hasClass"])(a,"el-tooltip")&&a.childNodes.length){var s=document.createRange();s.setStart(a,0),s.setEnd(a,a.childNodes.length);var l=s.getBoundingClientRect().width,u=(parseInt(Object(D["getStyle"])(a,"paddingLeft"),10)||0)+(parseInt(Object(D["getStyle"])(a,"paddingRight"),10)||0);if((l+u>a.offsetWidth||a.scrollWidth>a.offsetWidth)&&this.$refs.tooltip){var c=this.$refs.tooltip;this.tooltipContent=r.innerText||r.textContent,c.referenceElm=r,c.$refs.popper&&(c.$refs.popper.style.display="none"),c.doDestroy(),c.setExpectedState(!0),this.activateTooltip(c)}}},handleCellMouseLeave:function(e){var t=this.$refs.tooltip;t&&(t.setExpectedState(!1),t.handleClosePopper());var n=Object(w["b"])(e);if(n){var r=this.table.hoverState||{};this.table.$emit("cell-mouse-leave",r.row,r.column,r.cell,e)}},handleMouseEnter:P()(30,(function(e){this.store.commit("setHoverRow",e)})),handleMouseLeave:P()(30,(function(){this.store.commit("setHoverRow",null)})),handleContextMenu:function(e,t){this.handleEvent(e,t,"contextmenu")},handleDoubleClick:function(e,t){this.handleEvent(e,t,"dblclick")},handleClick:function(e,t){this.store.commit("setCurrentRow",t),this.handleEvent(e,t,"click")},handleEvent:function(e,t,n){var r=this.table,o=Object(w["b"])(e),i=void 0;o&&(i=Object(w["c"])(r,o),i&&r.$emit("cell-"+n,t,i,o,e)),r.$emit("row-"+n,t,i,e)},rowRender:function(e,t,n){var r=this,o=this.$createElement,i=this.treeIndent,a=this.columns,s=this.firstDefaultColumnIndex,l=a.map((function(e,t){return r.isColumnHidden(t)})),u=this.getRowClass(e,t),c=!0;n&&(u.push("el-table__row--level-"+n.level),c=n.display);var f=c?null:{display:"none"};return o("tr",{style:[f,this.getRowStyle(e,t)],class:u,key:this.getKeyOfRow(e,t),on:{dblclick:function(t){return r.handleDoubleClick(t,e)},click:function(t){return r.handleClick(t,e)},contextmenu:function(t){return r.handleContextMenu(t,e)},mouseenter:function(e){return r.handleMouseEnter(t)},mouseleave:this.handleMouseLeave}},[a.map((function(u,c){var f=r.getSpan(e,u,t,c),d=f.rowspan,p=f.colspan;if(!d||!p)return null;var h=q({},u);h.realWidth=r.getColspanRealWidth(a,p,c);var v={store:r.store,_self:r.context||r.table.$vnode.context,column:h,row:e,$index:t};return c===s&&n&&(v.treeNode={indent:n.level*i,level:n.level},"boolean"===typeof n.expanded&&(v.treeNode.expanded=n.expanded,"loading"in n&&(v.treeNode.loading=n.loading),"noLazyChildren"in n&&(v.treeNode.noLazyChildren=n.noLazyChildren))),o("td",{style:r.getCellStyle(t,c,e,u),class:r.getCellClass(t,c,e,u),attrs:{rowspan:d,colspan:p},on:{mouseenter:function(t){return r.handleCellMouseEnter(t,e)},mouseleave:r.handleCellMouseLeave}},[u.renderCell.call(r._renderProxy,r.$createElement,v,l[c])])}))])},wrappedRowRender:function(e,t){var n=this,r=this.$createElement,o=this.store,i=o.isRowExpanded,a=o.assertRowKey,s=o.states,l=s.treeData,u=s.lazyTreeNodeMap,c=s.childrenColumnName,f=s.rowKey;if(this.hasExpandColumn&&i(e)){var d=this.table.renderExpanded,p=this.rowRender(e,t);return d?[[p,r("tr",{key:"expanded-row__"+p.key},[r("td",{attrs:{colspan:this.columnsCount},class:"el-table__expanded-cell"},[d(this.$createElement,{row:e,$index:t,store:this.store})])])]]:(console.error("[Element Error]renderExpanded is required."),p)}if(Object.keys(l).length){a();var h=Object(w["g"])(e,f),v=l[h],m=null;v&&(m={expanded:v.expanded,level:v.level,display:!0},"boolean"===typeof v.lazy&&("boolean"===typeof v.loaded&&v.loaded&&(m.noLazyChildren=!(v.children&&v.children.length)),m.loading=v.loading));var y=[this.rowRender(e,t,m)];if(v){var g=0,b=function e(r,o){r&&r.length&&o&&r.forEach((function(r){var i={display:o.display&&o.expanded,level:o.level+1},a=Object(w["g"])(r,f);if(void 0===a||null===a)throw new Error("for nested data item, row-key is required.");if(v=q({},l[a]),v&&(i.expanded=v.expanded,v.level=v.level||i.level,v.display=!(!v.expanded||!i.display),"boolean"===typeof v.lazy&&("boolean"===typeof v.loaded&&v.loaded&&(i.noLazyChildren=!(v.children&&v.children.length)),i.loading=v.loading)),g++,y.push(n.rowRender(r,t+g,i)),v){var s=u[a]||r[c];e(s,v)}}))};v.display=!0;var _=u[h]||e[c];b(_,v)}return y}return this.rowRender(e,t)}}},G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"el-zoom-in-top"}},[e.multiple?n("div",{directives:[{name:"clickoutside",rawName:"v-clickoutside",value:e.handleOutsideClick,expression:"handleOutsideClick"},{name:"show",rawName:"v-show",value:e.showPopper,expression:"showPopper"}],staticClass:"el-table-filter"},[n("div",{staticClass:"el-table-filter__content"},[n("el-scrollbar",{attrs:{"wrap-class":"el-table-filter__wrap"}},[n("el-checkbox-group",{staticClass:"el-table-filter__checkbox-group",model:{value:e.filteredValue,callback:function(t){e.filteredValue=t},expression:"filteredValue"}},e._l(e.filters,(function(t){return n("el-checkbox",{key:t.value,attrs:{label:t.value}},[e._v(e._s(t.text))])})),1)],1)],1),n("div",{staticClass:"el-table-filter__bottom"},[n("button",{class:{"is-disabled":0===e.filteredValue.length},attrs:{disabled:0===e.filteredValue.length},on:{click:e.handleConfirm}},[e._v(e._s(e.t("el.table.confirmFilter")))]),n("button",{on:{click:e.handleReset}},[e._v(e._s(e.t("el.table.resetFilter")))])])]):n("div",{directives:[{name:"clickoutside",rawName:"v-clickoutside",value:e.handleOutsideClick,expression:"handleOutsideClick"},{name:"show",rawName:"v-show",value:e.showPopper,expression:"showPopper"}],staticClass:"el-table-filter"},[n("ul",{staticClass:"el-table-filter__list"},[n("li",{staticClass:"el-table-filter__list-item",class:{"is-active":void 0===e.filterValue||null===e.filterValue},on:{click:function(t){e.handleSelect(null)}}},[e._v(e._s(e.t("el.table.clearFilter")))]),e._l(e.filters,(function(t){return n("li",{key:t.value,staticClass:"el-table-filter__list-item",class:{"is-active":e.isActive(t)},attrs:{label:t.value},on:{click:function(n){e.handleSelect(t.value)}}},[e._v(e._s(t.text))])}))],2)])])},X=[];G._withStripped=!0;var Y=n(5),J=n.n(Y),Z=n(15),Q=n(12),ee=n.n(Q),te=[];!b.a.prototype.$isServer&&document.addEventListener("click",(function(e){te.forEach((function(t){var n=e.target;t&&t.$el&&(n===t.$el||t.$el.contains(n)||t.handleOutsideClick&&t.handleOutsideClick(e))}))}));var ne={open:function(e){e&&te.push(e)},close:function(e){var t=te.indexOf(e);-1!==t&&te.splice(e,1)}},re=n(40),oe=n.n(re),ie=n(14),ae=n.n(ie),se={name:"ElTableFilterPanel",mixins:[J.a,v.a],directives:{Clickoutside:ee.a},components:{ElCheckbox:a.a,ElCheckboxGroup:oe.a,ElScrollbar:ae.a},props:{placement:{type:String,default:"bottom-end"}},methods:{isActive:function(e){return e.value===this.filterValue},handleOutsideClick:function(){var e=this;setTimeout((function(){e.showPopper=!1}),16)},handleConfirm:function(){this.confirmFilter(this.filteredValue),this.handleOutsideClick()},handleReset:function(){this.filteredValue=[],this.confirmFilter(this.filteredValue),this.handleOutsideClick()},handleSelect:function(e){this.filterValue=e,"undefined"!==typeof e&&null!==e?this.confirmFilter(this.filteredValue):this.confirmFilter([]),this.handleOutsideClick()},confirmFilter:function(e){this.table.store.commit("filterChange",{column:this.column,values:e}),this.table.store.updateAllSelected()}},data:function(){return{table:null,cell:null,column:null}},computed:{filters:function(){return this.column&&this.column.filters},filterValue:{get:function(){return(this.column.filteredValue||[])[0]},set:function(e){this.filteredValue&&("undefined"!==typeof e&&null!==e?this.filteredValue.splice(0,1,e):this.filteredValue.splice(0,1))}},filteredValue:{get:function(){return this.column&&this.column.filteredValue||[]},set:function(e){this.column&&(this.column.filteredValue=e)}},multiple:function(){return!this.column||this.column.filterMultiple}},mounted:function(){var e=this;this.popperElm=this.$el,this.referenceElm=this.cell,this.table.bodyWrapper.addEventListener("scroll",(function(){e.updatePopper()})),this.$watch("showPopper",(function(t){e.column&&(e.column.filterOpened=t),t?ne.open(e):ne.close(e)}))},watch:{showPopper:function(e){!0===e&&parseInt(this.popperJS._popper.style.zIndex,10)1;return o&&(this.$parent.isGroup=!0),e("table",{class:"el-table__header",attrs:{cellspacing:"0",cellpadding:"0",border:"0"}},[e("colgroup",[this.columns.map((function(t){return e("col",{attrs:{name:t.id},key:t.id})})),this.hasGutter?e("col",{attrs:{name:"gutter"}}):""]),e("thead",{class:[{"is-group":o,"has-gutter":this.hasGutter}]},[this._l(r,(function(n,r){return e("tr",{style:t.getHeaderRowStyle(r),class:t.getHeaderRowClass(r)},[n.map((function(o,i){return e("th",{attrs:{colspan:o.colSpan,rowspan:o.rowSpan},on:{mousemove:function(e){return t.handleMouseMove(e,o)},mouseout:t.handleMouseOut,mousedown:function(e){return t.handleMouseDown(e,o)},click:function(e){return t.handleHeaderClick(e,o)},contextmenu:function(e){return t.handleHeaderContextMenu(e,o)}},style:t.getHeaderCellStyle(r,i,n,o),class:t.getHeaderCellClass(r,i,n,o),key:o.id},[e("div",{class:["cell",o.filteredValue&&o.filteredValue.length>0?"highlight":"",o.labelClassName]},[o.renderHeader?o.renderHeader.call(t._renderProxy,e,{column:o,$index:i,store:t.store,_self:t.$parent.$vnode.context}):o.label,o.sortable?e("span",{class:"caret-wrapper",on:{click:function(e){return t.handleSortClick(e,o)}}},[e("i",{class:"sort-caret ascending",on:{click:function(e){return t.handleSortClick(e,o,"ascending")}}}),e("i",{class:"sort-caret descending",on:{click:function(e){return t.handleSortClick(e,o,"descending")}}})]):"",o.filterable?e("span",{class:"el-table__column-filter-trigger",on:{click:function(e){return t.handleFilterClick(e,o)}}},[e("i",{class:["el-icon-arrow-down",o.filterOpened?"el-icon-arrow-up":""]})]):""])])})),t.hasGutter?e("th",{class:"gutter"}):""])}))])])},props:{fixed:String,store:{required:!0},border:Boolean,defaultSort:{type:Object,default:function(){return{prop:"",order:""}}}},components:{ElCheckbox:a.a},computed:de({table:function(){return this.$parent},hasGutter:function(){return!this.fixed&&this.tableLayout.gutterWidth}},N({columns:"columns",isAllSelected:"isAllSelected",leftFixedLeafCount:"fixedLeafColumnsLength",rightFixedLeafCount:"rightFixedLeafColumnsLength",columnsCount:function(e){return e.columns.length},leftFixedCount:function(e){return e.fixedColumns.length},rightFixedCount:function(e){return e.rightFixedColumns.length}})),created:function(){this.filterPanels={}},mounted:function(){var e=this;this.$nextTick((function(){var t=e.defaultSort,n=t.prop,r=t.order,o=!0;e.store.commit("sort",{prop:n,order:r,init:o})}))},beforeDestroy:function(){var e=this.filterPanels;for(var t in e)e.hasOwnProperty(t)&&e[t]&&e[t].$destroy(!0)},methods:{isCellHidden:function(e,t){for(var n=0,r=0;r=this.leftFixedLeafCount:"right"===this.fixed?n=this.columnsCount-this.rightFixedLeafCount},getHeaderRowStyle:function(e){var t=this.table.headerRowStyle;return"function"===typeof t?t.call(null,{rowIndex:e}):t},getHeaderRowClass:function(e){var t=[],n=this.table.headerRowClassName;return"string"===typeof n?t.push(n):"function"===typeof n&&t.push(n.call(null,{rowIndex:e})),t.join(" ")},getHeaderCellStyle:function(e,t,n,r){var o=this.table.headerCellStyle;return"function"===typeof o?o.call(null,{rowIndex:e,columnIndex:t,row:n,column:r}):o},getHeaderCellClass:function(e,t,n,r){var o=[r.id,r.order,r.headerAlign,r.className,r.labelClassName];0===e&&this.isCellHidden(t,n)&&o.push("is-hidden"),r.children||o.push("is-leaf"),r.sortable&&o.push("is-sortable");var i=this.table.headerCellClassName;return"string"===typeof i?o.push(i):"function"===typeof i&&o.push(i.call(null,{rowIndex:e,columnIndex:t,row:n,column:r})),o.join(" ")},toggleAllSelection:function(e){e.stopPropagation(),this.store.commit("toggleAllSelection")},handleFilterClick:function(e,t){e.stopPropagation();var n=e.target,r="TH"===n.tagName?n:n.parentNode;if(!Object(D["hasClass"])(r,"noclick")){r=r.querySelector(".el-table__column-filter-trigger")||r;var o=this.$parent,i=this.filterPanels[t.id];i&&t.filterOpened?i.showPopper=!1:(i||(i=new b.a(fe),this.filterPanels[t.id]=i,t.filterPlacement&&(i.placement=t.filterPlacement),i.table=o,i.cell=r,i.column=t,!this.$isServer&&i.$mount(document.createElement("div"))),setTimeout((function(){i.showPopper=!0}),16))}},handleHeaderClick:function(e,t){!t.filters&&t.sortable?this.handleSortClick(e,t):t.filterable&&!t.sortable&&this.handleFilterClick(e,t),this.$parent.$emit("header-click",t,e)},handleHeaderContextMenu:function(e,t){this.$parent.$emit("header-contextmenu",t,e)},handleMouseDown:function(e,t){var n=this;if(!this.$isServer&&!(t.children&&t.children.length>0)&&this.draggingColumn&&this.border){this.dragging=!0,this.$parent.resizeProxyVisible=!0;var r=this.$parent,o=r.$el,i=o.getBoundingClientRect().left,a=this.$el.querySelector("th."+t.id),s=a.getBoundingClientRect(),l=s.left-i+30;Object(D["addClass"])(a,"noclick"),this.dragState={startMouseLeft:e.clientX,startLeft:s.right-i,startColumnLeft:s.left-i,tableLeft:i};var u=r.$refs.resizeProxy;u.style.left=this.dragState.startLeft+"px",document.onselectstart=function(){return!1},document.ondragstart=function(){return!1};var c=function(e){var t=e.clientX-n.dragState.startMouseLeft,r=n.dragState.startLeft+t;u.style.left=Math.max(l,r)+"px"},f=function o(){if(n.dragging){var i=n.dragState,s=i.startColumnLeft,l=i.startLeft,f=parseInt(u.style.left,10),d=f-s;t.width=t.realWidth=d,r.$emit("header-dragend",t.width,l-s,t,e),n.store.scheduleLayout(),document.body.style.cursor="",n.dragging=!1,n.draggingColumn=null,n.dragState={},r.resizeProxyVisible=!1}document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",o),document.onselectstart=null,document.ondragstart=null,setTimeout((function(){Object(D["removeClass"])(a,"noclick")}),0)};document.addEventListener("mousemove",c),document.addEventListener("mouseup",f)}},handleMouseMove:function(e,t){if(!(t.children&&t.children.length>0)){var n=e.target;while(n&&"TH"!==n.tagName)n=n.parentNode;if(t&&t.resizable&&!this.dragging&&this.border){var r=n.getBoundingClientRect(),o=document.body.style;r.width>12&&r.right-e.pageX<8?(o.cursor="col-resize",Object(D["hasClass"])(n,"is-sortable")&&(n.style.cursor="col-resize"),this.draggingColumn=t):this.dragging||(o.cursor="",Object(D["hasClass"])(n,"is-sortable")&&(n.style.cursor="pointer"),this.draggingColumn=null)}}},handleMouseOut:function(){this.$isServer||(document.body.style.cursor="")},toggleOrder:function(e){var t=e.order,n=e.sortOrders;if(""===t)return n[0];var r=n.indexOf(t||null);return n[r>n.length-2?0:r+1]},handleSortClick:function(e,t,n){e.stopPropagation();var r=t.order===n?null:n||this.toggleOrder(t),o=e.target;while(o&&"TH"!==o.tagName)o=o.parentNode;if(o&&"TH"===o.tagName&&Object(D["hasClass"])(o,"noclick"))Object(D["removeClass"])(o,"noclick");else if(t.sortable){var i=this.store.states,a=i.sortProp,s=void 0,l=i.sortingColumn;(l!==t||l===t&&null===l.order)&&(l&&(l.order=null),i.sortingColumn=t,a=t.property),s=t.order=r||null,i.sortProp=a,i.sortOrder=s,this.store.commit("changeSortCondition")}}},data:function(){return{draggingColumn:null,dragging:!1,dragState:{}}}},me=Object.assign||function(e){for(var t=1;t=this.leftFixedLeafCount;if("right"===this.fixed){for(var r=0,o=0;o=this.columnsCount-this.rightFixedCount)},getRowClasses:function(e,t){var n=[e.id,e.align,e.labelClassName];return e.className&&n.push(e.className),this.isCellHidden(t,this.columns,e)&&n.push("is-hidden"),e.children||n.push("is-leaf"),n}}},ge=Object.assign||function(e){for(var t=1;t0){var r=n.scrollTop;t.pixelY<0&&0!==r&&e.preventDefault(),t.pixelY>0&&n.scrollHeight-n.clientHeight>r&&e.preventDefault(),n.scrollTop+=Math.ceil(t.pixelY/5)}else n.scrollLeft+=Math.ceil(t.pixelX/5)},handleHeaderFooterMousewheel:function(e,t){var n=t.pixelX,r=t.pixelY;Math.abs(n)>=Math.abs(r)&&(this.bodyWrapper.scrollLeft+=t.pixelX/5)},syncPostion:Object(s["throttle"])(20,(function(){var e=this.bodyWrapper,t=e.scrollLeft,n=e.scrollTop,r=e.offsetWidth,o=e.scrollWidth,i=this.$refs,a=i.headerWrapper,s=i.footerWrapper,l=i.fixedBodyWrapper,u=i.rightFixedBodyWrapper;a&&(a.scrollLeft=t),s&&(s.scrollLeft=t),l&&(l.scrollTop=n),u&&(u.scrollTop=n);var c=o-r-1;this.scrollPosition=t>=c?"right":0===t?"left":"middle"})),bindEvents:function(){this.bodyWrapper.addEventListener("scroll",this.syncPostion,{passive:!0}),this.fit&&Object(l["addResizeListener"])(this.$el,this.resizeListener)},unbindEvents:function(){this.bodyWrapper.removeEventListener("scroll",this.syncPostion,{passive:!0}),this.fit&&Object(l["removeResizeListener"])(this.$el,this.resizeListener)},resizeListener:function(){if(this.$ready){var e=!1,t=this.$el,n=this.resizeState,r=n.width,o=n.height,i=t.offsetWidth;r!==i&&(e=!0);var a=t.offsetHeight;(this.height||this.shouldUpdateHeight)&&o!==a&&(e=!0),e&&(this.resizeState.width=i,this.resizeState.height=a,this.doLayout())}},doLayout:function(){this.shouldUpdateHeight&&this.layout.updateElsHeight(),this.layout.updateColumnsWidth()},sort:function(e,t){this.store.commit("sort",{prop:e,order:t})},toggleAllSelection:function(){this.store.commit("toggleAllSelection")}},computed:ge({tableSize:function(){return this.size||(this.$ELEMENT||{}).size},bodyWrapper:function(){return this.$refs.bodyWrapper},shouldUpdateHeight:function(){return this.height||this.maxHeight||this.fixedColumns.length>0||this.rightFixedColumns.length>0},bodyWidth:function(){var e=this.layout,t=e.bodyWidth,n=e.scrollY,r=e.gutterWidth;return t?t-(n?r:0)+"px":""},bodyHeight:function(){var e=this.layout,t=e.headerHeight,n=void 0===t?0:t,r=e.bodyHeight,o=e.footerHeight,i=void 0===o?0:o;if(this.height)return{height:r?r+"px":""};if(this.maxHeight){var a=Object(w["j"])(this.maxHeight);if("number"===typeof a)return{"max-height":a-i-(this.showHeader?n:0)+"px"}}return{}},fixedBodyHeight:function(){if(this.height)return{height:this.layout.fixedBodyHeight?this.layout.fixedBodyHeight+"px":""};if(this.maxHeight){var e=Object(w["j"])(this.maxHeight);if("number"===typeof e)return e=this.layout.scrollX?e-this.layout.gutterWidth:e,this.showHeader&&(e-=this.layout.headerHeight),e-=this.layout.footerHeight,{"max-height":e+"px"}}return{}},fixedHeight:function(){return this.maxHeight?this.showSummary?{bottom:0}:{bottom:this.layout.scrollX&&this.data.length?this.layout.gutterWidth+"px":""}:this.showSummary?{height:this.layout.tableHeight?this.layout.tableHeight+"px":""}:{height:this.layout.viewportHeight?this.layout.viewportHeight+"px":""}},emptyBlockStyle:function(){if(this.data&&this.data.length)return null;var e="100%";return this.layout.appendHeight&&(e="calc(100% - "+this.layout.appendHeight+"px)"),{width:this.bodyWidth,height:e}}},N({selection:"selection",columns:"columns",tableData:"data",fixedColumns:"fixedColumns",rightFixedColumns:"rightFixedColumns"})),watch:{height:{immediate:!0,handler:function(e){this.layout.setHeight(e)}},maxHeight:{immediate:!0,handler:function(e){this.layout.setMaxHeight(e)}},currentRowKey:{immediate:!0,handler:function(e){this.rowKey&&this.store.setCurrentRowKey(e)}},data:{immediate:!0,handler:function(e){this.store.commit("setData",e)}},expandRowKeys:{immediate:!0,handler:function(e){e&&this.store.setExpandRowKeysAdapter(e)}}},created:function(){var e=this;this.tableId="el-table_"+be++,this.debouncedUpdateLayout=Object(s["debounce"])(50,(function(){return e.doLayout()}))},mounted:function(){var e=this;this.bindEvents(),this.store.updateColumns(),this.doLayout(),this.resizeState={width:this.$el.offsetWidth,height:this.$el.offsetHeight},this.store.states.columns.forEach((function(t){t.filteredValue&&t.filteredValue.length&&e.store.commit("filterChange",{column:t,values:t.filteredValue,silent:!0})})),this.$ready=!0},destroyed:function(){this.unbindEvents()},data:function(){var e=this.treeProps,t=e.hasChildren,n=void 0===t?"hasChildren":t,r=e.children,o=void 0===r?"children":r;this.store=L(this,{rowKey:this.rowKey,defaultExpandAll:this.defaultExpandAll,selectOnIndeterminate:this.selectOnIndeterminate,indent:this.indent,lazy:this.lazy,lazyColumnIdentifier:n,childrenColumnName:o});var i=new z({store:this.store,table:this,fit:this.fit,showHeader:this.showHeader});return{layout:i,isHidden:!1,renderExpanded:null,resizeProxyVisible:!1,resizeState:{width:null,height:null},isGroup:!1,scrollPosition:"left"}}},xe=_e,we=Object(ue["a"])(xe,r,o,!1,null,null,null);we.options.__file="packages/table/src/table.vue";var Ce=we.exports;Ce.install=function(e){e.component(Ce.name,Ce)};t["default"]=Ce}])},b41f:function(e,t,n){var r,o,i,a,s,l,u,c,f=n("d5dc"),d=n("4aef").f,p=n("67ea"),h=n("6dcf").set,v=n("c044"),m=f.MutationObserver||f.WebKitMutationObserver,y=f.process,g=f.Promise,b="process"==p(y),_=d(f,"queueMicrotask"),x=_&&_.value;x||(r=function(){var e,t;b&&(e=y.domain)&&e.exit();while(o){t=o.fn,o=o.next;try{t()}catch(n){throw o?a():i=void 0,n}}i=void 0,e&&e.enter()},b?a=function(){y.nextTick(r)}:m&&!v?(s=!0,l=document.createTextNode(""),new m(r).observe(l,{characterData:!0}),a=function(){l.data=s=!s}):g&&g.resolve?(u=g.resolve(void 0),c=u.then,a=function(){c.call(u,r)}):a=function(){h.call(f,r)}),e.exports=x||function(e){var t={fn:e,next:void 0};i&&(i.next=t),o||(o=t,a()),i=t}},b451:function(e,t,n){"use strict";t.__esModule=!0;var r=n("0261"),o=a(r),i=n("3c84");function a(e){return e&&e.__esModule?e:{default:e}}var s=[],l="@@clickoutsideContext",u=void 0,c=0;function f(e,t,n){return function(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};!(n&&n.context&&r.target&&o.target)||e.contains(r.target)||e.contains(o.target)||e===r.target||n.context.popperElm&&(n.context.popperElm.contains(r.target)||n.context.popperElm.contains(o.target))||(t.expression&&e[l].methodName&&n.context[e[l].methodName]?n.context[e[l].methodName]():e[l].bindingFn&&e[l].bindingFn())}}!o.default.prototype.$isServer&&(0,i.on)(document,"mousedown",(function(e){return u=e})),!o.default.prototype.$isServer&&(0,i.on)(document,"mouseup",(function(e){s.forEach((function(t){return t[l].documentHandler(e,u)}))})),t.default={bind:function(e,t,n){s.push(e);var r=c++;e[l]={id:r,documentHandler:f(e,t,n),methodName:t.expression,bindingFn:t.value}},update:function(e,t,n){e[l].documentHandler=f(e,t,n),e[l].methodName=t.expression,e[l].bindingFn=t.value},unbind:function(e){for(var t=s.length,n=0;n=74)&&(r=a.match(/Chrome\/(\d+)/),r&&(o=r[1]))),e.exports=o&&+o},c044:function(e,t,n){var r=n("a406");e.exports=/(iphone|ipod|ipad).*applewebkit/i.test(r)},c223:function(e,t,n){var r=n("7a23"),o=n("88b4"),i=n("ac83"),a=n("7dc7"),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(i(e),t=a(t,!0),i(n),o)try{return s(e,t,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},c405:function(e,t,n){},c49e:function(e,t,n){var r=n("df50");e.exports=r("document","documentElement")},c77e:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=74)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},2:function(e,t){e.exports=n("3c84")},3:function(e,t){e.exports=n("d41f")},5:function(e,t){e.exports=n("9bb9")},7:function(e,t){e.exports=n("0261")},74:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("span",[n("transition",{attrs:{name:e.transition},on:{"after-enter":e.handleAfterEnter,"after-leave":e.handleAfterLeave}},[n("div",{directives:[{name:"show",rawName:"v-show",value:!e.disabled&&e.showPopper,expression:"!disabled && showPopper"}],ref:"popper",staticClass:"el-popover el-popper",class:[e.popperClass,e.content&&"el-popover--plain"],style:{width:e.width+"px"},attrs:{role:"tooltip",id:e.tooltipId,"aria-hidden":e.disabled||!e.showPopper?"true":"false"}},[e.title?n("div",{staticClass:"el-popover__title",domProps:{textContent:e._s(e.title)}}):e._e(),e._t("default",[e._v(e._s(e.content))])],2)]),e._t("reference")],2)},o=[];r._withStripped=!0;var i=n(5),a=n.n(i),s=n(2),l=n(3),u={name:"ElPopover",mixins:[a.a],props:{trigger:{type:String,default:"click",validator:function(e){return["click","focus","hover","manual"].indexOf(e)>-1}},openDelay:{type:Number,default:0},closeDelay:{type:Number,default:200},title:String,disabled:Boolean,content:String,reference:{},popperClass:String,width:{},visibleArrow:{default:!0},arrowOffset:{type:Number,default:0},transition:{type:String,default:"fade-in-linear"},tabindex:{type:Number,default:0}},computed:{tooltipId:function(){return"el-popover-"+Object(l["generateId"])()}},watch:{showPopper:function(e){this.disabled||(e?this.$emit("show"):this.$emit("hide"))}},mounted:function(){var e=this,t=this.referenceElm=this.reference||this.$refs.reference,n=this.popper||this.$refs.popper;!t&&this.$slots.reference&&this.$slots.reference[0]&&(t=this.referenceElm=this.$slots.reference[0].elm),t&&(Object(s["addClass"])(t,"el-popover__reference"),t.setAttribute("aria-describedby",this.tooltipId),t.setAttribute("tabindex",this.tabindex),n.setAttribute("tabindex",0),"click"!==this.trigger&&(Object(s["on"])(t,"focusin",(function(){e.handleFocus();var n=t.__vue__;n&&"function"===typeof n.focus&&n.focus()})),Object(s["on"])(n,"focusin",this.handleFocus),Object(s["on"])(t,"focusout",this.handleBlur),Object(s["on"])(n,"focusout",this.handleBlur)),Object(s["on"])(t,"keydown",this.handleKeydown),Object(s["on"])(t,"click",this.handleClick)),"click"===this.trigger?(Object(s["on"])(t,"click",this.doToggle),Object(s["on"])(document,"click",this.handleDocumentClick)):"hover"===this.trigger?(Object(s["on"])(t,"mouseenter",this.handleMouseEnter),Object(s["on"])(n,"mouseenter",this.handleMouseEnter),Object(s["on"])(t,"mouseleave",this.handleMouseLeave),Object(s["on"])(n,"mouseleave",this.handleMouseLeave)):"focus"===this.trigger&&(this.tabindex<0&&console.warn("[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key"),t.querySelector("input, textarea")?(Object(s["on"])(t,"focusin",this.doShow),Object(s["on"])(t,"focusout",this.doClose)):(Object(s["on"])(t,"mousedown",this.doShow),Object(s["on"])(t,"mouseup",this.doClose)))},beforeDestroy:function(){this.cleanup()},deactivated:function(){this.cleanup()},methods:{doToggle:function(){this.showPopper=!this.showPopper},doShow:function(){this.showPopper=!0},doClose:function(){this.showPopper=!1},handleFocus:function(){Object(s["addClass"])(this.referenceElm,"focusing"),"click"!==this.trigger&&"focus"!==this.trigger||(this.showPopper=!0)},handleClick:function(){Object(s["removeClass"])(this.referenceElm,"focusing")},handleBlur:function(){Object(s["removeClass"])(this.referenceElm,"focusing"),"click"!==this.trigger&&"focus"!==this.trigger||(this.showPopper=!1)},handleMouseEnter:function(){var e=this;clearTimeout(this._timer),this.openDelay?this._timer=setTimeout((function(){e.showPopper=!0}),this.openDelay):this.showPopper=!0},handleKeydown:function(e){27===e.keyCode&&"manual"!==this.trigger&&this.doClose()},handleMouseLeave:function(){var e=this;clearTimeout(this._timer),this.closeDelay?this._timer=setTimeout((function(){e.showPopper=!1}),this.closeDelay):this.showPopper=!1},handleDocumentClick:function(e){var t=this.reference||this.$refs.reference,n=this.popper||this.$refs.popper;!t&&this.$slots.reference&&this.$slots.reference[0]&&(t=this.referenceElm=this.$slots.reference[0].elm),this.$el&&t&&!this.$el.contains(e.target)&&!t.contains(e.target)&&n&&!n.contains(e.target)&&(this.showPopper=!1)},handleAfterEnter:function(){this.$emit("after-enter")},handleAfterLeave:function(){this.$emit("after-leave"),this.doDestroy()},cleanup:function(){(this.openDelay||this.closeDelay)&&clearTimeout(this._timer)}},destroyed:function(){var e=this.reference;Object(s["off"])(e,"click",this.doToggle),Object(s["off"])(e,"mouseup",this.doClose),Object(s["off"])(e,"mousedown",this.doShow),Object(s["off"])(e,"focusin",this.doShow),Object(s["off"])(e,"focusout",this.doClose),Object(s["off"])(e,"mousedown",this.doShow),Object(s["off"])(e,"mouseup",this.doClose),Object(s["off"])(e,"mouseleave",this.handleMouseLeave),Object(s["off"])(e,"mouseenter",this.handleMouseEnter),Object(s["off"])(document,"click",this.handleDocumentClick)}},c=u,f=n(0),d=Object(f["a"])(c,r,o,!1,null,null,null);d.options.__file="packages/popover/src/main.vue";var p=d.exports,h=function(e,t,n){var r=t.expression?t.value:t.arg,o=n.context.$refs[r];o&&(Array.isArray(o)?o[0].$refs.reference=e:o.$refs.reference=e)},v={bind:function(e,t,n){h(e,t,n)},inserted:function(e,t,n){h(e,t,n)}},m=n(7),y=n.n(m);y.a.directive("popover",v),p.install=function(e){e.directive("popover",v),e.component(p.name,p)},p.directive=v;t["default"]=p}})},c85c:function(e,t,n){var r=n("d5dc"),o=n("200e"),i="__core-js_shared__",a=r[i]||o(i,{});e.exports=a},c9db:function(e,t,n){"use strict";var r,o,i,a,s=n("91fe"),l=n("e17a"),u=n("d5dc"),c=n("df50"),f=n("644f"),d=n("3d8a"),p=n("417f"),h=n("94d7"),v=n("a867"),m=n("d68d"),y=n("a9f2"),g=n("40d4"),b=n("67ea"),_=n("527d"),x=n("66b2"),w=n("e52f"),C=n("fb8e"),S=n("6dcf").set,O=n("b41f"),E=n("4301"),k=n("a7d9"),$=n("df22"),j=n("5c90"),A=n("d0e2"),T=n("12d9"),M=n("57c4"),P=n("bf98"),L=M("species"),N="Promise",R=A.get,I=A.set,F=A.getterFor(N),H=f,z=u.TypeError,D=u.document,B=u.process,W=c("fetch"),V=$.f,U=V,q="process"==b(B),K=!!(D&&D.createEvent&&u.dispatchEvent),G="unhandledrejection",X="rejectionhandled",Y=0,J=1,Z=2,Q=1,ee=2,te=T(N,(function(){var e=_(H)!==String(H);if(!e){if(66===P)return!0;if(!q&&"function"!=typeof PromiseRejectionEvent)return!0}if(l&&!H.prototype["finally"])return!0;if(P>=51&&/native code/.test(H))return!1;var t=H.resolve(1),n=function(e){e((function(){}),(function(){}))},r=t.constructor={};return r[L]=n,!(t.then((function(){}))instanceof n)})),ne=te||!w((function(e){H.all(e)["catch"]((function(){}))})),re=function(e){var t;return!(!m(e)||"function"!=typeof(t=e.then))&&t},oe=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;O((function(){var o=t.value,i=t.state==J,a=0;while(r.length>a){var s,l,u,c=r[a++],f=i?c.ok:c.fail,d=c.resolve,p=c.reject,h=c.domain;try{f?(i||(t.rejection===ee&&le(e,t),t.rejection=Q),!0===f?s=o:(h&&h.enter(),s=f(o),h&&(h.exit(),u=!0)),s===c.promise?p(z("Promise-chain cycle")):(l=re(s))?l.call(s,d,p):d(s)):p(o)}catch(v){h&&!u&&h.exit(),p(v)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&ae(e,t)}))}},ie=function(e,t,n){var r,o;K?(r=D.createEvent("Event"),r.promise=t,r.reason=n,r.initEvent(e,!1,!0),u.dispatchEvent(r)):r={promise:t,reason:n},(o=u["on"+e])?o(r):e===G&&k("Unhandled promise rejection",n)},ae=function(e,t){S.call(u,(function(){var n,r=t.value,o=se(t);if(o&&(n=j((function(){q?B.emit("unhandledRejection",r,e):ie(G,e,r)})),t.rejection=q||se(t)?ee:Q,n.error))throw n.value}))},se=function(e){return e.rejection!==Q&&!e.parent},le=function(e,t){S.call(u,(function(){q?B.emit("rejectionHandled",e):ie(X,e,t.value)}))},ue=function(e,t,n,r){return function(o){e(t,n,o,r)}},ce=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=Z,oe(e,t,!0))},fe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw z("Promise can't be resolved itself");var o=re(n);o?O((function(){var r={done:!1};try{o.call(n,ue(fe,e,r,t),ue(ce,e,r,t))}catch(i){ce(e,r,i,t)}})):(t.value=n,t.state=J,oe(e,t,!1))}catch(i){ce(e,{done:!1},i,t)}}};te&&(H=function(e){g(this,H,N),y(e),r.call(this);var t=R(this);try{e(ue(fe,this,t),ue(ce,this,t))}catch(n){ce(this,t,n)}},r=function(e){I(this,{type:N,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:Y,value:void 0})},r.prototype=p(H.prototype,{then:function(e,t){var n=F(this),r=V(C(this,H));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=q?B.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=Y&&oe(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),o=function(){var e=new r,t=R(e);this.promise=e,this.resolve=ue(fe,e,t),this.reject=ue(ce,e,t)},$.f=V=function(e){return e===H||e===i?new o(e):U(e)},l||"function"!=typeof f||(a=f.prototype.then,d(f.prototype,"then",(function(e,t){var n=this;return new H((function(e,t){a.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof W&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return E(H,W.apply(u,arguments))}}))),s({global:!0,wrap:!0,forced:te},{Promise:H}),h(H,N,!1,!0),v(N),i=c(N),s({target:N,stat:!0,forced:te},{reject:function(e){var t=V(this);return t.reject.call(void 0,e),t.promise}}),s({target:N,stat:!0,forced:l||te},{resolve:function(e){return E(l&&this===i?H:this,e)}}),s({target:N,stat:!0,forced:ne},{all:function(e){var t=this,n=V(t),r=n.resolve,o=n.reject,i=j((function(){var n=y(t.resolve),i=[],a=0,s=1;x(e,(function(e){var l=a++,u=!1;i.push(void 0),s++,n.call(t,e).then((function(e){u||(u=!0,i[l]=e,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(e){var t=this,n=V(t),r=n.reject,o=j((function(){var o=y(t.resolve);x(e,(function(e){o.call(t,e).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},ca47:function(e,t,n){var r=n("60f8");e.exports=function(e,t,n){return void 0===n?r(e,t,!1):r(e,n,!1!==t)}},d0e2:function(e,t,n){var r,o,i,a=n("3109"),s=n("d5dc"),l=n("d68d"),u=n("2ba5"),c=n("f28d"),f=n("4d52"),d=n("4888"),p=s.WeakMap,h=function(e){return i(e)?o(e):r(e,{})},v=function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}};if(a){var m=new p,y=m.get,g=m.has,b=m.set;r=function(e,t){return b.call(m,e,t),t},o=function(e){return y.call(m,e)||{}},i=function(e){return g.call(m,e)}}else{var _=f("state");d[_]=!0,r=function(e,t){return u(e,_,t),t},o=function(e){return c(e,_)?e[_]:{}},i=function(e){return c(e,_)}}e.exports={set:r,get:o,has:i,enforce:h,getterFor:v}},d41f:function(e,t,n){"use strict";t.__esModule=!0,t.isEmpty=t.isEqual=t.arrayEquals=t.looseEqual=t.capitalize=t.kebabCase=t.autoprefixer=t.isFirefox=t.isEdge=t.isIE=t.coerceTruthyValueToArray=t.arrayFind=t.arrayFindIndex=t.escapeRegexpString=t.valueEquals=t.generateId=t.getValueByPath=void 0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.noop=u,t.hasOwn=c,t.toObject=d,t.getPropByPath=p,t.rafThrottle=g,t.objToArray=b;var o=n("0261"),i=s(o),a=n("22c9");function s(e){return e&&e.__esModule?e:{default:e}}var l=Object.prototype.hasOwnProperty;function u(){}function c(e,t){return l.call(e,t)}function f(e,t){for(var n in t)e[n]=t[n];return e}function d(e){for(var t={},n=0;n0&&void 0!==arguments[0]?arguments[0]:"";return String(e).replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")};var h=t.arrayFindIndex=function(e,t){for(var n=0;n!==e.length;++n)if(t(e[n]))return n;return-1},v=(t.arrayFind=function(e,t){var n=h(e,t);return-1!==n?e[n]:void 0},t.coerceTruthyValueToArray=function(e){return Array.isArray(e)?e:e?[e]:[]},t.isIE=function(){return!i.default.prototype.$isServer&&!isNaN(Number(document.documentMode))},t.isEdge=function(){return!i.default.prototype.$isServer&&navigator.userAgent.indexOf("Edge")>-1},t.isFirefox=function(){return!i.default.prototype.$isServer&&!!window.navigator.userAgent.match(/firefox/i)},t.autoprefixer=function(e){if("object"!==("undefined"===typeof e?"undefined":r(e)))return e;var t=["transform","transition","animation"],n=["ms-","webkit-"];return t.forEach((function(t){var r=e[t];t&&r&&n.forEach((function(n){e[n+t]=r}))})),e},t.kebabCase=function(e){var t=/([^-])([A-Z])/g;return e.replace(t,"$1-$2").replace(t,"$1-$2").toLowerCase()},t.capitalize=function(e){return(0,a.isString)(e)?e.charAt(0).toUpperCase()+e.slice(1):e},t.looseEqual=function(e,t){var n=(0,a.isObject)(e),r=(0,a.isObject)(t);return n&&r?JSON.stringify(e)===JSON.stringify(t):!n&&!r&&String(e)===String(t)}),m=t.arrayEquals=function(e,t){if(e=e||[],t=t||[],e.length!==t.length)return!1;for(var n=0;n=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},da43:function(e,t,n){},da65:function(e,t,n){},da66:function(e,t,n){var r=n("d68d");e.exports=function(e){if(!r(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},daa7:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=77)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},10:function(e,t){e.exports=n("9504")},13:function(e,t){e.exports=n("ec2d")},15:function(e,t){e.exports=n("4eda")},19:function(e,t){e.exports=n("6406")},2:function(e,t){e.exports=n("3c84")},23:function(e,t){e.exports=n("bee9")},47:function(e,t){e.exports=n("f0f6")},6:function(e,t){e.exports=n("1f4f")},7:function(e,t){e.exports=n("0261")},77:function(e,t,n){"use strict";n.r(t);var r=n(7),o=n.n(r),i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"msgbox-fade"}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],staticClass:"el-message-box__wrapper",attrs:{tabindex:"-1",role:"dialog","aria-modal":"true","aria-label":e.title||"dialog"},on:{click:function(t){return t.target!==t.currentTarget?null:e.handleWrapperClick(t)}}},[n("div",{staticClass:"el-message-box",class:[e.customClass,e.center&&"el-message-box--center"]},[null!==e.title?n("div",{staticClass:"el-message-box__header"},[n("div",{staticClass:"el-message-box__title"},[e.icon&&e.center?n("div",{class:["el-message-box__status",e.icon]}):e._e(),n("span",[e._v(e._s(e.title))])]),e.showClose?n("button",{staticClass:"el-message-box__headerbtn",attrs:{type:"button","aria-label":"Close"},on:{click:function(t){e.handleAction(e.distinguishCancelAndClose?"close":"cancel")},keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction(e.distinguishCancelAndClose?"close":"cancel")}}},[n("i",{staticClass:"el-message-box__close el-icon-close"})]):e._e()]):e._e(),n("div",{staticClass:"el-message-box__content"},[n("div",{staticClass:"el-message-box__container"},[e.icon&&!e.center&&""!==e.message?n("div",{class:["el-message-box__status",e.icon]}):e._e(),""!==e.message?n("div",{staticClass:"el-message-box__message"},[e._t("default",[e.dangerouslyUseHTMLString?n("p",{domProps:{innerHTML:e._s(e.message)}}):n("p",[e._v(e._s(e.message))])])],2):e._e()]),n("div",{directives:[{name:"show",rawName:"v-show",value:e.showInput,expression:"showInput"}],staticClass:"el-message-box__input"},[n("el-input",{ref:"input",attrs:{type:e.inputType,placeholder:e.inputPlaceholder},nativeOn:{keydown:function(t){return"button"in t||!e._k(t.keyCode,"enter",13,t.key,"Enter")?e.handleInputEnter(t):null}},model:{value:e.inputValue,callback:function(t){e.inputValue=t},expression:"inputValue"}}),n("div",{staticClass:"el-message-box__errormsg",style:{visibility:e.editorErrorMessage?"visible":"hidden"}},[e._v(e._s(e.editorErrorMessage))])],1)]),n("div",{staticClass:"el-message-box__btns"},[e.showCancelButton?n("el-button",{class:[e.cancelButtonClasses],attrs:{loading:e.cancelButtonLoading,round:e.roundButton,size:"small"},on:{keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction("cancel")}},nativeOn:{click:function(t){e.handleAction("cancel")}}},[e._v("\n "+e._s(e.cancelButtonText||e.t("el.messagebox.cancel"))+"\n ")]):e._e(),n("el-button",{directives:[{name:"show",rawName:"v-show",value:e.showConfirmButton,expression:"showConfirmButton"}],ref:"confirm",class:[e.confirmButtonClasses],attrs:{loading:e.confirmButtonLoading,round:e.roundButton,size:"small"},on:{keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction("confirm")}},nativeOn:{click:function(t){e.handleAction("confirm")}}},[e._v("\n "+e._s(e.confirmButtonText||e.t("el.messagebox.confirm"))+"\n ")])],1)])])])},a=[];i._withStripped=!0;var s=n(15),l=n.n(s),u=n(6),c=n.n(u),f=n(10),d=n.n(f),p=n(13),h=n.n(p),v=n(2),m=n(19),y=n(47),g=n.n(y),b=void 0,_={success:"success",info:"info",warning:"warning",error:"error"},x={mixins:[l.a,c.a],props:{modal:{default:!0},lockScroll:{default:!0},showClose:{type:Boolean,default:!0},closeOnClickModal:{default:!0},closeOnPressEscape:{default:!0},closeOnHashChange:{default:!0},center:{default:!1,type:Boolean},roundButton:{default:!1,type:Boolean}},components:{ElInput:d.a,ElButton:h.a},computed:{icon:function(){var e=this.type,t=this.iconClass;return t||(e&&_[e]?"el-icon-"+_[e]:"")},confirmButtonClasses:function(){return"el-button--primary "+this.confirmButtonClass},cancelButtonClasses:function(){return""+this.cancelButtonClass}},methods:{getSafeClose:function(){var e=this,t=this.uid;return function(){e.$nextTick((function(){t===e.uid&&e.doClose()}))}},doClose:function(){var e=this;this.visible&&(this.visible=!1,this._closing=!0,this.onClose&&this.onClose(),b.closeDialog(),this.lockScroll&&setTimeout(this.restoreBodyStyle,200),this.opened=!1,this.doAfterClose(),setTimeout((function(){e.action&&e.callback(e.action,e)})))},handleWrapperClick:function(){this.closeOnClickModal&&this.handleAction(this.distinguishCancelAndClose?"close":"cancel")},handleInputEnter:function(){if("textarea"!==this.inputType)return this.handleAction("confirm")},handleAction:function(e){("prompt"!==this.$type||"confirm"!==e||this.validate())&&(this.action=e,"function"===typeof this.beforeClose?(this.close=this.getSafeClose(),this.beforeClose(e,this,this.close)):this.doClose())},validate:function(){if("prompt"===this.$type){var e=this.inputPattern;if(e&&!e.test(this.inputValue||""))return this.editorErrorMessage=this.inputErrorMessage||Object(m["t"])("el.messagebox.error"),Object(v["addClass"])(this.getInputElement(),"invalid"),!1;var t=this.inputValidator;if("function"===typeof t){var n=t(this.inputValue);if(!1===n)return this.editorErrorMessage=this.inputErrorMessage||Object(m["t"])("el.messagebox.error"),Object(v["addClass"])(this.getInputElement(),"invalid"),!1;if("string"===typeof n)return this.editorErrorMessage=n,Object(v["addClass"])(this.getInputElement(),"invalid"),!1}}return this.editorErrorMessage="",Object(v["removeClass"])(this.getInputElement(),"invalid"),!0},getFirstFocus:function(){var e=this.$el.querySelector(".el-message-box__btns .el-button"),t=this.$el.querySelector(".el-message-box__btns .el-message-box__title");return e||t},getInputElement:function(){var e=this.$refs.input.$refs;return e.input||e.textarea},handleClose:function(){this.handleAction("close")}},watch:{inputValue:{immediate:!0,handler:function(e){var t=this;this.$nextTick((function(n){"prompt"===t.$type&&null!==e&&t.validate()}))}},visible:function(e){var t=this;e&&(this.uid++,"alert"!==this.$type&&"confirm"!==this.$type||this.$nextTick((function(){t.$refs.confirm.$el.focus()})),this.focusAfterClosed=document.activeElement,b=new g.a(this.$el,this.focusAfterClosed,this.getFirstFocus())),"prompt"===this.$type&&(e?setTimeout((function(){t.$refs.input&&t.$refs.input.$el&&t.getInputElement().focus()}),500):(this.editorErrorMessage="",Object(v["removeClass"])(this.getInputElement(),"invalid")))}},mounted:function(){var e=this;this.$nextTick((function(){e.closeOnHashChange&&window.addEventListener("hashchange",e.close)}))},beforeDestroy:function(){this.closeOnHashChange&&window.removeEventListener("hashchange",this.close),setTimeout((function(){b.closeDialog()}))},data:function(){return{uid:1,title:void 0,message:"",type:"",iconClass:"",customClass:"",showInput:!1,inputValue:null,inputPlaceholder:"",inputType:"text",inputPattern:null,inputValidator:null,inputErrorMessage:"",showConfirmButton:!0,showCancelButton:!1,action:"",confirmButtonText:"",cancelButtonText:"",confirmButtonLoading:!1,cancelButtonLoading:!1,confirmButtonClass:"",confirmButtonDisabled:!1,cancelButtonClass:"",editorErrorMessage:null,callback:null,dangerouslyUseHTMLString:!1,focusAfterClosed:null,isOnComposition:!1,distinguishCancelAndClose:!1}}},w=x,C=n(0),S=Object(C["a"])(w,i,a,!1,null,null,null);S.options.__file="packages/message-box/src/main.vue";var O=S.exports,E=n(9),k=n.n(E),$=n(23),j="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},A={title:null,message:"",type:"",iconClass:"",showInput:!1,showClose:!0,modalFade:!0,lockScroll:!0,closeOnClickModal:!0,closeOnPressEscape:!0,closeOnHashChange:!0,inputValue:null,inputPlaceholder:"",inputType:"text",inputPattern:null,inputValidator:null,inputErrorMessage:"",showConfirmButton:!0,showCancelButton:!1,confirmButtonPosition:"right",confirmButtonHighlight:!1,cancelButtonHighlight:!1,confirmButtonText:"",cancelButtonText:"",confirmButtonClass:"",cancelButtonClass:"",customClass:"",beforeClose:null,dangerouslyUseHTMLString:!1,center:!1,roundButton:!1,distinguishCancelAndClose:!1},T=o.a.extend(O),M=void 0,P=void 0,L=[],N=function(e){if(M){var t=M.callback;"function"===typeof t&&(P.showInput?t(P.inputValue,e):t(e)),M.resolve&&("confirm"===e?P.showInput?M.resolve({value:P.inputValue,action:e}):M.resolve(e):!M.reject||"cancel"!==e&&"close"!==e||M.reject(e))}},R=function(){P=new T({el:document.createElement("div")}),P.callback=N},I=function e(){if(P||R(),P.action="",(!P.visible||P.closeTimer)&&L.length>0){M=L.shift();var t=M.options;for(var n in t)t.hasOwnProperty(n)&&(P[n]=t[n]);void 0===t.callback&&(P.callback=N);var r=P.callback;P.callback=function(t,n){r(t,n),e()},Object($["isVNode"])(P.message)?(P.$slots.default=[P.message],P.message=null):delete P.$slots.default,["modal","showClose","closeOnClickModal","closeOnPressEscape","closeOnHashChange"].forEach((function(e){void 0===P[e]&&(P[e]=!0)})),document.body.appendChild(P.$el),o.a.nextTick((function(){P.visible=!0}))}},F=function e(t,n){if(!o.a.prototype.$isServer){if("string"===typeof t||Object($["isVNode"])(t)?(t={message:t},"string"===typeof arguments[1]&&(t.title=arguments[1])):t.callback&&!n&&(n=t.callback),"undefined"!==typeof Promise)return new Promise((function(r,o){L.push({options:k()({},A,e.defaults,t),callback:n,resolve:r,reject:o}),I()}));L.push({options:k()({},A,e.defaults,t),callback:n}),I()}};F.setDefaults=function(e){F.defaults=e},F.alert=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,$type:"alert",closeOnPressEscape:!1,closeOnClickModal:!1},n))},F.confirm=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,$type:"confirm",showCancelButton:!0},n))},F.prompt=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,showCancelButton:!0,showInput:!0,$type:"prompt"},n))},F.close=function(){P.doClose(),P.visible=!1,L=[],M=null};var H=F;t["default"]=H},9:function(e,t){e.exports=n("e93e")}})},de3e:function(e,t,n){var r=n("91fe"),o=n("e045");r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},df22:function(e,t,n){"use strict";var r=n("a9f2"),o=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new o(e)}},df50:function(e,t,n){var r=n("2a2f"),o=n("d5dc"),i=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},df62:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=131)}({131:function(e,t,n){"use strict";n.r(t);var r=n(5),o=n.n(r),i=n(17),a=n.n(i),s=n(2),l=n(3),u=n(7),c=n.n(u),f={name:"ElTooltip",mixins:[o.a],props:{openDelay:{type:Number,default:0},disabled:Boolean,manual:Boolean,effect:{type:String,default:"dark"},arrowOffset:{type:Number,default:0},popperClass:String,content:String,visibleArrow:{default:!0},transition:{type:String,default:"el-fade-in-linear"},popperOptions:{default:function(){return{boundariesPadding:10,gpuAcceleration:!1}}},enterable:{type:Boolean,default:!0},hideAfter:{type:Number,default:0},tabindex:{type:Number,default:0}},data:function(){return{tooltipId:"el-tooltip-"+Object(l["generateId"])(),timeoutPending:null,focusing:!1}},beforeCreate:function(){var e=this;this.$isServer||(this.popperVM=new c.a({data:{node:""},render:function(e){return this.node}}).$mount(),this.debounceClose=a()(200,(function(){return e.handleClosePopper()})))},render:function(e){var t=this;this.popperVM&&(this.popperVM.node=e("transition",{attrs:{name:this.transition},on:{afterLeave:this.doDestroy}},[e("div",{on:{mouseleave:function(){t.setExpectedState(!1),t.debounceClose()},mouseenter:function(){t.setExpectedState(!0)}},ref:"popper",attrs:{role:"tooltip",id:this.tooltipId,"aria-hidden":this.disabled||!this.showPopper?"true":"false"},directives:[{name:"show",value:!this.disabled&&this.showPopper}],class:["el-tooltip__popper","is-"+this.effect,this.popperClass]},[this.$slots.content||this.content])]));var n=this.getFirstElement();if(!n)return null;var r=n.data=n.data||{};return r.staticClass=this.addTooltipClass(r.staticClass),n},mounted:function(){var e=this;this.referenceElm=this.$el,1===this.$el.nodeType&&(this.$el.setAttribute("aria-describedby",this.tooltipId),this.$el.setAttribute("tabindex",this.tabindex),Object(s["on"])(this.referenceElm,"mouseenter",this.show),Object(s["on"])(this.referenceElm,"mouseleave",this.hide),Object(s["on"])(this.referenceElm,"focus",(function(){if(e.$slots.default&&e.$slots.default.length){var t=e.$slots.default[0].componentInstance;t&&t.focus?t.focus():e.handleFocus()}else e.handleFocus()})),Object(s["on"])(this.referenceElm,"blur",this.handleBlur),Object(s["on"])(this.referenceElm,"click",this.removeFocusing)),this.value&&this.popperVM&&this.popperVM.$nextTick((function(){e.value&&e.updatePopper()}))},watch:{focusing:function(e){e?Object(s["addClass"])(this.referenceElm,"focusing"):Object(s["removeClass"])(this.referenceElm,"focusing")}},methods:{show:function(){this.setExpectedState(!0),this.handleShowPopper()},hide:function(){this.setExpectedState(!1),this.debounceClose()},handleFocus:function(){this.focusing=!0,this.show()},handleBlur:function(){this.focusing=!1,this.hide()},removeFocusing:function(){this.focusing=!1},addTooltipClass:function(e){return e?"el-tooltip "+e.replace("el-tooltip",""):"el-tooltip"},handleShowPopper:function(){var e=this;this.expectedState&&!this.manual&&(clearTimeout(this.timeout),this.timeout=setTimeout((function(){e.showPopper=!0}),this.openDelay),this.hideAfter>0&&(this.timeoutPending=setTimeout((function(){e.showPopper=!1}),this.hideAfter)))},handleClosePopper:function(){this.enterable&&this.expectedState||this.manual||(clearTimeout(this.timeout),this.timeoutPending&&clearTimeout(this.timeoutPending),this.showPopper=!1,this.disabled&&this.doDestroy())},setExpectedState:function(e){!1===e&&clearTimeout(this.timeoutPending),this.expectedState=e},getFirstElement:function(){var e=this.$slots.default;if(!Array.isArray(e))return null;for(var t=null,n=0;nc){var p,h=u(arguments[c++]),v=f?i(h).concat(f(h)):i(h),m=v.length,y=0;while(m>y)p=v[y++],r&&!d.call(h,p)||(n[p]=h[p])}return n}:c},e17a:function(e,t){e.exports=!1},e28b:function(e,t,n){var r=n("9552"),o=n("ed35"),i=n("57c4"),a=i("iterator");e.exports=function(e){if(void 0!=e)return e[a]||e["@@iterator"]||o[r(e)]}},e3b4:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=118)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},118:function(e,t,n){"use strict";n.r(t);var r=function(e,t){var n=t._c;return n("div",t._g(t._b({class:[t.data.staticClass,"el-divider","el-divider--"+t.props.direction]},"div",t.data.attrs,!1),t.listeners),[t.slots().default&&"vertical"!==t.props.direction?n("div",{class:["el-divider__text","is-"+t.props.contentPosition]},[t._t("default")],2):t._e()])},o=[];r._withStripped=!0;var i={name:"ElDivider",props:{direction:{type:String,default:"horizontal",validator:function(e){return-1!==["horizontal","vertical"].indexOf(e)}},contentPosition:{type:String,default:"center",validator:function(e){return-1!==["left","center","right"].indexOf(e)}}}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!0,null,null,null);l.options.__file="packages/divider/src/main.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},e52f:function(e,t,n){var r=n("57c4"),o=r("iterator"),i=!1;try{var a=0,s={next:function(){return{done:!!a++}},return:function(){i=!0}};s[o]=function(){return this},Array.from(s,(function(){throw 2}))}catch(l){}e.exports=function(e,t){if(!t&&!i)return!1;var n=!1;try{var r={};r[o]=function(){return{next:function(){return{done:n=!0}}}},e(r)}catch(l){}return n}},e536:function(e,t,n){"use strict";t.__esModule=!0,t.default=function(){if(o.default.prototype.$isServer)return 0;if(void 0!==a)return a;var e=document.createElement("div");e.className="el-scrollbar__wrap",e.style.visibility="hidden",e.style.width="100px",e.style.position="absolute",e.style.top="-9999px",document.body.appendChild(e);var t=e.offsetWidth;e.style.overflow="scroll";var n=document.createElement("div");n.style.width="100%",e.appendChild(n);var r=n.offsetWidth;return e.parentNode.removeChild(e),a=t-r,a};var r=n("0261"),o=i(r);function i(e){return e&&e.__esModule?e:{default:e}}var a=void 0},e628:function(e,t,n){var r=n("df50"),o=n("65af"),i=n("1072"),a=n("ac83");e.exports=r("Reflect","ownKeys")||function(e){var t=o.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},e93b:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=68)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},15:function(e,t){e.exports=n("4eda")},2:function(e,t){e.exports=n("3c84")},41:function(e,t){e.exports=n("3cb2")},68:function(e,t,n){"use strict";n.r(t);var r=n(7),o=n.n(r),i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"el-loading-fade"},on:{"after-leave":e.handleAfterLeave}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],staticClass:"el-loading-mask",class:[e.customClass,{"is-fullscreen":e.fullscreen}],style:{backgroundColor:e.background||""}},[n("div",{staticClass:"el-loading-spinner"},[e.spinner?n("i",{class:e.spinner}):n("svg",{staticClass:"circular",attrs:{viewBox:"25 25 50 50"}},[n("circle",{staticClass:"path",attrs:{cx:"50",cy:"50",r:"20",fill:"none"}})]),e.text?n("p",{staticClass:"el-loading-text"},[e._v(e._s(e.text))]):e._e()])])])},a=[];i._withStripped=!0;var s={data:function(){return{text:null,spinner:null,background:null,fullscreen:!0,visible:!1,customClass:""}},methods:{handleAfterLeave:function(){this.$emit("after-leave")},setText:function(e){this.text=e}}},l=s,u=n(0),c=Object(u["a"])(l,i,a,!1,null,null,null);c.options.__file="packages/loading/src/loading.vue";var f=c.exports,d=n(2),p=n(15),h=n(41),v=n.n(h),m=o.a.extend(f),y={install:function(e){if(!e.prototype.$isServer){var t=function(t,r){r.value?e.nextTick((function(){r.modifiers.fullscreen?(t.originalPosition=Object(d["getStyle"])(document.body,"position"),t.originalOverflow=Object(d["getStyle"])(document.body,"overflow"),t.maskStyle.zIndex=p["PopupManager"].nextZIndex(),Object(d["addClass"])(t.mask,"is-fullscreen"),n(document.body,t,r)):(Object(d["removeClass"])(t.mask,"is-fullscreen"),r.modifiers.body?(t.originalPosition=Object(d["getStyle"])(document.body,"position"),["top","left"].forEach((function(e){var n="top"===e?"scrollTop":"scrollLeft";t.maskStyle[e]=t.getBoundingClientRect()[e]+document.body[n]+document.documentElement[n]-parseInt(Object(d["getStyle"])(document.body,"margin-"+e),10)+"px"})),["height","width"].forEach((function(e){t.maskStyle[e]=t.getBoundingClientRect()[e]+"px"})),n(document.body,t,r)):(t.originalPosition=Object(d["getStyle"])(t,"position"),n(t,t,r)))})):(v()(t.instance,(function(e){if(t.instance.hiding){t.domVisible=!1;var n=r.modifiers.fullscreen||r.modifiers.body?document.body:t;Object(d["removeClass"])(n,"el-loading-parent--relative"),Object(d["removeClass"])(n,"el-loading-parent--hidden"),t.instance.hiding=!1}}),300,!0),t.instance.visible=!1,t.instance.hiding=!0)},n=function(t,n,r){n.domVisible||"none"===Object(d["getStyle"])(n,"display")||"hidden"===Object(d["getStyle"])(n,"visibility")?n.domVisible&&!0===n.instance.hiding&&(n.instance.visible=!0,n.instance.hiding=!1):(Object.keys(n.maskStyle).forEach((function(e){n.mask.style[e]=n.maskStyle[e]})),"absolute"!==n.originalPosition&&"fixed"!==n.originalPosition&&Object(d["addClass"])(t,"el-loading-parent--relative"),r.modifiers.fullscreen&&r.modifiers.lock&&Object(d["addClass"])(t,"el-loading-parent--hidden"),n.domVisible=!0,t.appendChild(n.mask),e.nextTick((function(){n.instance.hiding?n.instance.$emit("after-leave"):n.instance.visible=!0})),n.domInserted=!0)};e.directive("loading",{bind:function(e,n,r){var o=e.getAttribute("element-loading-text"),i=e.getAttribute("element-loading-spinner"),a=e.getAttribute("element-loading-background"),s=e.getAttribute("element-loading-custom-class"),l=r.context,u=new m({el:document.createElement("div"),data:{text:l&&l[o]||o,spinner:l&&l[i]||i,background:l&&l[a]||a,customClass:l&&l[s]||s,fullscreen:!!n.modifiers.fullscreen}});e.instance=u,e.mask=u.$el,e.maskStyle={},n.value&&t(e,n)},update:function(e,n){e.instance.setText(e.getAttribute("element-loading-text")),n.oldValue!==n.value&&t(e,n)},unbind:function(e,n){e.domInserted&&(e.mask&&e.mask.parentNode&&e.mask.parentNode.removeChild(e.mask),t(e,{value:!1,modifiers:n.modifiers})),e.instance&&e.instance.$destroy()}})}}},g=y,b=n(9),_=n.n(b),x=o.a.extend(f),w={text:null,fullscreen:!0,body:!1,lock:!1,customClass:""},C=void 0;x.prototype.originalPosition="",x.prototype.originalOverflow="",x.prototype.close=function(){var e=this;this.fullscreen&&(C=void 0),v()(this,(function(t){var n=e.fullscreen||e.body?document.body:e.target;Object(d["removeClass"])(n,"el-loading-parent--relative"),Object(d["removeClass"])(n,"el-loading-parent--hidden"),e.$el&&e.$el.parentNode&&e.$el.parentNode.removeChild(e.$el),e.$destroy()}),300),this.visible=!1};var S=function(e,t,n){var r={};e.fullscreen?(n.originalPosition=Object(d["getStyle"])(document.body,"position"),n.originalOverflow=Object(d["getStyle"])(document.body,"overflow"),r.zIndex=p["PopupManager"].nextZIndex()):e.body?(n.originalPosition=Object(d["getStyle"])(document.body,"position"),["top","left"].forEach((function(t){var n="top"===t?"scrollTop":"scrollLeft";r[t]=e.target.getBoundingClientRect()[t]+document.body[n]+document.documentElement[n]+"px"})),["height","width"].forEach((function(t){r[t]=e.target.getBoundingClientRect()[t]+"px"}))):n.originalPosition=Object(d["getStyle"])(t,"position"),Object.keys(r).forEach((function(e){n.$el.style[e]=r[e]}))},O=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!o.a.prototype.$isServer){if(e=_()({},w,e),"string"===typeof e.target&&(e.target=document.querySelector(e.target)),e.target=e.target||document.body,e.target!==document.body?e.fullscreen=!1:e.body=!0,e.fullscreen&&C)return C;var t=e.body?document.body:e.target,n=new x({el:document.createElement("div"),data:e});return S(e,t,n),"absolute"!==n.originalPosition&&"fixed"!==n.originalPosition&&Object(d["addClass"])(t,"el-loading-parent--relative"),e.fullscreen&&e.lock&&Object(d["addClass"])(t,"el-loading-parent--hidden"),t.appendChild(n.$el),o.a.nextTick((function(){n.visible=!0})),e.fullscreen&&(C=n),n}},E=O;t["default"]={install:function(e){e.use(g),e.prototype.$loading=E},directive:g,service:E}},7:function(e,t){e.exports=n("0261")},9:function(e,t){e.exports=n("e93e")}})},e93e:function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e){for(var t=1,n=arguments.length;t0?r:n)(e)}},f28d:function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},f30e:function(e,t){e.exports=function(e){try{return!!e()}catch(t){return!0}}},f69c:function(e,t,n){var r=n("f28d"),o=n("e628"),i=n("4aef"),a=n("c223");e.exports=function(e,t){for(var n=o(t),s=a.f,l=i.f,u=0;u0&&!this.isAllSelected,value:this.isAllSelected},nativeOn:{click:this.toggleAllSelection}})},renderCell:function(e,t){var n=t.row,r=t.column,o=t.store,i=t.$index;return e("el-checkbox",{nativeOn:{click:function(e){return e.stopPropagation()}},attrs:{value:o.isSelected(n),disabled:!!r.selectable&&!r.selectable.call(null,n,i)},on:{input:function(){o.commit("rowSelectedChanged",n)}}})},sortable:!1,resizable:!1},index:{renderHeader:function(e,t){var n=t.column;return n.label||"#"},renderCell:function(e,t){var n=t.$index,r=t.column,o=n+1,i=r.index;return"number"===typeof i?o=n+i:"function"===typeof i&&(o=i(n)),e("div",[o])},sortable:!1},expand:{renderHeader:function(e,t){var n=t.column;return n.label||""},renderCell:function(e,t){var n=t.row,r=t.store,o=["el-table__expand-icon"];r.states.expandRows.indexOf(n)>-1&&o.push("el-table__expand-icon--expanded");var i=function(e){e.stopPropagation(),r.toggleRowExpansion(n)};return e("div",{class:o,on:{click:i}},[e("i",{class:"el-icon el-icon-arrow-right"})])},sortable:!1,resizable:!1,className:"el-table__expand-column"}};function a(e,t){var n=t.row,o=t.column,i=t.$index,a=o.property,s=a&&Object(r["getPropByPath"])(n,a).v;return o&&o.formatter?o.formatter(n,o,s,i):s}function s(e,t){var n=t.row,r=t.treeNode,o=t.store;if(!r)return null;var i=[],a=function(e){e.stopPropagation(),o.loadOrToggle(n)};if(r.indent&&i.push(e("span",{class:"el-table__indent",style:{"padding-left":r.indent+"px"}})),"boolean"!==typeof r.expanded||r.noLazyChildren)i.push(e("span",{class:"el-table__placeholder"}));else{var s=["el-table__expand-icon",r.expanded?"el-table__expand-icon--expanded":""],l=["el-icon-arrow-right"];r.loading&&(l=["el-icon-loading"]),i.push(e("div",{class:s,on:{click:a}},[e("i",{class:l})]))}return i}var l=n(8),u=n(18),c=n.n(u),f=Object.assign||function(e){for(var t=1;t-1}))}}},data:function(){return{isSubColumn:!1,columns:[]}},computed:{owner:function(){var e=this.$parent;while(e&&!e.tableId)e=e.$parent;return e},columnOrTableParent:function(){var e=this.$parent;while(e&&!e.tableId&&!e.columnId)e=e.$parent;return e},realWidth:function(){return Object(l["l"])(this.width)},realMinWidth:function(){return Object(l["k"])(this.minWidth)},realAlign:function(){return this.align?"is-"+this.align:null},realHeaderAlign:function(){return this.headerAlign?"is-"+this.headerAlign:this.realAlign}},methods:{getPropsData:function(){for(var e=this,t=arguments.length,n=Array(t),r=0;rt.key[n])return 1}return 0};return e.map((function(e,t){return{value:e,index:t,key:s?s(e,t):null}})).sort((function(e,t){var r=l(e,t);return r||(r=e.index-t.index),r*n})).map((function(e){return e.value}))},l=function(e,t){var n=null;return e.columns.forEach((function(e){e.id===t&&(n=e)})),n},u=function(e,t){for(var n=null,r=0;r2&&void 0!==arguments[2]?arguments[2]:"children",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"hasChildren",o=function(e){return!(Array.isArray(e)&&e.length)};function i(e,a,s){t(e,a,s),a.forEach((function(e){if(e[r])t(e,null,s+1);else{var a=e[n];o(a)||i(e,a,s+1)}}))}e.forEach((function(e){if(e[r])t(e,null,0);else{var a=e[n];o(a)||i(e,a,0)}}))}}})},fb8e:function(e,t,n){var r=n("ac83"),o=n("a9f2"),i=n("57c4"),a=i("species");e.exports=function(e,t){var n,i=r(e).constructor;return void 0===i||void 0==(n=r(i)[a])?t:o(n)}},fee7:function(e,t,n){var r=n("f30e"),o=n("67ea"),i="".split;e.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==o(e)?i.call(e,""):Object(e)}:Object}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/js/detail.043d6e39.js b/app/src/main/assets/web/js/detail.043d6e39.js deleted file mode 100644 index ad7b9cbb3..000000000 --- a/app/src/main/assets/web/js/detail.043d6e39.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["detail"],{"05b3":function(t,e,n){},"0827":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg=="},1240:function(t,e,n){},1544:function(t,e,n){var i=n("8c47"),o=n("65af").f,r={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(t){try{return o(t)}catch(e){return a.slice()}};t.exports.f=function(t){return a&&"[object Window]"==r.call(t)?s(t):o(i(t))}},1632:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXh7vLf7PDj8PTm8/ecW+lZAAACZElEQVQozw2RsU8TUQCHfz3fw7MS87jeI7DdmSMpDEoHE+P0HqGkvRR8vb5XC4NpN2RQZqcK9xJkwtriekcggerC4OZADDiT+A+goxv/gfwB3zd8H/T6vYF/pTZkCSmDNd3CBEtmZJP4N+CvvhecDvmntKsvwB17rpbIRTLOEoYkj9KZzRUuJsuBQFwgptyJ3Y7EL4V+ud5LO1UnMeQSSObqisiISZkbQBlliP3qWSk3GPQXjxv6VF2BTDO4ySx1zhuJXbA2wBNJF4t5vH9keg6wu5NvUpLtXrZ3OHC9ZsgVcZdOl38PM1y/L6m8GRiErj4AqezUjHGatGGIgs5NJDHh8Ua1IuB4035haVT6SaYWMoQ0eJ3rB/Gpnr3fB49YAy1Wa21YKqAHOmAveVw6CCMGMZh5bGtVI7jnZaiQNbta1Z+285oSoKoRbta1KZ/1bBdKH/RIxv2pRVpkoCmvpr097RWoo0CpMlTWllIenSjECU8mV43mHx2fIRfH/pncrJm3+58BWdbSqCS07/yiQnvHiCG4ZPGRFeAtfreoOubyctzHvLNHhjNvIhukxQzjU5O6QdOEzUp1Ef4d98Pxz+IPYX0bcpnT52dbedfz8y7C4R89RV+MjJkuCCx7mWDt4eyK/62lQB55xXGJK7p8u6bgRv4hVHylelYGGFs64W94tng8sAIVqSRJBpqRA9rFvAysS+9ak8s7557pz5HR4qhCRmWgplpTRJ+bhYfSAMO8/YBucWPuSdmFFtOnuWqvV2NbF6CJnbhNDzEZ/T0XSDrUydzkZCG1z/oIEyUFYxW/KPXNfwopuHDcO04UAAAAAElFTkSuQmCC"},"1a8c":function(t,e,n){"use strict";var i=n("91fe"),o=n("407d").map,r=n("b1a1"),a=n("6885"),s=r("map"),c=a("map");i({target:"Array",proto:!0,forced:!s||!c},{map:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}})},"1e75":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXM2t7O3ODQ3uLR4OTDp25yAAACdUlEQVQozw3P70sTcQDH8c/3/M7NG+j35mnHwjwh4hRy/QFK3zvPNbeIG1koPZmxfj2IDAwihL53zj0JYisfmEHcZJZOiBUG60lZiI8T/ANusuftgQ+kCPIPeMP7hS5mUrV9c1g6MQCAEZ8tDLHwofImAGRlX+SZK3Vu9rRRPuO4PK6/9nA4GIATsxlODS+rdCMhkAZivpYV0LWoQHSLSA4NfUg+6mY+7BKL2++F9LvnrBDYm6JO9i/YO3i/HJTGQ4pdIV82TbEDFG6vGYCd4wZchgK5J2CrKTLE+Tx0v+YGlIbdWJFcQl4ptBN8fUJQN1MCJLcZLYwUVVo+famGGty8EXJF5ofOEDzcodT3/Fb0I5sHmc1ZG7CcSl8COgxlXx09jT05OafjCZLIHJhGIaU6wDZHsuMQ41wbdjmQXbhKnMq1zlXSYrjCnyZblqexA7fC8RxS74tq2P3OxSQwTuJSApH8OZLzBBp1pOe0i3rdyDUA47GySZ31YmC4EQYSXvFSvieORGBxXF9aeVtUWKGS9WMC4Z9Y2uXnJ2nCUXVMbPOYqNYNmGWWQ7Evr+BWC+a0JAMTImcq/S4Z5INdQMeuOqDIMa9beilxfA60iC6sP1INcPDpmHBW8drZHNmqwyddJtVje9q8WGUgWAOzmbU4FCQBFi8B2Wk6pickBnYhJMenmJGuRmtt2IoKq9NuFGbNFR99sHnvrnLsLysKANDIsxbp6RNMAsoDSKuRpMwZbAAzI68QatIjmZ0aImyM3O8/4e2MNlOHZomFsa/fLDsysliHS+nlYLQJMnynxrH8QO4PaAV2Li8B/+52UgeGIVNFYf8B1XG/kFSmLcUAAAAASUVORK5CYII="},"21d4":function(t,e,n){"use strict";var i=n("0618"),o=n("dcb6"),r=RegExp.prototype.exec,a=String.prototype.replace,s=r,c=function(){var t=/a/,e=/b*/g;return r.call(t,"a"),r.call(e,"a"),0!==t.lastIndex||0!==e.lastIndex}(),l=o.UNSUPPORTED_Y||o.BROKEN_CARET,u=void 0!==/()??/.exec("")[1],A=c||u||l;A&&(s=function(t){var e,n,o,s,A=this,f=l&&A.sticky,g=i.call(A),d=A.source,h=0,p=t;return f&&(g=g.replace("y",""),-1===g.indexOf("g")&&(g+="g"),p=String(t).slice(A.lastIndex),A.lastIndex>0&&(!A.multiline||A.multiline&&"\n"!==t[A.lastIndex-1])&&(d="(?: "+d+")",p=" "+p,h++),n=new RegExp("^(?:"+d+")",g)),u&&(n=new RegExp("^"+d+"$(?!\\s)",g)),c&&(e=A.lastIndex),o=r.call(f?n:A,p),f?o?(o.input=o.input.slice(h),o[0]=o[0].slice(h),o.index=A.lastIndex,A.lastIndex+=o[0].length):A.lastIndex=0:c&&o&&(A.lastIndex=A.global?o.index+o[0].length:e),u&&o&&o.length>1&&a.call(o[0],n,(function(){for(s=1;s=l?t?"":void 0:(r=s.charCodeAt(c),r<55296||r>56319||c+1===l||(a=s.charCodeAt(c+1))<56320||a>57343?t?s.charAt(c):r:t?s.slice(c,c+2):a-56320+(r-55296<<10)+65536)}};t.exports={codeAt:r(!1),charAt:r(!0)}},"356c":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXPz8/R0dHT09PU1NToNyAhAAACdElEQVQozw3NP0xTQQDH8d9d7sFrG+QeKVgQ4aoFCwFkYERyLY//0UB8GNGg1WAC0RBGJrzW4mCXQmpgvCYOwEAYiulSpYtza2KiW7s5FgNJFSV2/CzfL7RwpoJ20iadmgA8owOyaxmusKE44scBeb4vIv00dqYgmf6jzWcr7W6INbDQeZbQL9ytXeYgtFfzmW1Fek5msxJlwhyt6qDDxOLQzpVPompYrMPnEnhvLm7M5BxY5nowAj3zkydAkpC0FIG6g7AK+Ub25ybyNWVYwtpseP2rfrQwiGRpfqrnMuPeuvr2dA0p2YsHF2XghkrXKtZ8tLBjR7S2qIaYbKmyLd/QP+EogLjqqwNw5Lq1pDlMLkM5+gNoSvdq+Pxmz9/61EFq6GYM6GqaGvlN95zy3gsmEWI8K3k8OP9OmRLEPO6DP3Wv3g42COinJTZ33dcIvs4ESp6opMTjDs6mcYTEbFeUifuxh989yZrIx4lkpuixxz0nHLCekKbE17suKhYkMGhoYhTZtVBvg4bfq/1L1Im0AGMVpBFwumM0zwyuKiCMi5dqR4Flx47AGyF2xTbxqUdTwCH94BT3DozpLV5WuAL/N8rGtHKjotBOOuOtCJ9E21uqsyBoLOzaXbHPrK5PQBP+fBfeidvJAeMIAmzVt5IkJJ9DBWaZDAepYUhlQqHt0h72SJ3j8TZHom64f516xx9T5evgMPgwG82jZdJaJIDyWp6LAjOCclVyzNA3iTKzIULlBQEPaTXlPHok5gISclmyaWZlqY2aTHdRHpJOwTdDEQ3ZfKtbpclcNhyVClagmY+fIfyKukntPqBgnx5QvZHk/D/MK8JMClrSigAAAABJRU5ErkJggg=="},3874:function(t,e,n){},"3e5e":function(t,e,n){"use strict";var i=n("deaa"),o=n("e1dd"),r=n("ac83"),a=n("3193"),s=n("fb8e"),c=n("536c"),l=n("684e"),u=n("81a0"),A=n("21d4"),f=n("f30e"),g=[].push,d=Math.min,h=4294967295,p=!f((function(){return!RegExp(h,"y")}));i("split",2,(function(t,e,n){var i;return i="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var i=String(a(this)),r=void 0===n?h:n>>>0;if(0===r)return[];if(void 0===t)return[i];if(!o(t))return e.call(i,t,r);var s,c,l,u=[],f=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),d=0,p=new RegExp(t.source,f+"g");while(s=A.call(p,i)){if(c=p.lastIndex,c>d&&(u.push(i.slice(d,s.index)),s.length>1&&s.index=r))break;p.lastIndex===s.index&&p.lastIndex++}return d===i.length?!l&&p.test("")||u.push(""):u.push(i.slice(d)),u.length>r?u.slice(0,r):u}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:e.call(this,t,n)}:e,[function(e,n){var o=a(this),r=void 0==e?void 0:e[t];return void 0!==r?r.call(e,o,n):i.call(String(o),e,n)},function(t,o){var a=n(i,t,this,o,i!==e);if(a.done)return a.value;var A=r(t),f=String(this),g=s(A,RegExp),m=A.unicode,v=(A.ignoreCase?"i":"")+(A.multiline?"m":"")+(A.unicode?"u":"")+(p?"y":"g"),C=new g(p?A:"^(?:"+A.source+")",v),b=void 0===o?h:o>>>0;if(0===b)return[];if(0===f.length)return null===u(C,f)?[f]:[];var S=0,y=0,x=[];while(y1?arguments[1]:void 0)}})},"407d":function(t,e,n){var i=n("0b29"),o=n("fee7"),r=n("ee6f"),a=n("684e"),s=n("3132"),c=[].push,l=function(t){var e=1==t,n=2==t,l=3==t,u=4==t,A=6==t,f=5==t||A;return function(g,d,h,p){for(var m,v,C=r(g),b=o(C),S=i(d,h,3),y=a(b.length),x=0,B=p||s,I=e?B(g,y):n?B(g,0):void 0;y>x;x++)if((f||x in b)&&(m=b[x],v=S(m,x,C),t))if(e)I[x]=v;else if(v)switch(t){case 3:return!0;case 5:return m;case 6:return x;case 2:c.call(I,m)}else if(u)return!1;return A?-1:l||u?u:I}};t.exports={forEach:l(0),map:l(1),filter:l(2),some:l(3),every:l(4),find:l(5),findIndex:l(6)}},4178:function(t,e,n){"use strict";var i=n("91fe"),o=n("d5dc"),r=n("df50"),a=n("e17a"),s=n("7a23"),c=n("4ccd"),l=n("4445"),u=n("f30e"),A=n("f28d"),f=n("a8c9"),g=n("d68d"),d=n("ac83"),h=n("ee6f"),p=n("8c47"),m=n("7dc7"),v=n("aec8"),C=n("641d"),b=n("16e5"),S=n("65af"),y=n("1544"),x=n("1072"),B=n("4aef"),I=n("c223"),E=n("354c"),k=n("2ba5"),w=n("3d8a"),O=n("f880"),U=n("4d52"),D=n("4888"),Q=n("9db6"),V=n("57c4"),R=n("7287"),P=n("c0aa"),M=n("94d7"),F=n("d0e2"),N=n("407d").forEach,L=U("hidden"),K="Symbol",T="prototype",H=V("toPrimitive"),J=F.set,z=F.getterFor(K),W=Object[T],G=o.Symbol,q=r("JSON","stringify"),Y=B.f,Z=I.f,j=y.f,X=E.f,$=O("symbols"),_=O("op-symbols"),tt=O("string-to-symbol-registry"),et=O("symbol-to-string-registry"),nt=O("wks"),it=o.QObject,ot=!it||!it[T]||!it[T].findChild,rt=s&&u((function(){return 7!=C(Z({},"a",{get:function(){return Z(this,"a",{value:7}).a}})).a}))?function(t,e,n){var i=Y(W,e);i&&delete W[e],Z(t,e,n),i&&t!==W&&Z(W,e,i)}:Z,at=function(t,e){var n=$[t]=C(G[T]);return J(n,{type:K,tag:t,description:e}),s||(n.description=e),n},st=l?function(t){return"symbol"==typeof t}:function(t){return Object(t)instanceof G},ct=function(t,e,n){t===W&&ct(_,e,n),d(t);var i=m(e,!0);return d(n),A($,i)?(n.enumerable?(A(t,L)&&t[L][i]&&(t[L][i]=!1),n=C(n,{enumerable:v(0,!1)})):(A(t,L)||Z(t,L,v(1,{})),t[L][i]=!0),rt(t,i,n)):Z(t,i,n)},lt=function(t,e){d(t);var n=p(e),i=b(n).concat(dt(n));return N(i,(function(e){s&&!At.call(n,e)||ct(t,e,n[e])})),t},ut=function(t,e){return void 0===e?C(t):lt(C(t),e)},At=function(t){var e=m(t,!0),n=X.call(this,e);return!(this===W&&A($,e)&&!A(_,e))&&(!(n||!A(this,e)||!A($,e)||A(this,L)&&this[L][e])||n)},ft=function(t,e){var n=p(t),i=m(e,!0);if(n!==W||!A($,i)||A(_,i)){var o=Y(n,i);return!o||!A($,i)||A(n,L)&&n[L][i]||(o.enumerable=!0),o}},gt=function(t){var e=j(p(t)),n=[];return N(e,(function(t){A($,t)||A(D,t)||n.push(t)})),n},dt=function(t){var e=t===W,n=j(e?_:p(t)),i=[];return N(n,(function(t){!A($,t)||e&&!A(W,t)||i.push($[t])})),i};if(c||(G=function(){if(this instanceof G)throw TypeError("Symbol is not a constructor");var t=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,e=Q(t),n=function(t){this===W&&n.call(_,t),A(this,L)&&A(this[L],e)&&(this[L][e]=!1),rt(this,e,v(1,t))};return s&&ot&&rt(W,e,{configurable:!0,set:n}),at(e,t)},w(G[T],"toString",(function(){return z(this).tag})),w(G,"withoutSetter",(function(t){return at(Q(t),t)})),E.f=At,I.f=ct,B.f=ft,S.f=y.f=gt,x.f=dt,R.f=function(t){return at(V(t),t)},s&&(Z(G[T],"description",{configurable:!0,get:function(){return z(this).description}}),a||w(W,"propertyIsEnumerable",At,{unsafe:!0}))),i({global:!0,wrap:!0,forced:!c,sham:!c},{Symbol:G}),N(b(nt),(function(t){P(t)})),i({target:K,stat:!0,forced:!c},{for:function(t){var e=String(t);if(A(tt,e))return tt[e];var n=G(e);return tt[e]=n,et[n]=e,n},keyFor:function(t){if(!st(t))throw TypeError(t+" is not a symbol");if(A(et,t))return et[t]},useSetter:function(){ot=!0},useSimple:function(){ot=!1}}),i({target:"Object",stat:!0,forced:!c,sham:!s},{create:ut,defineProperty:ct,defineProperties:lt,getOwnPropertyDescriptor:ft}),i({target:"Object",stat:!0,forced:!c},{getOwnPropertyNames:gt,getOwnPropertySymbols:dt}),i({target:"Object",stat:!0,forced:u((function(){x.f(1)}))},{getOwnPropertySymbols:function(t){return x.f(h(t))}}),q){var ht=!c||u((function(){var t=G();return"[null]"!=q([t])||"{}"!=q({a:t})||"{}"!=q(Object(t))}));i({target:"JSON",stat:!0,forced:ht},{stringify:function(t,e,n){var i,o=[t],r=1;while(arguments.length>r)o.push(arguments[r++]);if(i=e,(g(e)||void 0!==t)&&!st(t))return f(e)||(e=function(t,e){if("function"==typeof i&&(e=i.call(this,t,e)),!st(e))return e}),o[1]=e,q.apply(null,o)}})}G[T][H]||k(G[T],H,G[T].valueOf),M(G,K),D[L]=!0},"41f6":function(t,e){t.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},4400:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAElBMVEUQERMODxESFBYWGBkaHB0eICLm6ozJAAACkUlEQVQ4yyWTUdLbMAiEASfvoOkBkBy/O5keIE0v8E/uf5h+68qZWALELgu2MG9PP9qyvCzTVhrrsPGOCjvTfXQZvtp/W3Gy6LCITqs4q/DZ+KYl76zKzHVYpY2wNY27nqN1sbLGcrLH3/ENH4oWlGctsDu8AO+HzTLlsYdh8MzP1m6YDMz0ACfcimvakBj+mwO/+5Uta5teOD379sxK1fUxmUhv8MU3jUT5gs26PMephFznkLcpQZ6/dPL9C/GWHcCxDN6oZhD5xBm5qoYBPA+PFE/H1tXDWcWl8TW7rS+4dUzAVy0BIrvC4/HcqW2TkG1HO8q9dC23INAg7NA4AFRFkDTM2lfELPyFzi1VddcpX2z0KjHBUDmdLNJ6dDps4ytrX+FPsZwE31wSL+6OWfHOAJ3+Y0Rk/MiKfmWNPg7oVP/U3Ck9FoCkC2gBpALOiqbMNTkOe8P4FWkTD2Y9Q3+5VmV0uLUJBl68U5uAK2Kl6QDXvLxbwweOL2sixW78uU8p0ysfc7cWrF1j6B1sPJ4WgclYSnJN1bzozrhEcFHmRzBkbJWqqdG+EYJXRFmT5jnLXPUNF6WBdoFbTxYsmDXVLU/WA7MExNc93sJS5hIXDeLxzMScHzdhKvEkibr6cQXYPrmtmTA7JcInISrTzRDvShTdka0uVGrsJAAR6tSn1sKziZtfKVjAxPrJsYgZO0bye+vKTZ/DgoAoLGNO6jYHimZYTL/3pLJHawquJukjBpfz8WOGVSVIWx9ywUfS5iENutidRM4NzkAmxgUSQ68xgNOU+ZLalr4TS2V+D2xqukZig+Z9DilR7Nouzwp1cp/3E5q6Rdlf08obKvAM4qZ6pMr+w3PmQALSSBfjyZn5DwrNRVbywBQiAAAAAElFTkSuQmCC"},"477e":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAACVBMVEX28ef48+n69esoK7jYAAAB4UlEQVQozw2OsW4bQQxEhwLXkDrysGdEqRRgVShfQQq8wOr2jD0jSpXCLvwXbtKfADlFqgSwC/9ljqweZgYzQFnb/QGepYhA9jzmTc1WaSEtQpbFgjWATI00ZZtIckXx8q2Oe5yEByBy+RHOTcM+VVTadULsvxvRC/q8WTwgcWGD+Mnaqa0oy2gw2pKFzK+PzEsus5hP9AHojKslVynLlioVTBEN8cjDNnZoR1uMGTiZAAN47HxMtEkGUE9b8HWzkqNX5Lpk0yVziAJOs46rK1pG/xNuXLjz95fSDoJE5IqG23MAYPtWoeWPvfVtIV/Ng9oH3W0gGMPIOqd4MK4QZ55dV61gOb8Zxp7I9qayaGxp6Q91cmC0ZRdBwEQVHWzSAanlZwVWc9yljeTCeaHjBVvlPSLeyeBUT2rPdJegQI103jVS3uYkyIx1il6mslMDedZuOkwzolsagvPuQAfp7cYg7k9V1NOxfq64PNSvMdwONV4VYEmqlbpZy5OAakRKkjPnL4CBv5/OZRgoWHBmNbxB0LgB1I4vXFj93UoF2/0TPEsWwV9EhbIiTPqYoTHYoMn3enTDjmrFeDTIzaL1bUC/PBIMuF+vSSYSaxoVt90EO3Gu1zrMuMRGUk7Ffv3L+A931Gsb/yBoIgAAAABJRU5ErkJggg=="},"536c":function(t,e,n){"use strict";var i=n("3303").charAt;t.exports=function(t,e,n){return e+(n?i(t,e).length:1)}},"537b":function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"chapter-wrapper",class:{night:t.isNight,day:!t.isNight},style:t.bodyTheme},[n("div",{staticClass:"tool-bar",style:t.leftBarTheme},[n("div",{staticClass:"tools"},[n("el-popover",{attrs:{placement:"right",width:t.cataWidth,trigger:"click","visible-arrow":!1,"popper-class":"pop-cata"},model:{value:t.popCataVisible,callback:function(e){t.popCataVisible=e},expression:"popCataVisible"}},[n("PopCata",{ref:"popCata",staticClass:"popup",on:{getContent:t.getContent}}),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},attrs:{slot:"reference"},slot:"reference"},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("目录")])])],1),n("el-popover",{attrs:{placement:"bottom-right",width:"470",trigger:"click","visible-arrow":!1,"popper-class":"pop-setting"},model:{value:t.readSettingsVisible,callback:function(e){t.readSettingsVisible=e},expression:"readSettingsVisible"}},[n("ReadSettings",{staticClass:"popup"}),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},attrs:{slot:"reference"},slot:"reference"},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("设置")])])],1),n("div",{staticClass:"tool-icon",on:{click:t.toShelf}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("书架")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toTop}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("顶部")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toBottom}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("底部")])])],1)]),n("div",{staticClass:"read-bar",style:t.rightBarTheme},[n("div",{staticClass:"tools"},[n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toLastChapter}},[n("div",{staticClass:"iconfont"},[t._v("  ")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toNextChapter}},[n("div",{staticClass:"iconfont"},[t._v("  ")])])])]),n("div",{staticClass:"chapter-bar"}),n("div",{ref:"content",staticClass:"chapter",style:t.chapterTheme},[n("div",{staticClass:"content"},[n("div",{ref:"top",staticClass:"top-bar"}),t.show?n("div",{ref:"title",staticClass:"title"},[t._v(t._s(t.title))]):t._e(),n("Pcontent",{attrs:{carray:t.content}}),n("div",{ref:"bottom",staticClass:"bottom-bar"})],1)])])},o=[],r=(n("b3f9"),n("3e5e"),function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"cata-wrapper",style:t.popupTheme},[n("div",{staticClass:"title"},[t._v(" 目录 ")]),n("div",{ref:"cataData",staticClass:"data-wrapper",class:{night:t.isNight,day:!t.isNight}},[n("div",{staticClass:"cata"},t._l(t.catalog,(function(e,i){return n("div",{key:e.durChapterIndex,ref:"cata",refInFor:!0,staticClass:"log",class:{selected:t.isSelected(i)},on:{click:function(n){return t.gotoChapter(e)}}},[n("div",{staticClass:"log-text"},[t._v(" "+t._s(e.title)+" ")])])})),0)])])}),a=[];n("4045"),n("4178"),n("fc88"),n("e350"),n("d9a3"),n("3a20"),n("ef8e"),n("252a");function s(t){return s="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},s(t)}var c,l,u=function(t,e,n,i){return t/=i/2,t<1?n/2*t*t+e:(t--,-n/2*(t*(t-2)-1)+e)},A=function(){var t,e,n,i,o,r,a,c,l,A,f,g,d;function h(){var e=t.scrollTop||t.scrollY||t.pageYOffset;return e="undefined"===typeof e?0:e,e}function p(e){var i=e.getBoundingClientRect().top,o=t.getBoundingClientRect?t.getBoundingClientRect().top:0;return i-o+n}function m(e){t.scrollTo?t.scrollTo(0,e):t.scrollTop=e}function v(t){A||(A=t),f=t-A,g=r(f,n,c,l),m(g),f1&&void 0!==arguments[1]?arguments[1]:{};switch(l=f.duration||1e3,o=f.offset||0,d=f.callback,r=f.easing||u,a=f.a11y||!1,s(f.container)){case"object":t=f.container;break;case"string":t=document.querySelector(f.container);break;default:t=window}switch(n=h(),s(A)){case"number":e=void 0,a=!1,i=n+A;break;case"object":e=A,i=p(e);break;case"string":e=document.querySelector(A),i=p(e);break}switch(c=i-n+o,s(f.duration)){case"number":l=f.duration;break;case"function":l=f.duration(c);break}requestAnimationFrame(v)}return b},f=A(),g=f,d=n("7286"),h=n.n(d),p=n("477e"),m=n.n(p),v=n("e160"),C=n.n(v),b=n("df5e"),S=n.n(b),y=n("ec0f"),x=n.n(y),B=n("b671"),I=n.n(B),E=n("5629"),k=n.n(E),w=n("d0e3"),O=n.n(w),U=n("4039"),D=n.n(U),Q=n("1e75"),V=n.n(Q),R=n("1632"),P=n.n(R),M=n("7abd"),F=n.n(M),N=n("356c"),L=n.n(N),K=n("b165"),T=n.n(K),H=n("cf68"),J=n.n(H),z=n("4400"),W=n.n(z),G=n("802e"),q=n.n(G),Y=n("0827"),Z=n.n(Y),j={themes:[{body:"#ede7da url("+h.a+") repeat",content:"#ede7da url("+m.a+") repeat",popup:"#ede7da url("+C.a+") repeat"},{body:"#ede7da url("+S.a+") repeat",content:"#ede7da url("+x.a+") repeat",popup:"#ede7da url("+I.a+") repeat"},{body:"#ede7da url("+k.a+") repeat",content:"#ede7da url("+O.a+") repeat",popup:"#ede7da url("+D.a+") repeat"},{body:"#ede7da url("+V.a+") repeat",content:"#ede7da url("+P.a+") repeat",popup:"#ede7da url("+F.a+") repeat"},{body:"#ebcece repeat",content:"#f5e4e4 repeat",popup:"#faeceb repeat"},{body:"#ede7da url("+L.a+") repeat",content:"#ede7da url("+T.a+") repeat",popup:"#ede7da url("+J.a+") repeat"},{body:"#ede7da url("+W.a+") repeat",content:"#ede7da url("+q.a+") repeat",popup:"#ede7da url("+Z.a+") repeat"}],fonts:[{fontFamily:"Microsoft YaHei, PingFangSC-Regular, HelveticaNeue-Light, Helvetica Neue Light, sans-serif"},{fontFamily:"PingFangSC-Regular, -apple-system, Simsun"},{fontFamily:"Kaiti"}]},X=j,$=(n("05b3"),{name:"PopCata",data:function(){return{isNight:6==this.$store.state.config.theme,index:this.$store.state.readingBook.index}},computed:{catalog:function(){return this.$store.state.readingBook.catalog},popCataVisible:function(){return this.$store.state.popCataVisible},theme:function(){return this.$store.state.config.theme},popupTheme:function(){return{background:X.themes[this.theme].popup}}},mounted:function(){},watch:{theme:function(t){this.isNight=6==t},popCataVisible:function(){this.$nextTick((function(){var t=this.$store.state.readingBook.index,e=this.$refs.cataData;g(this.$refs.cata[t],{container:e,duration:0})}))}},methods:{isSelected:function(t){return t==this.$store.state.readingBook.index},gotoChapter:function(t){this.index=this.catalog.indexOf(t),this.$store.commit("setPopCataVisible",!1),this.$store.commit("setContentLoading",!0),this.$emit("getContent",this.index)}}}),_=$,tt=(n("7f9c"),n("623f")),et=Object(tt["a"])(_,r,a,!1,null,"8c647fa4",null),nt=et.exports,it=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"settings-wrapper",class:{night:t.isNight,day:!t.isNight},style:t.popupTheme},[n("div",{staticClass:"settings-title"},[t._v("设置")]),n("div",{staticClass:"setting-list"},[n("ul",[n("li",{staticClass:"theme-list"},[n("i",[t._v("阅读主题")]),t._l(t.themeColors,(function(e,i){return n("span",{key:i,ref:"themes",refInFor:!0,staticClass:"theme-item",class:{selected:t.selectedTheme==i},style:e,on:{click:function(e){return t.setTheme(i)}}},[i<6?n("em",{staticClass:"iconfont"},[t._v("")]):n("em",{staticClass:"moon-icon"},[t._v(t._s(t.moonIcon))])])}))],2),n("li",{staticClass:"font-list"},[n("i",[t._v("正文字体")]),t._l(t.fonts,(function(e,i){return n("span",{key:i,staticClass:"font-item",class:{selected:t.selectedFont==i},on:{click:function(e){return t.setFont(i)}}},[t._v(t._s(e))])}))],2),n("li",{staticClass:"font-size"},[n("i",[t._v("字体大小")]),n("div",{staticClass:"resize"},[n("span",{staticClass:"less",on:{click:t.lessFontSize}},[n("em",{staticClass:"iconfont"},[t._v("")])]),n("b"),t._v(" "),n("span",{staticClass:"lang"},[t._v(t._s(t.fontSize))]),n("b"),n("span",{staticClass:"more",on:{click:t.moreFontSize}},[n("em",{staticClass:"iconfont"},[t._v("")])])])]),n("li",{staticClass:"read-width"},[n("i",[t._v("页面宽度")]),n("div",{staticClass:"resize"},[n("span",{staticClass:"less",on:{click:t.lessReadWidth}},[n("em",{staticClass:"iconfont"},[t._v("")])]),n("b"),t._v(" "),n("span",{staticClass:"lang"},[t._v(t._s(t.readWidth))]),n("b"),n("span",{staticClass:"more",on:{click:t.moreReadWidth}},[n("em",{staticClass:"iconfont"},[t._v("")])])])])])])])},ot=[],rt=(n("82da"),{name:"ReadSettings",data:function(){return{theme:0,isNight:6==this.$store.state.config.theme,moonIcon:"",themeColors:[{background:"rgba(250, 245, 235, 0.8)"},{background:"rgba(245, 234, 204, 0.8)"},{background:"rgba(230, 242, 230, 0.8)"},{background:"rgba(228, 241, 245, 0.8)"},{background:"rgba(245, 228, 228, 0.8)"},{background:"rgba(224, 224, 224, 0.8)"},{background:"rgba(0, 0, 0, 0.5)"}],moonIconStyle:{display:"inline",color:"rgba(255,255,255,0.2)"},fonts:["雅黑","宋体","楷书"]}},mounted:function(){var t=this.$store.state.config;this.theme=t.theme,6==this.theme?this.moonIcon="":this.moonIcon=""},computed:{config:function(){return this.$store.state.config},popupTheme:function(){return{background:X.themes[this.config.theme].popup}},selectedTheme:function(){return this.$store.state.config.theme},selectedFont:function(){return this.$store.state.config.font},fontSize:function(){return this.$store.state.config.fontSize},readWidth:function(){return this.$store.state.config.readWidth}},methods:{setTheme:function(t){6==t?(this.isNight=!0,this.moonIcon="",this.moonIconStyle.color="#ed4259"):(this.isNight=!1,this.moonIcon="",this.moonIconStyle.color="rgba(255,255,255,0.2)");var e=this.config;e.theme=t,this.$store.commit("setConfig",e)},setFont:function(t){var e=this.config;e.font=t,this.$store.commit("setConfig",e)},moreFontSize:function(){var t=this.config;t.fontSize<48&&(t.fontSize+=2),this.$store.commit("setConfig",t)},lessFontSize:function(){var t=this.config;t.fontSize>12&&(t.fontSize-=2),this.$store.commit("setConfig",t)},moreReadWidth:function(){var t=this.config;t.readWidth<960&&(t.readWidth+=160),this.$store.commit("setConfig",t)},lessReadWidth:function(){var t=this.config;t.readWidth>640&&(t.readWidth-=160),this.$store.commit("setConfig",t)}}}),at=rt,st=(n("8157"),Object(tt["a"])(at,it,ot,!1,null,"3d7e2fe5",null)),ct=st.exports,lt=(n("1a8c"),{name:"pcontent",data:function(){return{}},props:["carray"],render:function(){var t=arguments[0],e=this.fontFamily,n=this.fontSize,i=e;return i.fontSize=n,this.show?t("div",[this.carray.map((function(e){return t("p",{style:i},[e])}))]):t("div")},computed:{show:function(){return this.$store.state.showContent},fontFamily:function(){return X.fonts[this.$store.state.config.font]},fontSize:function(){return this.$store.state.config.fontSize+"px"}},watch:{fontSize:function(){var t=this;t.$store.commit("setShowContent",!1),this.$nextTick((function(){t.$store.commit("setShowContent",!0)}))}}}),ut=lt,At=(n("a9ea"),Object(tt["a"])(ut,c,l,!1,null,"6ee085ae",null)),ft=At.exports,gt=n("82ae"),dt=n.n(gt),ht={components:{PopCata:nt,Pcontent:ft,ReadSettings:ct},created:function(){var t=JSON.parse(localStorage.getItem("config"));null!=t&&this.$store.commit("setConfig",t)},beforeCreate:function(){var t=JSON.parse(localStorage.getItem("config"));null!=t&&this.$store.commit("setConfig",t)},mounted:function(){var t=this;this.loading=this.$loading({target:this.$refs.content,lock:!0,text:"正在获取内容",spinner:"el-icon-loading",background:"rgba(0,0,0,0)"});var e=this,n=sessionStorage.getItem("bookUrl"),i=sessionStorage.getItem("bookName"),o=JSON.parse(localStorage.getItem(n));null==o&&(o={bookName:i,bookUrl:n,index:0},localStorage.setItem(n,JSON.stringify(o))),this.getCatalog(n).then((function(n){var i=n.data.data;o.catalog=i,e.$store.commit("setReadingBook",o);var r=e.$store.state.readingBook.index||0;t.getContent(r)}),(function(t){throw e.loading.close(),e.$message.error("获取书籍目录失败"),t}))},watch:{chapterName:function(t){this.title=t},content:function(){this.$store.commit("setContentLoading",!1)},theme:function(t){this.isNight=6==t},bodyColor:function(t){this.bodyTheme.background=t},chapterColor:function(t){this.chapterTheme.background=t},readWidth:function(t){this.chapterTheme.width=t;var e=-((parseInt(t)+130)/2+68)+"px",n=-((parseInt(t)+130)/2+52)+"px";this.leftBarTheme.marginLeft=e,this.rightBarTheme.marginRight=n},popupColor:function(t){this.leftBarTheme.background=t,this.rightBarTheme.background=t},readSettingsVisible:function(t){if(!t){var e=JSON.stringify(this.$store.state.config);localStorage.setItem("config",e)}}},data:function(){return{title:"",content:[],noPoint:!0,isNight:6==this.$store.state.config.theme,bodyTheme:{background:X.themes[this.$store.state.config.theme].body},chapterTheme:{background:X.themes[this.$store.state.config.theme].content,width:this.$store.state.config.readWidth-130+"px"},leftBarTheme:{background:X.themes[this.$store.state.config.theme].popup,marginLeft:-(this.$store.state.config.readWidth/2+68)+"px"},rightBarTheme:{background:X.themes[this.$store.state.config.theme].popup,marginRight:-(this.$store.state.config.readWidth/2+52)+"px"}}},computed:{catalog:function(){return this.$store.state.catalog},windowHeight:function(){return window.innerHeight},contentHeight:function(){return this.$refs.content.offsetHeight},popCataVisible:{get:function(){return this.$store.state.popCataVisible},set:function(t){this.$store.commit("setPopCataVisible",t)}},readSettingsVisible:{get:function(){return this.$store.state.readSettingsVisible},set:function(t){this.$store.commit("setReadSettingsVisible",t)}},config:function(){return this.$store.state.config},theme:function(){return this.config.theme},bodyColor:function(){return X.themes[this.config.theme].body},chapterColor:function(){return X.themes[this.config.theme].content},popupColor:function(){return X.themes[this.config.theme].popup},readWidth:function(){return this.$store.state.config.readWidth-130+"px"},cataWidth:function(){return this.$store.state.config.readWidth-33},show:function(){return this.$store.state.showContent}},methods:{getCatalog:function(t){return dt.a.get("/getChapterList?url="+encodeURIComponent(t))},getContent:function(t){var e=this;this.$store.commit("setShowContent",!1),this.loading.visible||(this.loading=this.$loading({target:this.$refs.content,lock:!0,text:"正在获取内容",spinner:"el-icon-loading",background:"rgba(0,0,0,0)"}));var n=sessionStorage.getItem("bookUrl"),i=JSON.parse(localStorage.getItem(n));i.index=t,localStorage.setItem(n,JSON.stringify(i)),this.$store.state.readingBook.index=t;var o=this.$store.state.readingBook.catalog[t].title,r=this.$store.state.readingBook.catalog[t].index;this.title=o,g(this.$refs.top,{duration:0});var a=this;dt.a.get("/getBookContent?url="+encodeURIComponent(n)+"&index="+r).then((function(t){var n=t.data.data,i=n.split("\n\n"),o="";o=i.length>1?i[1].split("\n"):i[0].split("\n"),a.content=o,e.$store.commit("setContentLoading",!0),a.loading.close(),a.noPoint=!1,a.$store.commit("setShowContent",!0)}),(function(t){throw a.$message.error("获取章节内容失败"),a.content="  获取章节内容失败!",t}))},toTop:function(){g(this.$refs.top)},toBottom:function(){g(this.$refs.bottom)},toNextChapter:function(){this.$store.commit("setContentLoading",!0);var t=this.$store.state.readingBook.index;t++,this.getContent(t)},toLastChapter:function(){this.$store.commit("setContentLoading",!0);var t=this.$store.state.readingBook.index;t--,this.getContent(t)},toShelf:function(){this.$router.push("/")}}},pt=ht,mt=(n("88a3"),Object(tt["a"])(pt,i,o,!1,null,"08ce53b2",null));e["default"]=mt.exports},5629:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXN383Q4tDP4c/R5NEInCCXAAACVElEQVQozw3Hv2sTYRwH4M/79pJ7bZL2bXqtERJ97zjUpbZDhg6pfC8qibi8hLR0EaJ0EFxaCSWDxjfpj1zrYBcRBKE6SAfBJWsx9i8IQfdQxDlKtA6t2OnhQfN3lbG7ytYRywF8rVoPCNO0X2sQOKDpAnSDK2VwkHgmh5yLGT8qASt+2KofnNt2Xg1gf1UF8AoM6052cRMNaloLZb7RKQGrKKji2OefsZF+VqIvos5ZLVIZCX61JcwUdk56wASVkgQvzPfvmT2twTSwyYaC/Pl/UhAHorFhBgZtL6XdAZRp1tkPwC1NLa9CWs5prLhI85NBQsLdXvjDymG3/EbYfQhVNYqc3TtktQhWLY3ko0QsdMbSEp+64v0NfxyqLbIGdh6M2xHHlLBGqKTyQo4E/nebBgBfe1GpdeywYXc8CT7D3cKXuMXkBy4xN6o5OuKamYp3DVI6uccO9lxgd2CAlJgI2BGgaAgIJV/TYwKqu3WFccjbMuA+bVkWgS2bfnlRbD1Eb1sDyWMmjKYIBgGAWbqKRicfvzBkBIz3V5AKnguWdglQEysQsSuVzOg6ALy1pitA5ykGCsc857BRYcgCSZyFOdvoOigSGoPc5Ta73mgxshIcQE5sHMHd9D7yqITw7JO+GHVMxjhzYLcKPSEgmz3fU+BRy3iYNtiXLaBssCW8KguReqkQOTb3MStV0Ugt4U1eIs1RZWRII6Ww8xeNNItyGGQI4ZMlpg/3lQtkl2JFnBp1imRyFe0kK2Id3PCslMgiQNMS77gvFeDhG3cSkYvheeg/e7ClIh5oh+IAAAAASUVORK5CYII="},6885:function(t,e,n){var i=n("7a23"),o=n("f30e"),r=n("f28d"),a=Object.defineProperty,s={},c=function(t){throw t};t.exports=function(t,e){if(r(s,t))return s[t];e||(e={});var n=[][t],l=!!r(e,"ACCESSORS")&&e.ACCESSORS,u=r(e,0)?e[0]:c,A=r(e,1)?e[1]:void 0;return s[t]=!!n&&!o((function(){if(l&&!i)return!0;var t={length:-1};l?a(t,1,{enumerable:!0,get:c}):t[1]=1,n.call(t,u,A)}))}},7286:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXr5djn4dTp49bt59rT6LKxAAACnElEQVQozw3NUUwScRzA8d8R6MF8YMIx8uk47hDSJbj14IPzOGc7jPLvwTGg5uAYDbe2tt56cLtznvEnS6yDqCcEaWi91DvrbLJZz7b1aFtz1aO+2OZWvn+/+4CHeB6BMYaqBLfjPNRY6RFT2JJYby+uAk4WUTrtlmJ4hgPYb2q1XGDQjaK8pgJHvqNaAX+KyuIkDXpgQinb46nOulnn4b5laUHTxLfseeArAoNOeJlOIjdoal0n1FA7tKFv5roK+YaHOqP3P0XyKHPHY+MhTRe5uCZnKhtJKw2eSrSoBDPLtpZuNcFNJcFyiCMxOaaHIfXz1e8HQbWLySrBQ4x0x1qlhnHlnz2HQEC6TNb0gTHXa7IKhcaHqkE015hk9whA0YeWiLIXf7Fa2CZo3DjqjB4tTuF8jIcbfcEx5z/w4sXpQhXW+ju0cqh7icTFmRMaG+v6CIvTjcSpHcH8JEsF3EPh3fRthYdVLLgI2fWXm85/pGFE4l046s70L+yKCcirGFR+jbpy3kMmiCGHrSezVONsn1RBixncyk2PcVWk7DlgxHo8iZwDyq5uAUD854dZhdIFYzKoQig2haUKi1lVufz2RZUZPZ41n/hrOQB6h0Hhg8I367FNoEHgeM/KY7szSeQwD8q2WE3HM35ZLl0K1MJiOtHIkBclRQUwZnyOWcNsRQQgVLj1PSqkjF9DsoOSaSg3iinKzvfmgsNFFfpP/2T3GLGvL4fHEfwIX1sVvXcPqLztehWGcfn9nI2U9nTfCgJPe/jFPLZwgVEzimBgAm0VIyK2tt1cE/AzQdLK+SxLSQ4aDCZnnId94OG2S1XwvnTbNk/ZnhyRCQT+sZM6z9g6LXL1BOBe+zJySiFkHAINCtnQokbCJ/apCv0foqPiZVfhpywAAAAASUVORK5CYII="},7287:function(t,e,n){var i=n("57c4");e.f=i},"7abd":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXm9PXq+Pno9vfs+vttWKBGAAACPElEQVQozw3RQWrbQACF4TfCMjPqZgIj4RRaxsZKE0PuMBZ2cLKaCI9RDAXFmJJknUWWI1O1UlamOMHJSjGkuFn3AD2Cr9CepDrAg+/xIxK4QwIqHHQkUhQ/WuphInVIFBojl8QXc012Tgq4RTtVHWVLZVFh1tEoI91uiN4joCqde8Ukn/zGM1B2W4ari2PtTwyw55Ld+Wways54qhGPyS6FzbIT3lIY8WwWdCq56Yolx6KmSKzoqrsCB5heAp4TGNQWJ1Pc6XlE5jQD5OlIX9I47A9uiUQcPQxcury/ToyxWJG/za6ki88crxKPocKS59Sl3EtBG7C89fCGflpfqoSzCeC4crioJA7F0V5+8MaSIk4qSCdwzpogmbqzEirVpGiS2dOVJvUuuqFEmhHao06KEpq+8lvHI14NJk3Qrmi9vBuRLwAz0qZB4hsDXQFXgtnlpDX3C6ug9BquSw/CYtwAzuTz5vuQNdr/YibhR68378ehZH30FSpjh71LpQkrsj+Q062h5WwZ5wlRoD6uQJy1DqvSYuCUapMBqT5YA4ZFw4KlWapxoUGlKWrx0eDQvmigu4WMYt97ruru98fYL8/0lG6CTOFcFWBhFK5gKw19h2JN808nh7xhkU6sWKLXdtkqBL6h+lULK5k19wFB/FldnGYf3LDeuf6IC2/MzJOSOP0qPxLqzaGIqtBcFIItrstkazONOkrc1D1czjuwEGESB4JJnjgSMN7PXAu7fZQpl1C236C+9mM4Af8P98Ch4R2TRl8AAAAASUVORK5CYII="},"7f9c":function(t,e,n){"use strict";var i=n("3874"),o=n.n(i);o.a},"802e":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg=="},8157:function(t,e,n){"use strict";var i=n("d2ee"),o=n.n(i);o.a},"81a0":function(t,e,n){var i=n("67ea"),o=n("21d4");t.exports=function(t,e){var n=t.exec;if("function"===typeof n){var r=n.call(t,e);if("object"!==typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==i(t))throw TypeError("RegExp#exec called on incompatible receiver");return o.call(t,e)}},"82da":function(t,e,n){},"88a3":function(t,e,n){"use strict";var i=n("2625"),o=n.n(i);o.a},a8c9:function(t,e,n){var i=n("67ea");t.exports=Array.isArray||function(t){return"Array"==i(t)}},a9ea:function(t,e,n){"use strict";var i=n("2485"),o=n.n(i);o.a},b165:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXe3t7a2trc3Nzg4OCXP9lCAAACoklEQVQozwXBzU/TYBwA4N+QEr4CNbSFwcFuowSqMRvEAwShHWAYNsu7dS0dLnGUSWT4kZB4lGzE4VtcwgIDJqcOWLJxcv4BOoQZuCPxSNSD4WSWLJGL8XmAIiyo2RgJ4A1pxQQlOxRAszLTdnPu2oQGb05RC5slJld7ZAIfo4O44Bn1ud59F0BcjnYOa17Jhwc6EdiKettncsXjT1f8KUBZUW41pK0Jc1Az4dEV3rkkPBtDSZ83Blyt0kSf2PRjzIykoBwINisPbPPtljdVE9iAXRfUPkXLVIgYrCccp5g687NdZbcJ+xa5VE/HhTtT23IKsN5jj/pcUd0dTZNAqCVw72n4gOwnTOC0vvHfaauT8d9zAoRRfPpISZRVyUiw8ELzOG1b2DZpFzkSrHLhq52twDEdyZHwvp2j4uv/bjvOf23/AcEtTuJbY5Cp4YcAer1IGkUzOo2rn8LQOKjFJw3NTw24nprQXY5aF4wxcqcSdbFQ00H4xFl8Drx4X4CikvAM1tuR8bKIBCBoLnKN10KJG4zKAsc7c9WEB9gnCi6BhVjqoco6t20ILAJuVctvaEZK732cRHDRmGfuihOam0o2CHByUZ/epCcVlRs2wmCnMqsd6aSim3ibBJtm1LGyXW3Bb7tJCPlFtUG+SvPdeEUAB60lNdo+VQbLcwRNVtT68FsLcr1+NotgNihlpExS1V2SFgNbeC8bEhgm8sM17wSi6Us2gxVWJU/5GKBpandvfyYbU1yHCLpCgWGbbPXn40rehEsUXKIJr9DMKgICfjc4bl1YfvUhE/YIECGRqjCxSM9hrybAIkND5OeWfFZsXkxB+qDzb7pUQ3EfQ3Ml6EChEt3D+iS01VqC7EQ/Z/DuPQcz4yChoFQJce2Qr+NNAv0HxofmpXGqgHkAAAAASUVORK5CYII="},b1a1:function(t,e,n){var i=n("f30e"),o=n("57c4"),r=n("bf98"),a=o("species");t.exports=function(t){return r>=51||!i((function(){var e=[],n=e.constructor={};return n[a]=function(){return{foo:1}},1!==e[t](Boolean).foo}))}},b3f9:function(t,e,n){"use strict";var i=n("91fe"),o=n("21d4");i({target:"RegExp",proto:!0,forced:/./.exec!==o},{exec:o})},b671:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII="},c0aa:function(t,e,n){var i=n("2a2f"),o=n("f28d"),r=n("7287"),a=n("c223").f;t.exports=function(t){var e=i.Symbol||(i.Symbol={});o(e,t)||a(e,t,{value:r.f(t)})}},c27e:function(t,e,n){"use strict";var i=n("1240"),o=n.n(i);o.a},c84b:function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"detail-wrapper"},[n("div",{staticClass:"detail"},[n("div",{staticClass:"bar"},[n("el-breadcrumb",{attrs:{separator:"/"}},[n("el-breadcrumb-item",{staticClass:"index",attrs:{to:{path:"/"}}},[t._v("书架")]),n("el-breadcrumb-item",{staticClass:"sub"},[t._v("目录")])],1)],1),n("el-divider"),n("div",{staticClass:"catalog"},t._l(this.$store.state.catalog,(function(e){return n("div",{key:e.index,staticClass:"note",on:{click:function(n){return t.toChapter(e.url,e.title,e.index)}}},[t._v(" "+t._s(e.title)+" ")])})),0)],1)])},o=[],r=n("82ae"),a=n.n(r),s={data:function(){return{key:"value"}},mounted:function(){var t=this;a.a.get("/getChapterList?url="+encodeURIComponent(sessionStorage.getItem("bookUrl"))).then((function(e){t.$store.commit("setCatalog",e.data.data),sessionStorage.setItem("catalog",JSON.stringify(e.data.data))}),(function(t){throw t}))},methods:{toChapter:function(t,e,n){sessionStorage.setItem("chapterID",n),this.$router.push({path:"/chapter"})}}},c=s,l=(n("c27e"),n("623f")),u=Object(l["a"])(c,i,o,!1,null,"6d476c76",null);e["default"]=u.exports},cf68:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEXm5ubo6Ojp6enr6+vt7e1FnZagAAACrklEQVQ4yx1SixUbMQgT3AKAFwDcAfzpBN1/qMrJS5w7bCQhC6IGSUGYQJd6Ox9ZPXi1AGJBavhUTT0JjYPGAab9WcDYIxsmlnxkayX8mhxCmKHA75az5cfRbWybEExiu08xDSgGym0mwuf3j4SvHeQxDJJzh2zp4iOlrD8iOb4SXyC1wiOLRTcnrje+nGamFeXVKWkmzbFIPChkmJ6Fg7mBpV8n+JGOVCd4jv1thThkjeQGNeafpeV3rsEWLfyWc8tC9jOv6FQ8rRzHOOVB+jCYEUAJpDvh8xHNFm/Tm5p5lw94Pp3NhtKEfQsGvnXhowdZE73hPwxKvjDd4i4PCdd0fe3W5fO8ktAsUAacLgstpUw60JCiPLg2XpkgiqPIYYXJd9ksGIT3q+LlevypzItvO+s0F1dBzVr2QDMUkYmuyGcrIS44mVJ7JVKwQXjYuBYp0Uetecbswzsikzu3gUR8bJC/C8Gd/NAzI/xdUGOYQQHDZ8X2d5XuzGRUiXAi9si5CRgoiToRZPtzLJkd0FUHRHZwJf0BHT1sE7gcnh0jmKKlSSF4/GBirGk5+K9NKlGDCfc9JtPhg78JdabH0YQRKNZnJ8tFnPfXHJb4xum1TTCeEmyEdbyEJLjznMLHuFD2Y9NEkSleIBs7SiCbblhgctVi9ch++kDYnn1C9DA5TvdPsToXM55wI6k+8eKT1blwPTqWb5CFJ+7dTBmab+KHy+xwNtItXhZNSpHD2fxnynrxG3ZBKRe8KBpXk11AnadlccEhr9w1nBBvBylNkv7A8eqpGBCDqhitmWQXBjjdS6idr/QjXWLDeMzMbVDoJuM8zN7WenMZWXgZ2vX3F01J3jHZbwk1LRP+DWEvDJtOUoh/AIaBUz5VpWyhuyx4QtgL/NmgC6kM/JvNe+R/C/5aL7BKIbYAAAAASUVORK5CYII="},d0e3:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXh7eHl8eXj7+Pn8+eTbH1KAAACPElEQVQozxWPQWrbQABF/0xn3JFKQRTZOIuUsbCCbOgdRoYEOauxkYPcTRyTlPQWIxEltrsRwQ6hK9nEQek6F+gNTE/Q3qLLusv34cN7SH3mFicdYW4gNIhJWXPBRVXzjcFD0IqeU4o4PRbAIVjyico0vJpIifqPfL80QN9DAQY5ucRHE/hpHxBldXe9GilaHKcKMlj6pho2zXgkNdBl0oJ8kiF1DSiJF1ZHBJkQr0Dbux/5I42Zp4cFahJDFGeW6/QjBwmFY/Q7vZ2SnoOdW2parv/Cnm81+m0xrEfiVXQ3W4nOXIqVYi3l6AAQBwMFkViVBANMto4enXHPNTkHBB0oVj4r5vHzCWayrgBvxtygDlDB2CNDjd80ZInY69aKVYZcfJ8DW+fWuc+syEODALx+ojqoafHsthTI+ZW27PGpIeo/cR6YKcbqIuIFhHmBrzAovzIOOJk1ucvcDzrMRYGVBH2yvcAOf0KiKwfRovBI3tm/kW1eemtfNWwIIXE2mJNhvoszfmMBfRCv0OPwd2321uDW3nx2q/BDxFVeoN1g7a6Im8yRnoawa8kbdXnU0cHeTMxKfZGlJgvLb3sKsxgglQnDdAfvj9LUnqWRDo0GiUmPwyU7TAsD7wHeIW3Nfy1qVGKoE9NgJCdYCAexNRob9yCn4DAQmXtQuUtera6bEmTTXhZy6h856xi4mnEl6BI9mfISkLbtJyZIMJIAUd5ZOBEu88KRAk71yxfItj/hpIB0Errv4gO1os4/UICf+o3kkqwAAAAASUVORK5CYII="},d2ee:function(t,e,n){},dcb6:function(t,e,n){"use strict";var i=n("f30e");function o(t,e){return RegExp(t,e)}e.UNSUPPORTED_Y=i((function(){var t=o("a","y");return t.lastIndex=2,null!=t.exec("abcd")})),e.BROKEN_CARET=i((function(){var t=o("^r","gy");return t.lastIndex=2,null!=t.exec("str")}))},deaa:function(t,e,n){"use strict";n("b3f9");var i=n("3d8a"),o=n("f30e"),r=n("57c4"),a=n("21d4"),s=n("2ba5"),c=r("species"),l=!o((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),u=function(){return"$0"==="a".replace(/./,"$0")}(),A=r("replace"),f=function(){return!!/./[A]&&""===/./[A]("a","$0")}(),g=!o((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]}));t.exports=function(t,e,n,A){var d=r(t),h=!o((function(){var e={};return e[d]=function(){return 7},7!=""[t](e)})),p=h&&!o((function(){var e=!1,n=/a/;return"split"===t&&(n={},n.constructor={},n.constructor[c]=function(){return n},n.flags="",n[d]=/./[d]),n.exec=function(){return e=!0,null},n[d](""),!e}));if(!h||!p||"replace"===t&&(!l||!u||f)||"split"===t&&!g){var m=/./[d],v=n(d,""[t],(function(t,e,n,i,o){return e.exec===a?h&&!o?{done:!0,value:m.call(e,n,i)}:{done:!0,value:t.call(n,e,i)}:{done:!1}}),{REPLACE_KEEPS_$0:u,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:f}),C=v[0],b=v[1];i(String.prototype,t,C),i(RegExp.prototype,d,2==e?function(t,e){return b.call(t,this,e)}:function(t){return b.call(t,this)})}A&&s(RegExp.prototype[d],"sham",!0)}},df5e:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAATlBMVEXdzaHh0KPgz6LdzKDezqLczJ7ezZ/fz6Dcy5zi0aXdzZ3fz6Tfz57h0KDg0aLcyZrg0KXi0qPfzZ3j06bh0qbdyJbfzJrhz5/cxpLZwo0vDconAAAFn0lEQVRIxxyPW5LjMAwDAT5FybLl2JnM3P+i6+wXWVC1GoQGaD0h4XM3Q5o4T0HgABHBi6pZ4CDXXcUOFd6VhqC3Kch4EI8w9oMXwvU6m5LOOvcxKMOhuu8i5+5cMjcgb0t4F2uvOoeI3/MlT4IqsbtM9UG2AGSXUOsxzPevnXzK1CSHytZLvx7VdQmUcJsJCxJh2nmHW12Qod1qPjt8pih47uQ9aGpoNWF+yElCt60oH7vdIU/MnlRPSBLC/VwqxcKR8PFqnADN9ih5ufqnTlG9KwCofvs7kKYqOPHTNMQ93j9qNImFw9vjHPZ0F1m8hUUVB/Q/TrRYDMXr9++APMFARAt6sPh6wVAXzxUGhZsFUwCNfPZ8/72TAHebAhvuOuT3gO1Vn5d9Jd5sBRkg0p2seL9B7ulkjFJFIt9HPpLzdSzzMP3UcodAfMqC6pBuET2heHK1itZf1GZ1bi0BwOSxiCS8f/JBHMPMM4XCu3Mt1uz9lJbDJRqsKDZuikzkvskQEz6hanfDfO494azY5JpqPqOF1RhxD9XYEdaNxiqWqakKgmPfmrsta8KAiwF4HBxGVUJAgeSqQaiRRZJ7D2jedhw5t1CIAKxag0CBA60BpoBE6DcUi8O5AuM4pLfN0kHLmeu2B4e6HofqbgxsTWUw3PAODqa1oDtyzgXBlusi1KFdclMPE8O3jvLJ8RNi5/RxDQVzVmXA233XQ4KummunfxvLOZo+iH37964YjP06995CTdu9hsvErqJNzmf4wTrZ5DL7+qW9EoLnadrx67b8dUtrJnBXaT1N1uvPaYRKpWkq52xNsMN7vv4Sdryt/f4MhQoMCKnvVxikai1CQ6ZsnwJDc8+3Y/z8HcfvYQNq66pnAu1Hwa+3KNSwbNu8h3nDPqTl9fl7tx8fBhFfdS0o0F3JUKEZtZG9b/LZEM95lzaR30OnWPzroMxyZYdBIMoMnpN0J+m7/40+/P4soFSUjgzE7yY5zrMJuoZv0CmpVguYx1pprfb5HOviRVhHUVi/352shxCYrYBZxGtVaxiAz/MsaGSIsB7R1t4zJXH//n7RTTQQwxqcGEqEvklFHUgiO2GvJV+jAIPR+N29usWDoiSOVrN3XuqT1egQJAAU9EwslVJC8u0rGcy+WPqktJhjfMpatIG6CDAb0v5H34MGKqiVRue7GGLZ9Otxtt4JIrAhxBDwDuqI9JavcO0A7GlqFt219tH/bln9jBXzaKWAEqJV0CBxs5TwM8EvUPHaa8S86vN303MVWOsl3goDBHPWSoQ9c0kQmCKljfsKNH1+ofEOHW8a9a7glZGS8fPieL/SRSs0LAhI4FDTnXs1QYtubv2+IXPZpHB4bhivRexBkYKsSrYXNjvMUbVXpVJ+N6haV72c1k2zrnv5IYBMJBYTSZx0KTkoM3vY93rU/qs7zHplc/3d2ACadhFWByrn9LUk2IWb5JywvawTQc3F0iz+lgsBmInAIemBJtft2plKIlAFOgcroigrG2XlDsAzywQECNyaI8yr2ogoh7D4qJOYmZBzQgoZAM1PAcB8sDrr1uE5CDMR+nWSSVUGUCHAs8Vd21HOE0FzNj37pX0sLp9p3K8k++xxpkmzDxK64rmTSJnDUuIgTeslui6lg92jonZXI4jqNiUuzN4IagcKMjCniMGCODoo8T4tGDprn2hRww+NrnYiCwokd9iiWrkmbRfXYGLAoZrjO1lVQKExjUy5fIkgJURmz2uGFdASwwlWx5gDVTMK7hP6ISRVsFbYNmqtZL9MQtio285PaekyzDhZmtdexCYB0SZcTmBdhvdbmAEonk8hwcHQuZN1kVqrhyKoHHsnQhQAjF7SG533Da2S4LGjx1LoZqp7XeKQLDUBmYmydG0NQHpMeR5lRIRQc1PQ2ASMQflF4YBDMt0/GFlEHeRwCcEAAAAASUVORK5CYII="},e160:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEX6+fP8+/X+/ff///kbczPAAAACeElEQVQozxXHQUgUUQAG4P8936yzs6VvZNZmN9QxVxiF9OLBoOjtOC6rQq6ygXjI2fCQBdXBg4egtzFGdqkoI+zgBFbqkm3hQSxhFYLotOcubeKhOnVYoqQy+m4f5g5TvpX0xHLbLY9j8SMhJp+Jk4LfAUS2kVRIjILmnwGBTX42PhCVlDJQkIiy2nWAvaJ1h+oFIpJ0hMSYVbyyrgDWshcMpMyL1brPDQKWmduO+KTJ6XeXAMK9Yc3FpD7atyNwg6kt5XgFpLPhjUTFSYVn2abDiugGShwD8JTVRJVo/2ecuKtRb/qc4BK+9TboFfokog4T2Fn6Oqdnsjk90NMS76Rji6E0NmwkPBAZ4Xbkw8KoDAkAbEhkc78e9omxxgxg6qa5HvMv+UZbCV0qmHnSHKl5TxeA2XTCGWekR581mwC5crBH81PznASqB9va3TbkYAjJPLfg5uBfXaJgIgIBv9eessRIhxe7PA7kj6uUMeMaQ/OEQOYRaaHlqH2Gxwsl6E/pwVY5FH7uCypBZPKvDQyVziYBrAkMURe2MOOOxG/eQpp5PF+bFzUV5HtPj9GeiVSNZDELleifYTp9NAjsoiXg4cW+4ZORkdSMB/B74aAdjhsVakhgkugsbmqcDSLEoWp8zRjrux3tli6Q5uM3E+maT99Wy0RiP7tboiuRZle2c6CYeL2kcUc1KvPtQKucogMadKVTQOJYCeyCYlhQQ/Q7Etfd/vBygy9iqy+LyHeF46saCYvW6ingsbA9RBWtdi8GgUXW+oQx9/wP6bAAX1TWeV+CbShZDlQ9xT6SoSxZmKRAkmXb60kzEzkRF+Ccb94BGspGJoN/UzmyR4wjXHAAAAAASUVORK5CYII="},e1dd:function(t,e,n){var i=n("d68d"),o=n("67ea"),r=n("57c4"),a=r("match");t.exports=function(t){var e;return i(t)&&(void 0!==(e=t[a])?!!e:"RegExp"==o(t))}},e350:function(t,e,n){var i=n("c0aa");i("iterator")},ec0f:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAALVBMVEXx58b168ny6Mjz6sn06sf27Mvw5sTz6cbw5cLy58T37svv47/168v37s7t4Ltrv0//AAAEjUlEQVQ4yw2Ty2sTURxGf3dmOqmPxb0zmaStCnfmZpL6gpmbxIpUSMZGrSJkxsZiVZimNVaqMklrUnWTRq2KIDFWWx+IFrIRFxXEB4KIgqu6EBdu7M6FIPg32PW3+DhwDmBaYrK56KP4HGIsvg/uvOV0wK+qgBMlO9BujuH4DSJlOseqV5a/BEF97gt0ChyIPqBhXI9BtqtIB8vJB/LdCQ3OVjaLNX0g7+OmoI4e7nkemAqX6o8vg0yyQAyQS7IfgvFbI+6QyI3R4KELxw7kwM2ooQfyQigYnwY5MZbMlHI1DvnQVCoVcrt+R+bO7vPDif3ybNajwqAAe443dpfDsPt379VMWZzGRuqM79mQF+DUz9nt74bQ8J/O80MtVR51U02JKKmTCvTzLVf+vuxP/aHnPo9+2bW+zVsJ0Y630/CrfzX+b+UL+7O68Rczv+7lrMh5etfKXvhc2rk6KforxuoO2xB2tcxKfeXHt18rHOiHI/0RRjW/YGRDkHiwo3nzqL60o58C/bgRuaj7vk+QOwOhpnFNdjuWpKMCGP8Yapu9Ty5FTHKQLGSEFikjd9ADwP9ciaNNjc5qMH6w50AF/LKOsOYqsOG9GjKgc7ZXolqntm6fysJ6Ma6ll2CiqmOgE6O7x1wXExklbeqMYcwsmJmOoigt8SBg2WfilDSsAZJcBxDcrqtBXzFQJqZNHfscyIhoZlygAtyYAceah+elrFbI+46gEHDGiW878Kj7JpWyfhg6iyRMymV1MKBSeVpfgLHIohyTojI6sRyK1VpcqzVZeEBLOnA9unhGKUXPJDYtV9Dxuz4iA5xSkSWhCJdAiJR9PHlvfvbntbrR14FDqUNRAYDJmSnv3oKxuz5+7fiblgVJyYLTbgUM05P7LESkoXvyWNfb0aUU6FZizgQIa25VqKQZqFrk6v6BsqqIHlQmkQ9KrBhkC20/DrFsAFEEYLjM+lj2wYHXCwnNvZQR42XJ2iVK+UBXnI+OBE6oXpUUHiQ1yg0MhA03iwGbnOdQYc1CMiPIPQrCQJFH4L4BMFktAtKd9PN5gnU2Gra4KuK+V+mjtBRpAGIqDVe4wnSnajiFGO5d7smvhVQEMEYwqshrENIEaY7YeblJYtsb3QhAHWZCEKK67swwPMKw0If1Ta+6DgHmlgPzcUTSbi3rrv1Y64/BYEMPQ5SDHUOR022B4QRF6xLUPAaPX/V4IDI5N2BMwx4LqO1uO4j6uW7NvM7lATqGAxY/ZHVgoGZbu7SvkNR75x6qGSB23FdouENVwN7sCbewTdsXGrrnQ5ZZKOCOFtMTIzxlPu6eYmtL+nMFmoK7OeXajn86r9sqWbfmvHC4IagE5qfCPGZvLSq5F55hHIxJFa4/vRxHBlz0og4TojU1l/MOHJX17lybdF0mQhFO44JYUNt3UA473IXw/iPfDWtKG5oFSXIF5iU/VnyDSjxxeDk3jAXRyVyGTNB9FxH9qcFDNJpVbt2y9LytUXkK7Py6+z1RezHQqnoY8XcLimmd8dCnBhQCuaGpJCq3SoIlmYvLz8UkWhJw7T8k+Db/DYEKwgAAAABJRU5ErkJggg=="},ef8e:function(t,e,n){"use strict";var i=n("3303").charAt,o=n("d0e2"),r=n("5646"),a="String Iterator",s=o.set,c=o.getterFor(a);r(String,"String",(function(t){s(this,{type:a,string:String(t),index:0})}),(function(){var t,e=c(this),n=e.string,o=e.index;return o>=n.length?{value:void 0,done:!0}:(t=i(n,o),e.index+=t.length,{value:t,done:!1})}))},fb11:function(t,e,n){"use strict";var i=n("f30e");t.exports=function(t,e){var n=[][t];return!!n&&i((function(){n.call(null,e||function(){throw 1},1)}))}},fc88:function(t,e,n){"use strict";var i=n("91fe"),o=n("7a23"),r=n("d5dc"),a=n("f28d"),s=n("d68d"),c=n("c223").f,l=n("f69c"),u=r.Symbol;if(o&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var A={},f=function(){var t=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),e=this instanceof f?new u(t):void 0===t?u():u(t);return""===t&&(A[e]=!0),e};l(f,u);var g=f.prototype=u.prototype;g.constructor=f;var d=g.toString,h="Symbol(test)"==String(u("test")),p=/^Symbol\((.*)\)[^)]+$/;c(g,"description",{configurable:!0,get:function(){var t=s(this)?this.valueOf():this,e=d.call(t);if(a(A,t))return"";var n=h?e.slice(7,-1):e.replace(p,"$1");return""===n?void 0:n}}),i({global:!0,forced:!0},{Symbol:f})}}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/css/about.f23c15cb.css b/app/src/main/assets/web/new/css/about.dbe575e1.css similarity index 53% rename from app/src/main/assets/web/css/about.f23c15cb.css rename to app/src/main/assets/web/new/css/about.dbe575e1.css index 1e35af10d..1fcd705d9 100644 --- a/app/src/main/assets/web/css/about.f23c15cb.css +++ b/app/src/main/assets/web/new/css/about.dbe575e1.css @@ -1 +1 @@ -@charset "UTF-8";@font-face{font-family:FZZCYSK;src:local("☺"),url(../fonts/shelffont.6c094b6d.ttf);font-style:normal;font-weight:400}.index-wrapper[data-v-5e92421e]{height:100%;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.index-wrapper .navigation-wrapper[data-v-5e92421e]{width:260px;min-width:260px;padding:48px 36px;background-color:#f7f7f7}.index-wrapper .navigation-wrapper .navigation-title[data-v-5e92421e]{font-size:24px;font-weight:500;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .navigation-sub-title[data-v-5e92421e]{font-size:16px;font-weight:300;font-family:FZZCYSK;margin-top:16px;color:#b1b1b1}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-5e92421e]{border-radius:50%;margin-top:24px}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-5e92421e] .el-input__inner{border-radius:50px;border-color:#e3e3e3}.index-wrapper .navigation-wrapper .recent-wrapper[data-v-5e92421e]{margin-top:36px}.index-wrapper .navigation-wrapper .recent-wrapper .recent-title[data-v-5e92421e]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent[data-v-5e92421e]{margin:18px 0}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent .recent-book[data-v-5e92421e]{font-size:10px;cursor:pointer}.index-wrapper .navigation-wrapper .setting-wrapper[data-v-5e92421e]{margin-top:36px}.index-wrapper .navigation-wrapper .setting-wrapper .setting-title[data-v-5e92421e]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .setting-wrapper .no-point[data-v-5e92421e]{pointer-events:none}.index-wrapper .navigation-wrapper .setting-wrapper .setting-connect[data-v-5e92421e]{font-size:8px;margin-top:16px;cursor:pointer}.index-wrapper .navigation-wrapper .bottom-icons[data-v-5e92421e]{position:fixed;bottom:0;height:120px;width:260px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.index-wrapper .shelf-wrapper[data-v-5e92421e]{padding:48px 48px;width:100%}.index-wrapper .shelf-wrapper[data-v-5e92421e] .el-icon-loading{font-size:36px;color:#b5b5b5}.index-wrapper .shelf-wrapper[data-v-5e92421e] .el-loading-text{font-weight:500;color:#b5b5b5}.index-wrapper .shelf-wrapper .books-wrapper[data-v-5e92421e]{height:100%;overflow:scroll}.index-wrapper .shelf-wrapper .books-wrapper .wrapper[data-v-5e92421e]{display:grid;grid-template-columns:repeat(auto-fill,380px);-ms-flex-pack:distribute;justify-content:space-around;grid-gap:10px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book[data-v-5e92421e]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:-webkit-box;display:-ms-flexbox;display:flex;cursor:pointer;margin-bottom:18px;padding:24px 24px;width:360px;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:distribute;justify-content:space-around}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .cover-img .cover[data-v-5e92421e],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .cover-img[data-v-5e92421e]{width:84px;height:112px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info[data-v-5e92421e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-align:left;-ms-flex-align:left;align-items:left;height:112px;margin-left:20px;-webkit-box-flex:1;-ms-flex:1;flex:1}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .name[data-v-5e92421e]{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;font-size:16px;font-weight:700;color:#33373d}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .sub[data-v-5e92421e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;font-size:12px;font-weight:600;color:#6b6b6b}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .sub .dot[data-v-5e92421e]{margin:0 7px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .dur-chapter[data-v-5e92421e],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .intro[data-v-5e92421e],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .last-chapter[data-v-5e92421e]{color:#969ba3;font-size:13px;margin-top:3px;font-weight:500;word-wrap:break-word;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;text-align:left}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book[data-v-5e92421e]:hover{background:rgba(0,0,0,.1);-webkit-transition-duration:.5s;transition-duration:.5s}.index-wrapper .shelf-wrapper .books-wrapper .wrapper[data-v-5e92421e]:last-child{margin-right:auto}.index-wrapper .shelf-wrapper .books-wrapper[data-v-5e92421e]::-webkit-scrollbar{width:0!important} \ No newline at end of file +@charset "UTF-8";@font-face{font-family:FZZCYSK;src:local("☺"),url(../fonts/shelffont.6c094b6d.ttf);font-style:normal;font-weight:400}.index-wrapper[data-v-21a7f6c5]{height:100%;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.index-wrapper .navigation-wrapper[data-v-21a7f6c5]{width:260px;min-width:260px;padding:48px 36px;background-color:#f7f7f7}.index-wrapper .navigation-wrapper .navigation-title[data-v-21a7f6c5]{font-size:24px;font-weight:500;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .navigation-sub-title[data-v-21a7f6c5]{font-size:16px;font-weight:300;font-family:FZZCYSK;margin-top:16px;color:#b1b1b1}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-21a7f6c5]{border-radius:50%;margin-top:24px}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-21a7f6c5] .el-input__inner{border-radius:50px;border-color:#e3e3e3}.index-wrapper .navigation-wrapper .recent-wrapper[data-v-21a7f6c5]{margin-top:36px}.index-wrapper .navigation-wrapper .recent-wrapper .recent-title[data-v-21a7f6c5]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent[data-v-21a7f6c5]{margin:18px 0}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent .recent-book[data-v-21a7f6c5]{font-size:10px;cursor:pointer}.index-wrapper .navigation-wrapper .setting-wrapper[data-v-21a7f6c5]{margin-top:36px}.index-wrapper .navigation-wrapper .setting-wrapper .setting-title[data-v-21a7f6c5]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .setting-wrapper .no-point[data-v-21a7f6c5]{pointer-events:none}.index-wrapper .navigation-wrapper .setting-wrapper .setting-connect[data-v-21a7f6c5]{font-size:8px;margin-top:16px;cursor:pointer}.index-wrapper .navigation-wrapper .bottom-icons[data-v-21a7f6c5]{position:fixed;bottom:0;height:120px;width:260px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.index-wrapper .shelf-wrapper[data-v-21a7f6c5]{padding:48px 48px;width:100%}.index-wrapper .shelf-wrapper[data-v-21a7f6c5] .el-icon-loading{font-size:36px;color:#b5b5b5}.index-wrapper .shelf-wrapper[data-v-21a7f6c5] .el-loading-text{font-weight:500;color:#b5b5b5}.index-wrapper .shelf-wrapper .books-wrapper[data-v-21a7f6c5]{height:100%;overflow:scroll}.index-wrapper .shelf-wrapper .books-wrapper .wrapper[data-v-21a7f6c5]{display:grid;grid-template-columns:repeat(auto-fill,380px);-ms-flex-pack:distribute;justify-content:space-around;grid-gap:10px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book[data-v-21a7f6c5]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:-webkit-box;display:-ms-flexbox;display:flex;cursor:pointer;margin-bottom:18px;padding:24px 24px;width:360px;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:distribute;justify-content:space-around}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .cover-img .cover[data-v-21a7f6c5],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .cover-img[data-v-21a7f6c5]{width:84px;height:112px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info[data-v-21a7f6c5]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-align:left;-ms-flex-align:left;align-items:left;height:112px;margin-left:20px;-webkit-box-flex:1;-ms-flex:1;flex:1}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .name[data-v-21a7f6c5]{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;font-size:16px;font-weight:700;color:#33373d}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .sub[data-v-21a7f6c5]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;font-size:12px;font-weight:600;color:#6b6b6b}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .sub .dot[data-v-21a7f6c5]{margin:0 7px}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .dur-chapter[data-v-21a7f6c5],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .intro[data-v-21a7f6c5],.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book .info .last-chapter[data-v-21a7f6c5]{color:#969ba3;font-size:13px;margin-top:3px;font-weight:500;word-wrap:break-word;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;text-align:left}.index-wrapper .shelf-wrapper .books-wrapper .wrapper .book[data-v-21a7f6c5]:hover{background:rgba(0,0,0,.1);-webkit-transition-duration:.5s;transition-duration:.5s}.index-wrapper .shelf-wrapper .books-wrapper .wrapper[data-v-21a7f6c5]:last-child{margin-right:auto}.index-wrapper .shelf-wrapper .books-wrapper[data-v-21a7f6c5]::-webkit-scrollbar{width:0!important} \ No newline at end of file diff --git a/app/src/main/assets/web/css/app.e1c0d2e4.css b/app/src/main/assets/web/new/css/app.e4c919b7.css old mode 100644 new mode 100755 similarity index 100% rename from app/src/main/assets/web/css/app.e1c0d2e4.css rename to app/src/main/assets/web/new/css/app.e4c919b7.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 old mode 100644 new mode 100755 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/new/css/detail.9ba76c69.css b/app/src/main/assets/web/new/css/detail.9ba76c69.css new file mode 100755 index 000000000..a126a7b1c --- /dev/null +++ b/app/src/main/assets/web/new/css/detail.9ba76c69.css @@ -0,0 +1 @@ +@charset "UTF-8";.detail-wrapper[data-v-57115a66]{padding:2% 0}.detail-wrapper .detail .bar .el-breadcrumb[data-v-57115a66]{font-size:24px;font-weight:500;line-height:48px}.detail-wrapper .detail .bar .el-breadcrumb .index[data-v-57115a66]{color:#333}.detail-wrapper .detail .bar .el-breadcrumb .sub[data-v-57115a66]{color:#676767}.detail-wrapper .detail .el-divider[data-v-57115a66]{margin-top:2%}.detail-wrapper .detail .catalog[data-v-57115a66]{margin-top:2%;display:grid;grid-template-columns:repeat(auto-fill,300px);-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.detail-wrapper .detail .catalog .note[data-v-57115a66]{width:200px;font-weight:500;font-size:14px;line-height:40px;padding-left:12px;padding-right:12px;word-wrap:break-word;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;text-align:left}@font-face{font-family:FZZCYSK;src:local("☺"),url(../fonts/popfont.f39ecc1a.ttf);font-style:normal;font-weight:400}.cata-wrapper[data-v-22f8c37b]{margin:-16px;padding:18px 0 24px 25px}.cata-wrapper .title[data-v-22f8c37b]{font-size:18px;font-weight:400;font-family:FZZCYSK;margin:0 0 20px 0;color:#ed4259;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;border-bottom:1px solid #ed4259}.cata-wrapper .data-wrapper[data-v-22f8c37b]{height:300px;overflow:auto}.cata-wrapper .data-wrapper .cata[data-v-22f8c37b]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.cata-wrapper .data-wrapper .cata .selected[data-v-22f8c37b]{color:#eb4259}.cata-wrapper .data-wrapper .cata .log[data-v-22f8c37b]{width:50%;height:40px;cursor:pointer;float:left;font:16px/40px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.cata-wrapper .data-wrapper .cata .log .log-text[data-v-22f8c37b]{margin-right:26px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.cata-wrapper .night[data-v-22f8c37b] .log{border-bottom:1px solid #666}.cata-wrapper .day[data-v-22f8c37b] .log{border-bottom:1px solid #f2f2f2}@font-face{font-family:iconfont;src:url(../fonts/iconfont.f9a3fb0e.woff) format("woff")}[data-v-36dafd56] .iconfont,[data-v-36dafd56] .moon-icon{font-family:iconfont;font-style:normal}.settings-wrapper[data-v-36dafd56]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin:-13px;width:478px;height:300px;text-align:left;padding:40px 0 40px 24px;background:#ede7da url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII=) repeat}.settings-wrapper .settings-title[data-v-36dafd56]{font-size:18px;line-height:22px;margin-bottom:28px;font-family:FZZCYSK;font-weight:400}.settings-wrapper .setting-list ul[data-v-36dafd56]{list-style:none outside none;margin:0;padding:0}.settings-wrapper .setting-list ul li[data-v-36dafd56]{list-style:none outside none}.settings-wrapper .setting-list ul li i[data-v-36dafd56]{font:12px/16px PingFangSC-Regular,-apple-system,Simsun;display:inline-block;min-width:48px;margin-right:16px;vertical-align:middle;color:#666}.settings-wrapper .setting-list ul li .theme-item[data-v-36dafd56]{line-height:32px;width:34px;height:34px;margin-right:16px;border-radius:100%;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul li .theme-item .iconfont[data-v-36dafd56]{display:none}.settings-wrapper .setting-list ul li .selected[data-v-36dafd56]{color:#ed4259}.settings-wrapper .setting-list ul li .selected .iconfont[data-v-36dafd56]{display:inline}.settings-wrapper .setting-list ul .font-list[data-v-36dafd56]{margin-top:28px}.settings-wrapper .setting-list ul .font-list .font-item[data-v-36dafd56]{width:78px;height:34px;cursor:pointer;margin-right:16px;border-radius:2px;text-align:center;vertical-align:middle;display:inline-block;font:14px/34px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.settings-wrapper .setting-list ul .font-list .font-item[data-v-36dafd56]:hover,.settings-wrapper .setting-list ul .font-list .selected[data-v-36dafd56]{color:#ed4259;border:1px solid #ed4259}.settings-wrapper .setting-list ul .font-size[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width[data-v-36dafd56]{margin-top:28px}.settings-wrapper .setting-list ul .font-size .resize[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width .resize[data-v-36dafd56]{display:inline-block;width:274px;height:34px;vertical-align:middle;border-radius:2px}.settings-wrapper .setting-list ul .font-size .resize span[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width .resize span[data-v-36dafd56]{width:89px;height:34px;line-height:34px;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul .font-size .resize span em[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width .resize span em[data-v-36dafd56]{font-style:normal}.settings-wrapper .setting-list ul .font-size .resize .less[data-v-36dafd56]:hover,.settings-wrapper .setting-list ul .font-size .resize .more[data-v-36dafd56]:hover,.settings-wrapper .setting-list ul .read-width .resize .less[data-v-36dafd56]:hover,.settings-wrapper .setting-list ul .read-width .resize .more[data-v-36dafd56]:hover{color:#ed4259}.settings-wrapper .setting-list ul .font-size .resize .lang[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width .resize .lang[data-v-36dafd56]{color:#a6a6a6;font-weight:400;font-family:FZZCYSK}.settings-wrapper .setting-list ul .font-size .resize b[data-v-36dafd56],.settings-wrapper .setting-list ul .read-width .resize b[data-v-36dafd56]{display:inline-block;height:20px;vertical-align:middle}.night[data-v-36dafd56] .selected,.night[data-v-36dafd56] .theme-item{border:1px solid #666}.night[data-v-36dafd56] .moon-icon{color:#ed4259}.night[data-v-36dafd56] .font-list .font-item,.night[data-v-36dafd56] .resize{border:1px solid #666;background:rgba(45,45,45,.5)}.night[data-v-36dafd56] .resize b{border-right:1px solid #666}.day[data-v-36dafd56] .theme-item{border:1px solid #e5e5e5}.day[data-v-36dafd56] .selected{border:1px solid #ed4259}.day[data-v-36dafd56] .moon-icon{display:inline;color:hsla(0,0%,100%,.2)}.day[data-v-36dafd56] .font-list .font-item{background:hsla(0,0%,100%,.5);border:1px solid rgba(0,0,0,.1)}.day[data-v-36dafd56] .resize{border:1px solid #e5e5e5;background:hsla(0,0%,100%,.5)}.day[data-v-36dafd56] .resize b{border-right:1px solid #e5e5e5}p[data-v-72ace4c0]{display:block;word-wrap:break-word;word-break:break-all}[data-v-5cf5dc8c] .pop-setting{margin-left:68px;top:0}[data-v-5cf5dc8c] .pop-cata{margin-left:10px}.chapter-wrapper[data-v-5cf5dc8c]{padding:0 4%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.chapter-wrapper[data-v-5cf5dc8c] .no-point{pointer-events:none}.chapter-wrapper .tool-bar[data-v-5cf5dc8c]{position:fixed;top:0;left:50%;z-index:100}.chapter-wrapper .tool-bar .tools[data-v-5cf5dc8c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.chapter-wrapper .tool-bar .tools .tool-icon[data-v-5cf5dc8c]{font-size:18px;width:58px;height:48px;text-align:center;padding-top:12px;cursor:pointer;outline:none}.chapter-wrapper .tool-bar .tools .tool-icon .iconfont[data-v-5cf5dc8c]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .tool-bar .tools .tool-icon .icon-text[data-v-5cf5dc8c]{font-size:12px}.chapter-wrapper .read-bar[data-v-5cf5dc8c]{position:fixed;bottom:0;right:50%;z-index:100}.chapter-wrapper .read-bar .tools[data-v-5cf5dc8c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.chapter-wrapper .read-bar .tools .tool-icon[data-v-5cf5dc8c]{font-size:18px;width:42px;height:31px;padding-top:12px;text-align:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;outline:none;margin-top:-1px}.chapter-wrapper .read-bar .tools .tool-icon .iconfont[data-v-5cf5dc8c]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .chapter-bar .el-breadcrumb .item[data-v-5cf5dc8c]{font-size:14px;color:#606266}.chapter-wrapper .chapter[data-v-5cf5dc8c]{font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif;text-align:left;padding:0 65px;min-height:100vh;width:670px;margin:0 auto}.chapter-wrapper .chapter[data-v-5cf5dc8c] .el-icon-loading{font-size:36px;color:#b5b5b5}.chapter-wrapper .chapter[data-v-5cf5dc8c] .el-loading-text{font-weight:500;color:#b5b5b5}.chapter-wrapper .chapter .content[data-v-5cf5dc8c]{font-size:18px;line-height:1.8;overflow:hidden;font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif}.chapter-wrapper .chapter .content .title[data-v-5cf5dc8c]{margin-bottom:57px;font:24px/32px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.chapter-wrapper .chapter .content .bottom-bar[data-v-5cf5dc8c],.chapter-wrapper .chapter .content .top-bar[data-v-5cf5dc8c]{height:64px}.day[data-v-5cf5dc8c] .popup{-webkit-box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04)}.day[data-v-5cf5dc8c] .tool-icon{border:1px solid rgba(0,0,0,.1);margin-top:-1px;color:#000}.day[data-v-5cf5dc8c] .tool-icon .icon-text{color:rgba(0,0,0,.4)}.day[data-v-5cf5dc8c] .chapter{border:1px solid #d8d8d8;color:#262626}.night[data-v-5cf5dc8c] .popup{-webkit-box-shadow:0 2px 4px rgba(0,0,0,.48),0 0 6px rgba(0,0,0,.16);box-shadow:0 2px 4px rgba(0,0,0,.48),0 0 6px rgba(0,0,0,.16)}.night[data-v-5cf5dc8c] .tool-icon{border:1px solid #444;margin-top:-1px;color:#666}.night[data-v-5cf5dc8c] .tool-icon .icon-text{color:#666}.night[data-v-5cf5dc8c] .chapter{border:1px solid #444;color:#666}.night[data-v-5cf5dc8c] .popper__arrow{background:#666} \ No newline at end of file diff --git a/app/src/main/assets/web/new/favicon.ico b/app/src/main/assets/web/new/favicon.ico new file mode 100755 index 000000000..df36fcfb7 Binary files /dev/null and b/app/src/main/assets/web/new/favicon.ico differ 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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/new/index.html b/app/src/main/assets/web/new/index.html new file mode 100755 index 000000000..1db8000c1 --- /dev/null +++ b/app/src/main/assets/web/new/index.html @@ -0,0 +1,3 @@ +Legado Bookshelf
\ No newline at end of file diff --git a/app/src/main/assets/web/new/js/about.59a63964.js b/app/src/main/assets/web/new/js/about.59a63964.js new file mode 100644 index 000000000..e45302d5d --- /dev/null +++ b/app/src/main/assets/web/new/js/about.59a63964.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about"],{"0e8a":function(t,e,a){},"7b5b":function(t,e,a){},"7e43":function(t,e,a){t.exports=a.p+"img/noCover.b5c48bc1.jpeg"},d504:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"index-wrapper"},[s("div",{staticClass:"navigation-wrapper"},[s("div",{staticClass:"navigation-title"},[t._v(" 阅读 ")]),s("div",{staticClass:"navigation-sub-title"},[t._v(" 清风不识字,何故乱翻书 ")]),s("div",{staticClass:"search-wrapper"},[s("el-input",{staticClass:"search-input",attrs:{size:"mini",placeholder:"搜索书籍"},model:{value:t.search,callback:function(e){t.search=e},expression:"search"}},[s("i",{staticClass:"el-input__icon el-icon-search",attrs:{slot:"prefix"},slot:"prefix"})])],1),s("div",{staticClass:"recent-wrapper"},[s("div",{staticClass:"recent-title"},[t._v(" 最近阅读 ")]),s("div",{staticClass:"reading-recent"},[s("el-tag",{staticClass:"recent-book",class:{"no-point":""==t.readingRecent.url},attrs:{type:"warning"},on:{click:function(e){return t.toDetail(t.readingRecent.url,t.readingRecent.name,t.readingRecent.chapterIndex)}}},[t._v(" "+t._s(t.readingRecent.name)+" ")])],1)]),s("div",{staticClass:"setting-wrapper"},[s("div",{staticClass:"setting-title"},[t._v(" 基本设定 ")]),s("div",{staticClass:"setting-item"},[s("el-tag",{staticClass:"setting-connect",class:{"no-point":t.newConnect},attrs:{type:t.connectType},on:{click:t.setIP}},[t._v(" "+t._s(t.connectStatus)+" ")])],1)]),s("div",{staticClass:"bottom-icons"},[s("a",{attrs:{href:"https://github.com/Celeter/web-yuedu3",target:"_blank"}},[s("div",{staticClass:"bottom-icon"},[s("img",{attrs:{src:a("fa39"),alt:""}})])])])]),s("div",{ref:"shelfWrapper",staticClass:"shelf-wrapper"},[s("div",{staticClass:"books-wrapper"},[s("div",{staticClass:"wrapper"},t._l(t.shelf,(function(e){return s("div",{key:e.noteUrl,staticClass:"book",on:{click:function(a){return t.toDetail(e.bookUrl,e.name,e.durChapterIndex)}}},[s("div",{staticClass:"cover-img"},[s("img",{staticClass:"cover",attrs:{src:e.coverUrl||a("7e43"),alt:""}})]),s("div",{staticClass:"info",on:{click:function(a){return t.toDetail(e.bookUrl,e.name,e.durChapterIndex)}}},[s("div",{staticClass:"name"},[t._v(t._s(e.name))]),s("div",{staticClass:"sub"},[s("div",{staticClass:"author"},[t._v(" "+t._s(e.author)+" ")]),s("div",{staticClass:"dot"},[t._v("•")]),s("div",{staticClass:"size"},[t._v("共"+t._s(e.totalChapterNum)+"章")])]),s("div",{staticClass:"dur-chapter"},[t._v("已读:"+t._s(e.durChapterTitle))]),s("div",{staticClass:"last-chapter"},[t._v("最新:"+t._s(e.latestChapterTitle))])])])})),0)])])])},n=[],i=(a("7b5b"),a("bc3a")),r=a.n(i),c={data:function(){return{search:"",readingRecent:{name:"尚无阅读记录",url:"",chapterIndex:0}}},mounted:function(){var t=localStorage.getItem("readingRecent");null!=t&&(this.readingRecent=JSON.parse(t),"undefined"==typeof this.readingRecent.chapterIndex&&(this.readingRecent.chapterIndex=0)),this.loading=this.$loading({target:this.$refs.shelfWrapper,lock:!0,text:"正在获取书籍信息",spinner:"el-icon-loading",background:"rgb(247,247,247)"});var e=this;r.a.get("/getBookshelf",{timeout:3e3}).then((function(t){e.loading.close(),e.$store.commit("setConnectType","success"),e.$store.commit("increaseBookNum",t.data.data.length),e.$store.commit("addBooks",t.data.data.sort((function(t,e){var a=t["durChapterTime"]||0,s=e["durChapterTime"]||0;return s-a}))),e.$store.commit("setConnectStatus","已连接 "),e.$store.commit("setNewConnect",!1)})).catch((function(t){throw e.loading.close(),e.$store.commit("setConnectType","danger"),e.$store.commit("setConnectStatus","连接失败"),e.$message.error("后端连接失败"),e.$store.commit("setNewConnect",!1),t}))},methods:{setIP:function(){},toDetail:function(t,e,a){sessionStorage.setItem("bookUrl",t),sessionStorage.setItem("bookName",e),sessionStorage.setItem("chapterIndex",a),this.readingRecent={name:e,url:t,chapterIndex:a},localStorage.setItem("readingRecent",JSON.stringify(this.readingRecent)),this.$router.push({path:"/chapter"})}},computed:{shelf:function(){return this.$store.state.shelf},connectStatus:function(){return this.$store.state.connectStatus},connectType:function(){return this.$store.state.connectType},newConnect:function(){return this.$store.state.newConnect}}},o=c,l=(a("ef57"),a("2877")),d=Object(l["a"])(o,s,n,!1,null,"21a7f6c5",null);e["default"]=d.exports},ef57:function(t,e,a){"use strict";var s=a("0e8a"),n=a.n(s);n.a},f820:function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},n=[function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"about"},[a("h1",[t._v("This is an about page")])])}],i=a("2877"),r={},c=Object(i["a"])(r,s,n,!1,null,null,null);e["default"]=c.exports},fa39:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECUlEQVRYR7WXTYhcRRDHq3pY9yKrYBQ8KBsjgvHgwRhiQBTjYZm4Xe8NusawhwS/o9GLoKhgBGPAgJd1NdGIXwtZTbRf9Rqzl6gHTVyDeIkIgnEOghAM6oKHzTJd0sO8Zaa338zb7NjwmJn++Ndv+lVVVyOsoM3Ozl69sLBAiHiDc26NUuoKv9w5d14p9aeI/DI4OMgjIyN/lJXFMhOttQ8BgBaR0TLzEXEGAKzW+lCv+V0BmLmGiLtF5M5eQrFxRPxaRCaI6LOi9YUAzPwGADxxMYYjayaJ6MkoZKyTmU8AwF19Mp7LfElEW0LNZTvAzIcBYFufjedy00T0QLt2B4AxZo9S6qX/yXhT1jn3cpqme3IbSwDM/DgAvNlu3Dm3Uyl1HAA2IOJ2EdleEu5Io9H4EBHPVCqVLSISRsMuInrLazUBpqamhoaGhr4TkRsDgLVpmtbzPmPMLQBwOwD4vvzxw8P5IyJztVrtVL4my7L1iPhTx7Yj/jw/P79pfHx8vgmQZdkLiPhK+O8GBgauqVarv5f819FpxpjLlVJ/hYMi8mKSJHubAMz8KwBcF1EYI6IjqwRIlFImonGWiNZhlmVVRDxWYGTVAMx8HwB8EtMXka1orT0gIo9GJrxNRLH+FW8IMx8EgEeW5QDEgx5gTkQ2Bk7yr9b60hVb6rKAmc8BwJWBne+x4P3XiWhtPwGstV9FzpSzHuBvALgsMHaaiDp2ZbUwWZZNIuKuQOcfD7AAAJeEcaq1Xr9ao+3rmdknnscCzQse4LdWEukYazQaa2q12vl+QTDztwCwOdCr+zA8iYi3RQwREdl+ADDz9QDwIwB0OLaInPJRcEhEHoyEyAmt9d39ALDW2lg1hYjv+lfgC4WJgkTxcJIkPcuqbpC+qgKATwvm7PYAGwDgdBeRZ4notYvZCWPMDqXUe13W3to8C6y10yJyv//u6zj/2R6ziPiRiBwt6xPMrBExFZEdRcYR8WOt9bb8MNoKAJ+3Jvtwed05d4dSKtz+c4h4VGsdrRWttZMici8AXFVix+4homNLBUmWZQcQMc/9x4mommXZ84i4t11MKbV5dHR06bxvH5uZmbnZOfdN6O0RmMNE1CxulgCstdeKyBcAcFPrVTyltZ4wxiSVSuXplkhda72zh9P1rClFZFOSJHMdAP5Hq3rxR6eH+IGIvIOuqFlr94nIc10WdRzxy6riAMJnr2nn3JlcME3TppMWNWvtfhF5pmB8WX0RvZgEEEtaYUUbM2KtfUdE/FUubNHipvBmZIxZp5TaDwBprlQGIHLqzSHiPq01x4B7Xk6Z2d8TfDwPlwFozfd1f90598Hi4uKrY2NjFwrzQVkP81nNi/byAWOMv8gOp2n6fhnt/wDqJrRWLmhIrwAAAABJRU5ErkJggg=="}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/new/js/about~detail.1caf6ef5.js b/app/src/main/assets/web/new/js/about~detail.1caf6ef5.js new file mode 100755 index 000000000..174656bbe --- /dev/null +++ b/app/src/main/assets/web/new/js/about~detail.1caf6ef5.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about~detail"],{"0a06":function(e,t,r){"use strict";var n=r("c532"),o=r("30b5"),i=r("f6b4"),s=r("5270"),a=r("4a7b");function u(e){this.defaults=e,this.interceptors={request:new i,response:new i}}u.prototype.request=function(e){"string"===typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=a(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[s,void 0],r=Promise.resolve(e);this.interceptors.request.forEach((function(e){t.unshift(e.fulfilled,e.rejected)})),this.interceptors.response.forEach((function(e){t.push(e.fulfilled,e.rejected)}));while(t.length)r=r.then(t.shift(),t.shift());return r},u.prototype.getUri=function(e){return e=a(this.defaults,e),o(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},n.forEach(["delete","get","head","options"],(function(e){u.prototype[e]=function(t,r){return this.request(n.merge(r||{},{method:e,url:t}))}})),n.forEach(["post","put","patch"],(function(e){u.prototype[e]=function(t,r,o){return this.request(n.merge(o||{},{method:e,url:t,data:r}))}})),e.exports=u},"0df6":function(e,t,r){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},"1d2b":function(e,t,r){"use strict";e.exports=function(e,t){return function(){for(var r=new Array(arguments.length),n=0;n=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};n.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),n.forEach(["post","put","patch"],(function(e){u.headers[e]=n.merge(i)})),e.exports=u}).call(this,r("4362"))},"2d83":function(e,t,r){"use strict";var n=r("387f");e.exports=function(e,t,r,o,i){var s=new Error(e);return n(s,t,r,o,i)}},"2e67":function(e,t,r){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},"30b5":function(e,t,r){"use strict";var n=r("c532");function o(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}e.exports=function(e,t,r){if(!t)return e;var i;if(r)i=r(t);else if(n.isURLSearchParams(t))i=t.toString();else{var s=[];n.forEach(t,(function(e,t){null!==e&&"undefined"!==typeof e&&(n.isArray(e)?t+="[]":e=[e],n.forEach(e,(function(e){n.isDate(e)?e=e.toISOString():n.isObject(e)&&(e=JSON.stringify(e)),s.push(o(t)+"="+o(e))})))})),i=s.join("&")}if(i){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+i}return e}},"387f":function(e,t,r){"use strict";e.exports=function(e,t,r,n,o){return e.config=t,r&&(e.code=r),e.request=n,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},3934:function(e,t,r){"use strict";var n=r("c532");e.exports=n.isStandardBrowserEnv()?function(){var e,t=/(msie|trident)/i.test(navigator.userAgent),r=document.createElement("a");function o(e){var n=e;return t&&(r.setAttribute("href",n),n=r.href),r.setAttribute("href",n),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:"/"===r.pathname.charAt(0)?r.pathname:"/"+r.pathname}}return e=o(window.location.href),function(t){var r=n.isString(t)?o(t):t;return r.protocol===e.protocol&&r.host===e.host}}():function(){return function(){return!0}}()},4362:function(e,t,r){t.nextTick=function(e){var t=Array.prototype.slice.call(arguments);t.shift(),setTimeout((function(){e.apply(null,t)}),0)},t.platform=t.arch=t.execPath=t.title="browser",t.pid=1,t.browser=!0,t.env={},t.argv=[],t.binding=function(e){throw new Error("No such module. (Possibly not yet loaded)")},function(){var e,n="/";t.cwd=function(){return n},t.chdir=function(t){e||(e=r("df7c")),n=e.resolve(t,n)}}(),t.exit=t.kill=t.umask=t.dlopen=t.uptime=t.memoryUsage=t.uvCounters=function(){},t.features={}},"467f":function(e,t,r){"use strict";var n=r("2d83");e.exports=function(e,t,r){var o=r.config.validateStatus;!o||o(r.status)?e(r):t(n("Request failed with status code "+r.status,r.config,null,r.request,r))}},"4a7b":function(e,t,r){"use strict";var n=r("c532");e.exports=function(e,t){t=t||{};var r={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];n.forEach(o,(function(e){"undefined"!==typeof t[e]&&(r[e]=t[e])})),n.forEach(i,(function(o){n.isObject(t[o])?r[o]=n.deepMerge(e[o],t[o]):"undefined"!==typeof t[o]?r[o]=t[o]:n.isObject(e[o])?r[o]=n.deepMerge(e[o]):"undefined"!==typeof e[o]&&(r[o]=e[o])})),n.forEach(s,(function(n){"undefined"!==typeof t[n]?r[n]=t[n]:"undefined"!==typeof e[n]&&(r[n]=e[n])}));var a=o.concat(i).concat(s),u=Object.keys(t).filter((function(e){return-1===a.indexOf(e)}));return n.forEach(u,(function(n){"undefined"!==typeof t[n]?r[n]=t[n]:"undefined"!==typeof e[n]&&(r[n]=e[n])})),r}},5270:function(e,t,r){"use strict";var n=r("c532"),o=r("c401"),i=r("2e67"),s=r("2444");function a(e){e.cancelToken&&e.cancelToken.throwIfRequested()}e.exports=function(e){a(e),e.headers=e.headers||{},e.data=o(e.data,e.headers,e.transformRequest),e.headers=n.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),n.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]}));var t=e.adapter||s.adapter;return t(e).then((function(t){return a(e),t.data=o(t.data,t.headers,e.transformResponse),t}),(function(t){return i(t)||(a(e),t&&t.response&&(t.response.data=o(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))}},"7a77":function(e,t,r){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},"7aac":function(e,t,r){"use strict";var n=r("c532");e.exports=n.isStandardBrowserEnv()?function(){return{write:function(e,t,r,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),n.isNumber(r)&&a.push("expires="+new Date(r).toGMTString()),n.isString(o)&&a.push("path="+o),n.isString(i)&&a.push("domain="+i),!0===s&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},"83b9":function(e,t,r){"use strict";var n=r("d925"),o=r("e683");e.exports=function(e,t){return e&&!n(t)?o(e,t):t}},"8df4":function(e,t,r){"use strict";var n=r("7a77");function o(e){if("function"!==typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var r=this;e((function(e){r.reason||(r.reason=new n(e),t(r.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var e,t=new o((function(t){e=t}));return{token:t,cancel:e}},e.exports=o},b50d:function(e,t,r){"use strict";var n=r("c532"),o=r("467f"),i=r("30b5"),s=r("83b9"),a=r("c345"),u=r("3934"),c=r("2d83");e.exports=function(e){return new Promise((function(t,f){var p=e.data,l=e.headers;n.isFormData(p)&&delete l["Content-Type"];var d=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";l.Authorization="Basic "+btoa(h+":"+m)}var g=s(e.baseURL,e.url);if(d.open(e.method.toUpperCase(),i(g,e.params,e.paramsSerializer),!0),d.timeout=e.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in d?a(d.getAllResponseHeaders()):null,n=e.responseType&&"text"!==e.responseType?d.response:d.responseText,i={data:n,status:d.status,statusText:d.statusText,headers:r,config:e,request:d};o(t,f,i),d=null}},d.onabort=function(){d&&(f(c("Request aborted",e,"ECONNABORTED",d)),d=null)},d.onerror=function(){f(c("Network Error",e,null,d)),d=null},d.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",d)),d=null},n.isStandardBrowserEnv()){var y=r("7aac"),v=(e.withCredentials||u(g))&&e.xsrfCookieName?y.read(e.xsrfCookieName):void 0;v&&(l[e.xsrfHeaderName]=v)}if("setRequestHeader"in d&&n.forEach(l,(function(e,t){"undefined"===typeof p&&"content-type"===t.toLowerCase()?delete l[t]:d.setRequestHeader(t,e)})),n.isUndefined(e.withCredentials)||(d.withCredentials=!!e.withCredentials),e.responseType)try{d.responseType=e.responseType}catch(b){if("json"!==e.responseType)throw b}"function"===typeof e.onDownloadProgress&&d.addEventListener("progress",e.onDownloadProgress),"function"===typeof e.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){d&&(d.abort(),f(e),d=null)})),void 0===p&&(p=null),d.send(p)}))}},bc3a:function(e,t,r){e.exports=r("cee4")},c345:function(e,t,r){"use strict";var n=r("c532"),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,r,i,s={};return e?(n.forEach(e.split("\n"),(function(e){if(i=e.indexOf(":"),t=n.trim(e.substr(0,i)).toLowerCase(),r=n.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;s[t]="set-cookie"===t?(s[t]?s[t]:[]).concat([r]):s[t]?s[t]+", "+r:r}})),s):s}},c401:function(e,t,r){"use strict";var n=r("c532");e.exports=function(e,t,r){return n.forEach(r,(function(r){e=r(e,t)})),e}},c532:function(e,t,r){"use strict";var n=r("1d2b"),o=Object.prototype.toString;function i(e){return"[object Array]"===o.call(e)}function s(e){return"undefined"===typeof e}function a(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&"function"===typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function u(e){return"[object ArrayBuffer]"===o.call(e)}function c(e){return"undefined"!==typeof FormData&&e instanceof FormData}function f(e){var t;return t="undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer,t}function p(e){return"string"===typeof e}function l(e){return"number"===typeof e}function d(e){return null!==e&&"object"===typeof e}function h(e){return"[object Date]"===o.call(e)}function m(e){return"[object File]"===o.call(e)}function g(e){return"[object Blob]"===o.call(e)}function y(e){return"[object Function]"===o.call(e)}function v(e){return d(e)&&y(e.pipe)}function b(e){return"undefined"!==typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function x(){return("undefined"===typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!==typeof window&&"undefined"!==typeof document)}function E(e,t){if(null!==e&&"undefined"!==typeof e)if("object"!==typeof e&&(e=[e]),i(e))for(var r=0,n=e.length;r=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}function n(e){"string"!==typeof e&&(e+="");var t,r=0,n=-1,o=!0;for(t=e.length-1;t>=0;--t)if(47===e.charCodeAt(t)){if(!o){r=t+1;break}}else-1===n&&(o=!1,n=t+1);return-1===n?"":e.slice(r,n)}function o(e,t){if(e.filter)return e.filter(t);for(var r=[],n=0;n=-1&&!n;i--){var s=i>=0?arguments[i]:e.cwd();if("string"!==typeof s)throw new TypeError("Arguments to path.resolve must be strings");s&&(t=s+"/"+t,n="/"===s.charAt(0))}return t=r(o(t.split("/"),(function(e){return!!e})),!n).join("/"),(n?"/":"")+t||"."},t.normalize=function(e){var n=t.isAbsolute(e),s="/"===i(e,-1);return e=r(o(e.split("/"),(function(e){return!!e})),!n).join("/"),e||n||(e="."),e&&s&&(e+="/"),(n?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(o(e,(function(e,t){if("string"!==typeof e)throw new TypeError("Arguments to path.join must be strings");return e})).join("/"))},t.relative=function(e,r){function n(e){for(var t=0;t=0;r--)if(""!==e[r])break;return t>r?[]:e.slice(t,r-t+1)}e=t.resolve(e).substr(1),r=t.resolve(r).substr(1);for(var o=n(e.split("/")),i=n(r.split("/")),s=Math.min(o.length,i.length),a=s,u=0;u=1;--i)if(t=e.charCodeAt(i),47===t){if(!o){n=i;break}}else o=!1;return-1===n?r?"/":".":r&&1===n?"/":e.slice(0,n)},t.basename=function(e,t){var r=n(e);return t&&r.substr(-1*t.length)===t&&(r=r.substr(0,r.length-t.length)),r},t.extname=function(e){"string"!==typeof e&&(e+="");for(var t=-1,r=0,n=-1,o=!0,i=0,s=e.length-1;s>=0;--s){var a=e.charCodeAt(s);if(47!==a)-1===n&&(o=!1,n=s+1),46===a?-1===t?t=s:1!==i&&(i=1):-1!==t&&(i=-1);else if(!o){r=s+1;break}}return-1===t||-1===n||0===i||1===i&&t===n-1&&t===r+1?"":e.slice(t,n)};var i="b"==="ab".substr(-1)?function(e,t,r){return e.substr(t,r)}:function(e,t,r){return t<0&&(t=e.length+t),e.substr(t,r)}}).call(this,r("4362"))},e683:function(e,t,r){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},f6b4:function(e,t,r){"use strict";var n=r("c532");function o(){this.handlers=[]}o.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},o.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},o.prototype.forEach=function(e){n.forEach(this.handlers,(function(t){null!==t&&e(t)}))},e.exports=o}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/js/app.b25f3cec.js b/app/src/main/assets/web/new/js/app.d7843716.js similarity index 54% rename from app/src/main/assets/web/js/app.b25f3cec.js rename to app/src/main/assets/web/new/js/app.d7843716.js index fd14ecb99..ee4bb86ef 100644 --- a/app/src/main/assets/web/js/app.b25f3cec.js +++ b/app/src/main/assets/web/new/js/app.d7843716.js @@ -1 +1 @@ -(function(e){function t(t){for(var o,r,i=t[0],l=t[1],c=t[2],f=0,d=[];f>>32-t}function o(e,t){var n,o,r,a,u;return r=2147483648&e,a=2147483648&t,n=1073741824&e,o=1073741824&t,u=(1073741823&e)+(1073741823&t),n&o?2147483648^u^r^a:n|o?1073741824&u?3221225472^u^r^a:1073741824^u^r^a:u^r^a}function r(e,t,n){return e&t|~e&n}function a(e,t,n){return e&n|t&~n}function u(e,t,n){return e^t^n}function i(e,t,n){return t^(e|~n)}function l(e,t,a,u,i,l,c){return e=o(e,o(o(r(t,a,u),i),c)),o(n(e,l),t)}function c(e,t,r,u,i,l,c){return e=o(e,o(o(a(t,r,u),i),c)),o(n(e,l),t)}function f(e,t,r,a,i,l,c){return e=o(e,o(o(u(t,r,a),i),c)),o(n(e,l),t)}function d(e,t,r,a,u,l,c){return e=o(e,o(o(i(t,r,a),u),c)),o(n(e,l),t)}function s(e){var t,n=e.length,o=n+8,r=(o-o%64)/64,a=16*(r+1),u=Array(a-1),i=0,l=0;while(l>>29,u}function p(e){var t,n,o="",r="";for(n=0;n<=3;n++)t=e>>>8*n&255,r="0"+t.toString(16),o+=r.substr(r.length-2,2);return o}var b,h,g,m,v,y,w,C,S,P=Array(),O=7,j=12,k=17,T=22,_=5,x=9,E=14,A=20,L=4,$=11,B=16,N=23,V=6,M=10,D=15,q=21;for(P=s(t),y=1732584193,w=4023233417,C=2562383102,S=271733878,b=0;b>>32-t}function o(e,t){var n,o,r,a,u;return r=2147483648&e,a=2147483648&t,n=1073741824&e,o=1073741824&t,u=(1073741823&e)+(1073741823&t),n&o?2147483648^u^r^a:n|o?1073741824&u?3221225472^u^r^a:1073741824^u^r^a:u^r^a}function r(e,t,n){return e&t|~e&n}function a(e,t,n){return e&n|t&~n}function u(e,t,n){return e^t^n}function i(e,t,n){return t^(e|~n)}function c(e,t,a,u,i,c,l){return e=o(e,o(o(r(t,a,u),i),l)),o(n(e,c),t)}function l(e,t,r,u,i,c,l){return e=o(e,o(o(a(t,r,u),i),l)),o(n(e,c),t)}function f(e,t,r,a,i,c,l){return e=o(e,o(o(u(t,r,a),i),l)),o(n(e,c),t)}function d(e,t,r,a,u,c,l){return e=o(e,o(o(i(t,r,a),u),l)),o(n(e,c),t)}function s(e){var t,n=e.length,o=n+8,r=(o-o%64)/64,a=16*(r+1),u=Array(a-1),i=0,c=0;while(c>>29,u}function p(e){var t,n,o="",r="";for(n=0;n<=3;n++)t=e>>>8*n&255,r="0"+t.toString(16),o+=r.substr(r.length-2,2);return o}var b,h,g,m,v,y,w,C,S,P=Array(),O=7,j=12,k=17,T=22,_=5,x=9,E=14,A=20,L=4,$=11,B=16,N=23,V=6,M=10,D=15,q=21;for(P=s(t),y=1732584193,w=4023233417,C=2562383102,S=271733878,b=0;bh;h++)if(m=c?b(r(g=e[h])[0],g[1]):b(e[h]),m&&m instanceof u)return m;return new u(!1)}d=p.call(e)}y=d.next;while(!(g=y.call(d)).done)if(m=l(d,b,g.value,c),"object"==typeof m&&m&&m instanceof u)return m;return new u(!1)};c.stop=function(e){return new u(!0,e)}},"23cb":function(e,t,n){var r=n("a691"),o=Math.max,i=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):i(n,t)}},"23e7":function(e,t,n){var r=n("da84"),o=n("06cf").f,i=n("9112"),a=n("6eeb"),s=n("ce4e"),l=n("e893"),u=n("94ca");e.exports=function(e,t){var n,c,f,d,p,h,v=e.target,m=e.global,y=e.stat;if(c=m?r:y?r[v]||s(v,{}):(r[v]||{}).prototype,c)for(f in t){if(p=t[f],e.noTargetGet?(h=o(c,f),d=h&&h.value):d=c[f],n=u(m?f:v+(y?".":"#")+f,e.forced),!n&&void 0!==d){if(typeof p===typeof d)continue;l(p,d)}(e.sham||d&&d.sham)&&i(p,"sham",!0),a(c,f,p,e)}}},"241c":function(e,t,n){var r=n("ca84"),o=n("7839"),i=o.concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},"25f0":function(e,t,n){"use strict";var r=n("6eeb"),o=n("825a"),i=n("d039"),a=n("ad6d"),s="toString",l=RegExp.prototype,u=l[s],c=i((function(){return"/a/b"!=u.call({source:"a",flags:"b"})})),f=u.name!=s;(c||f)&&r(RegExp.prototype,s,(function(){var e=o(this),t=String(e.source),n=e.flags,r=String(void 0===n&&e instanceof RegExp&&!("flags"in l)?a.call(e):n);return"/"+t+"/"+r}),{unsafe:!0})},2626:function(e,t,n){"use strict";var r=n("d066"),o=n("9bf2"),i=n("b622"),a=n("83ab"),s=i("species");e.exports=function(e){var t=r(e),n=o.f;a&&t&&!t[s]&&n(t,s,{configurable:!0,get:function(){return this}})}},2877:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,(u.functional?this.parent:this).$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},"299c":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=131)}({131:function(e,t,n){"use strict";n.r(t);var r=n(5),o=n.n(r),i=n(17),a=n.n(i),s=n(2),l=n(3),u=n(7),c=n.n(u),f={name:"ElTooltip",mixins:[o.a],props:{openDelay:{type:Number,default:0},disabled:Boolean,manual:Boolean,effect:{type:String,default:"dark"},arrowOffset:{type:Number,default:0},popperClass:String,content:String,visibleArrow:{default:!0},transition:{type:String,default:"el-fade-in-linear"},popperOptions:{default:function(){return{boundariesPadding:10,gpuAcceleration:!1}}},enterable:{type:Boolean,default:!0},hideAfter:{type:Number,default:0},tabindex:{type:Number,default:0}},data:function(){return{tooltipId:"el-tooltip-"+Object(l["generateId"])(),timeoutPending:null,focusing:!1}},beforeCreate:function(){var e=this;this.$isServer||(this.popperVM=new c.a({data:{node:""},render:function(e){return this.node}}).$mount(),this.debounceClose=a()(200,(function(){return e.handleClosePopper()})))},render:function(e){var t=this;this.popperVM&&(this.popperVM.node=e("transition",{attrs:{name:this.transition},on:{afterLeave:this.doDestroy}},[e("div",{on:{mouseleave:function(){t.setExpectedState(!1),t.debounceClose()},mouseenter:function(){t.setExpectedState(!0)}},ref:"popper",attrs:{role:"tooltip",id:this.tooltipId,"aria-hidden":this.disabled||!this.showPopper?"true":"false"},directives:[{name:"show",value:!this.disabled&&this.showPopper}],class:["el-tooltip__popper","is-"+this.effect,this.popperClass]},[this.$slots.content||this.content])]));var n=this.getFirstElement();if(!n)return null;var r=n.data=n.data||{};return r.staticClass=this.addTooltipClass(r.staticClass),n},mounted:function(){var e=this;this.referenceElm=this.$el,1===this.$el.nodeType&&(this.$el.setAttribute("aria-describedby",this.tooltipId),this.$el.setAttribute("tabindex",this.tabindex),Object(s["on"])(this.referenceElm,"mouseenter",this.show),Object(s["on"])(this.referenceElm,"mouseleave",this.hide),Object(s["on"])(this.referenceElm,"focus",(function(){if(e.$slots.default&&e.$slots.default.length){var t=e.$slots.default[0].componentInstance;t&&t.focus?t.focus():e.handleFocus()}else e.handleFocus()})),Object(s["on"])(this.referenceElm,"blur",this.handleBlur),Object(s["on"])(this.referenceElm,"click",this.removeFocusing)),this.value&&this.popperVM&&this.popperVM.$nextTick((function(){e.value&&e.updatePopper()}))},watch:{focusing:function(e){e?Object(s["addClass"])(this.referenceElm,"focusing"):Object(s["removeClass"])(this.referenceElm,"focusing")}},methods:{show:function(){this.setExpectedState(!0),this.handleShowPopper()},hide:function(){this.setExpectedState(!1),this.debounceClose()},handleFocus:function(){this.focusing=!0,this.show()},handleBlur:function(){this.focusing=!1,this.hide()},removeFocusing:function(){this.focusing=!1},addTooltipClass:function(e){return e?"el-tooltip "+e.replace("el-tooltip",""):"el-tooltip"},handleShowPopper:function(){var e=this;this.expectedState&&!this.manual&&(clearTimeout(this.timeout),this.timeout=setTimeout((function(){e.showPopper=!0}),this.openDelay),this.hideAfter>0&&(this.timeoutPending=setTimeout((function(){e.showPopper=!1}),this.hideAfter)))},handleClosePopper:function(){this.enterable&&this.expectedState||this.manual||(clearTimeout(this.timeout),this.timeoutPending&&clearTimeout(this.timeoutPending),this.showPopper=!1,this.disabled&&this.doDestroy())},setExpectedState:function(e){!1===e&&clearTimeout(this.timeoutPending),this.expectedState=e},getFirstElement:function(){var e=this.$slots.default;if(!Array.isArray(e))return null;for(var t=null,n=0;n=0&&Math.floor(t)===t&&isFinite(e)}function p(e){return o(e)&&"function"===typeof e.then&&"function"===typeof e.catch}function h(e){return null==e?"":Array.isArray(e)||c(e)&&e.toString===u?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function m(e,t){for(var n=Object.create(null),r=e.split(","),o=0;o-1)return e.splice(n,1)}}var b=Object.prototype.hasOwnProperty;function _(e,t){return b.call(e,t)}function x(e){var t=Object.create(null);return function(n){var r=t[n];return r||(t[n]=e(n))}}var w=/-(\w)/g,C=x((function(e){return e.replace(w,(function(e,t){return t?t.toUpperCase():""}))})),S=x((function(e){return e.charAt(0).toUpperCase()+e.slice(1)})),O=/\B([A-Z])/g,E=x((function(e){return e.replace(O,"-$1").toLowerCase()}));function k(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function $(e,t){return e.bind(t)}var j=Function.prototype.bind?$:k;function A(e,t){t=t||0;var n=e.length-t,r=new Array(n);while(n--)r[n]=e[n+t];return r}function T(e,t){for(var n in t)e[n]=t[n];return e}function M(e){for(var t={},n=0;n0,ne=Q&&Q.indexOf("edge/")>0,re=(Q&&Q.indexOf("android"),Q&&/iphone|ipad|ipod|ios/.test(Q)||"ios"===Z),oe=(Q&&/chrome\/\d+/.test(Q),Q&&/phantomjs/.test(Q),Q&&Q.match(/firefox\/(\d+)/)),ie={}.watch,ae=!1;if(Y)try{var se={};Object.defineProperty(se,"passive",{get:function(){ae=!0}}),window.addEventListener("test-passive",null,se)}catch(Ca){}var le=function(){return void 0===G&&(G=!Y&&!J&&"undefined"!==typeof e&&(e["process"]&&"server"===e["process"].env.VUE_ENV)),G},ue=Y&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ce(e){return"function"===typeof e&&/native code/.test(e.toString())}var fe,de="undefined"!==typeof Symbol&&ce(Symbol)&&"undefined"!==typeof Reflect&&ce(Reflect.ownKeys);fe="undefined"!==typeof Set&&ce(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var pe=P,he=0,ve=function(){this.id=he++,this.subs=[]};ve.prototype.addSub=function(e){this.subs.push(e)},ve.prototype.removeSub=function(e){g(this.subs,e)},ve.prototype.depend=function(){ve.target&&ve.target.addDep(this)},ve.prototype.notify=function(){var e=this.subs.slice();for(var t=0,n=e.length;t-1)if(i&&!_(o,"default"))a=!1;else if(""===a||a===E(e)){var l=et(String,o.type);(l<0||s0&&(a=kt(a,(t||"")+"_"+n),Et(a[0])&&Et(u)&&(c[l]=we(u.text+a[0].text),a.shift()),c.push.apply(c,a)):s(a)?Et(u)?c[l]=we(u.text+a):""!==a&&c.push(we(a)):Et(a)&&Et(u)?c[l]=we(u.text+a.text):(i(e._isVList)&&o(a.tag)&&r(a.key)&&o(t)&&(a.key="__vlist"+t+"_"+n+"__"),c.push(a)));return c}function $t(e){var t=e.$options.provide;t&&(e._provided="function"===typeof t?t.call(e):t)}function jt(e){var t=At(e.$options.inject,e);t&&(je(!1),Object.keys(t).forEach((function(n){Le(e,n,t[n])})),je(!0))}function At(e,t){if(e){for(var n=Object.create(null),r=de?Reflect.ownKeys(e):Object.keys(e),o=0;o0,a=e?!!e.$stable:!i,s=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var l in o={},e)e[l]&&"$"!==l[0]&&(o[l]=Lt(t,l,e[l]))}else o={};for(var u in t)u in o||(o[u]=Rt(t,u));return e&&Object.isExtensible(e)&&(e._normalized=o),U(o,"$stable",a),U(o,"$key",s),U(o,"$hasNormal",i),o}function Lt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return e=e&&"object"===typeof e&&!Array.isArray(e)?[e]:Ot(e),e&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function Rt(e,t){return function(){return e[t]}}function Nt(e,t){var n,r,i,a,s;if(Array.isArray(e)||"string"===typeof e)for(n=new Array(e.length),r=0,i=e.length;r1?A(n):n;for(var r=A(arguments,1),o='event handler for "'+e+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Gn=function(){return Xn.now()})}function Yn(){var e,t;for(Kn=Gn(),Vn=!0,zn.sort((function(e,t){return e.id-t.id})),Un=0;UnUn&&zn[n].id>e.id)n--;zn.splice(n+1,0,e)}else zn.push(e);Wn||(Wn=!0,ht(Yn))}}var tr=0,nr=function(e,t,n,r,o){this.vm=e,o&&(e._watcher=this),e._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++tr,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new fe,this.newDepIds=new fe,this.expression="","function"===typeof t?this.getter=t:(this.getter=K(t),this.getter||(this.getter=P)),this.value=this.lazy?void 0:this.get()};nr.prototype.get=function(){var e;ye(this);var t=this.vm;try{e=this.getter.call(t,t)}catch(Ca){if(!this.user)throw Ca;tt(Ca,t,'getter for watcher "'+this.expression+'"')}finally{this.deep&&mt(e),ge(),this.cleanupDeps()}return e},nr.prototype.addDep=function(e){var t=e.id;this.newDepIds.has(t)||(this.newDepIds.add(t),this.newDeps.push(e),this.depIds.has(t)||e.addSub(this))},nr.prototype.cleanupDeps=function(){var e=this.deps.length;while(e--){var t=this.deps[e];this.newDepIds.has(t.id)||t.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},nr.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():er(this)},nr.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||l(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(Ca){tt(Ca,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},nr.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},nr.prototype.depend=function(){var e=this.deps.length;while(e--)this.deps[e].depend()},nr.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);var e=this.deps.length;while(e--)this.deps[e].removeSub(this);this.active=!1}};var rr={enumerable:!0,configurable:!0,get:P,set:P};function or(e,t,n){rr.get=function(){return this[t][n]},rr.set=function(e){this[t][n]=e},Object.defineProperty(e,n,rr)}function ir(e){e._watchers=[];var t=e.$options;t.props&&ar(e,t.props),t.methods&&hr(e,t.methods),t.data?sr(e):Pe(e._data={},!0),t.computed&&cr(e,t.computed),t.watch&&t.watch!==ie&&vr(e,t.watch)}function ar(e,t){var n=e.$options.propsData||{},r=e._props={},o=e.$options._propKeys=[],i=!e.$parent;i||je(!1);var a=function(i){o.push(i);var a=Ye(i,t,n,e);Le(r,i,a),i in e||or(e,"_props",i)};for(var s in t)a(s);je(!0)}function sr(e){var t=e.$options.data;t=e._data="function"===typeof t?lr(t,e):t||{},c(t)||(t={});var n=Object.keys(t),r=e.$options.props,o=(e.$options.methods,n.length);while(o--){var i=n[o];0,r&&_(r,i)||V(i)||or(e,"_data",i)}Pe(t,!0)}function lr(e,t){ye();try{return e.call(t,t)}catch(Ca){return tt(Ca,t,"data()"),{}}finally{ge()}}var ur={lazy:!0};function cr(e,t){var n=e._computedWatchers=Object.create(null),r=le();for(var o in t){var i=t[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new nr(e,a||P,P,ur)),o in e||fr(e,o,i)}}function fr(e,t,n){var r=!le();"function"===typeof n?(rr.get=r?dr(t):pr(n),rr.set=P):(rr.get=n.get?r&&!1!==n.cache?dr(t):pr(n.get):P,rr.set=n.set||P),Object.defineProperty(e,t,rr)}function dr(e){return function(){var t=this._computedWatchers&&this._computedWatchers[e];if(t)return t.dirty&&t.evaluate(),ve.target&&t.depend(),t.value}}function pr(e){return function(){return e.call(this,this)}}function hr(e,t){e.$options.props;for(var n in t)e[n]="function"!==typeof t[n]?P:j(t[n],e)}function vr(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=A(arguments,1);return n.unshift(this),"function"===typeof e.install?e.install.apply(e,n):"function"===typeof e&&e.apply(null,n),t.push(e),this}}function Or(e){e.mixin=function(e){return this.options=Ge(this.options,e),this}}function Er(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=n.cid,o=e._Ctor||(e._Ctor={});if(o[r])return o[r];var i=e.name||n.options.name;var a=function(e){this._init(e)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=t++,a.options=Ge(n.options,e),a["super"]=n,a.options.props&&kr(a),a.options.computed&&$r(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,z.forEach((function(e){a[e]=n[e]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=e,a.sealedOptions=T({},a.options),o[r]=a,a}}function kr(e){var t=e.options.props;for(var n in t)or(e.prototype,"_props",n)}function $r(e){var t=e.options.computed;for(var n in t)fr(e.prototype,n,t[n])}function jr(e){z.forEach((function(t){e[t]=function(e,n){return n?("component"===t&&c(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"===typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}}))}function Ar(e){return e&&(e.Ctor.options.name||e.tag)}function Tr(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"===typeof e?e.split(",").indexOf(t)>-1:!!f(e)&&e.test(t)}function Mr(e,t){var n=e.cache,r=e.keys,o=e._vnode;for(var i in n){var a=n[i];if(a){var s=Ar(a.componentOptions);s&&!t(s)&&Pr(n,i,r,o)}}}function Pr(e,t,n,r){var o=e[t];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),e[t]=null,g(n,t)}br(Cr),yr(Cr),jn(Cr),Pn(Cr),gn(Cr);var Lr=[String,RegExp,Array],Rr={name:"keep-alive",abstract:!0,props:{include:Lr,exclude:Lr,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var e in this.cache)Pr(this.cache,e,this.keys)},mounted:function(){var e=this;this.$watch("include",(function(t){Mr(e,(function(e){return Tr(t,e)}))})),this.$watch("exclude",(function(t){Mr(e,(function(e){return!Tr(t,e)}))}))},render:function(){var e=this.$slots.default,t=Cn(e),n=t&&t.componentOptions;if(n){var r=Ar(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Tr(i,r))||a&&r&&Tr(a,r))return t;var s=this,l=s.cache,u=s.keys,c=null==t.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):t.key;l[c]?(t.componentInstance=l[c].componentInstance,g(u,c),u.push(c)):(l[c]=t,u.push(c),this.max&&u.length>parseInt(this.max)&&Pr(l,u[0],u,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}},Nr={KeepAlive:Rr};function Ir(e){var t={get:function(){return D}};Object.defineProperty(e,"config",t),e.util={warn:pe,extend:T,mergeOptions:Ge,defineReactive:Le},e.set=Re,e.delete=Ne,e.nextTick=ht,e.observable=function(e){return Pe(e),e},e.options=Object.create(null),z.forEach((function(t){e.options[t+"s"]=Object.create(null)})),e.options._base=e,T(e.options.components,Nr),Sr(e),Or(e),Er(e),jr(e)}Ir(Cr),Object.defineProperty(Cr.prototype,"$isServer",{get:le}),Object.defineProperty(Cr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cr,"FunctionalRenderContext",{value:Jt}),Cr.version="2.6.12";var Fr=m("style,class"),Hr=m("input,textarea,option,select,progress"),zr=function(e,t,n){return"value"===n&&Hr(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Br=m("contenteditable,draggable,spellcheck"),Dr=m("events,caret,typing,plaintext-only"),Wr=function(e,t){return Gr(t)||"false"===t?"false":"contenteditable"===e&&Dr(t)?t:"true"},Vr=m("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Ur="http://www.w3.org/1999/xlink",qr=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Kr=function(e){return qr(e)?e.slice(6,e.length):""},Gr=function(e){return null==e||!1===e};function Xr(e){var t=e.data,n=e,r=e;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(t=Yr(r.data,t));while(o(n=n.parent))n&&n.data&&(t=Yr(t,n.data));return Jr(t.staticClass,t.class)}function Yr(e,t){return{staticClass:Zr(e.staticClass,t.staticClass),class:o(e.class)?[e.class,t.class]:t.class}}function Jr(e,t){return o(e)||o(t)?Zr(e,Qr(t)):""}function Zr(e,t){return e?t?e+" "+t:e:t||""}function Qr(e){return Array.isArray(e)?eo(e):l(e)?to(e):"string"===typeof e?e:""}function eo(e){for(var t,n="",r=0,i=e.length;r-1?so[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:so[e]=/HTMLUnknownElement/.test(t.toString())}var uo=m("text,number,password,search,email,tel,url");function co(e){if("string"===typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}function fo(e,t){var n=document.createElement(e);return"select"!==e||t.data&&t.data.attrs&&void 0!==t.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n}function po(e,t){return document.createElementNS(no[e],t)}function ho(e){return document.createTextNode(e)}function vo(e){return document.createComment(e)}function mo(e,t,n){e.insertBefore(t,n)}function yo(e,t){e.removeChild(t)}function go(e,t){e.appendChild(t)}function bo(e){return e.parentNode}function _o(e){return e.nextSibling}function xo(e){return e.tagName}function wo(e,t){e.textContent=t}function Co(e,t){e.setAttribute(t,"")}var So=Object.freeze({createElement:fo,createElementNS:po,createTextNode:ho,createComment:vo,insertBefore:mo,removeChild:yo,appendChild:go,parentNode:bo,nextSibling:_o,tagName:xo,setTextContent:wo,setStyleScope:Co}),Oo={create:function(e,t){Eo(t)},update:function(e,t){e.data.ref!==t.data.ref&&(Eo(e,!0),Eo(t))},destroy:function(e){Eo(e,!0)}};function Eo(e,t){var n=e.data.ref;if(o(n)){var r=e.context,i=e.componentInstance||e.elm,a=r.$refs;t?Array.isArray(a[n])?g(a[n],i):a[n]===i&&(a[n]=void 0):e.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var ko=new be("",{},[]),$o=["create","activate","update","remove","destroy"];function jo(e,t){return e.key===t.key&&(e.tag===t.tag&&e.isComment===t.isComment&&o(e.data)===o(t.data)&&Ao(e,t)||i(e.isAsyncPlaceholder)&&e.asyncFactory===t.asyncFactory&&r(t.asyncFactory.error))}function Ao(e,t){if("input"!==e.tag)return!0;var n,r=o(n=e.data)&&o(n=n.attrs)&&n.type,i=o(n=t.data)&&o(n=n.attrs)&&n.type;return r===i||uo(r)&&uo(i)}function To(e,t,n){var r,i,a={};for(r=t;r<=n;++r)i=e[r].key,o(i)&&(a[i]=r);return a}function Mo(e){var t,n,a={},l=e.modules,u=e.nodeOps;for(t=0;t<$o.length;++t)for(a[$o[t]]=[],n=0;nv?(f=r(n[g+1])?null:n[g+1].elm,C(e,f,n,h,g,i)):h>g&&O(t,d,v)}function $(e,t,n,r){for(var i=n;i-1?Wo(e,t,n):Vr(t)?Gr(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Br(t)?e.setAttribute(t,Wr(t,n)):qr(t)?Gr(n)?e.removeAttributeNS(Ur,Kr(t)):e.setAttributeNS(Ur,t,n):Wo(e,t,n)}function Wo(e,t,n){if(Gr(n))e.removeAttribute(t);else{if(ee&&!te&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var Vo={create:Bo,update:Bo};function Uo(e,t){var n=t.elm,i=t.data,a=e.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Xr(t),l=n._transitionClasses;o(l)&&(s=Zr(s,Qr(l))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var qo,Ko={create:Uo,update:Uo},Go="__r",Xo="__c";function Yo(e){if(o(e[Go])){var t=ee?"change":"input";e[t]=[].concat(e[Go],e[t]||[]),delete e[Go]}o(e[Xo])&&(e.change=[].concat(e[Xo],e.change||[]),delete e[Xo])}function Jo(e,t,n){var r=qo;return function o(){var i=t.apply(null,arguments);null!==i&&ei(e,o,n,r)}}var Zo=at&&!(oe&&Number(oe[1])<=53);function Qo(e,t,n,r){if(Zo){var o=Kn,i=t;t=i._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=o||e.timeStamp<=0||e.target.ownerDocument!==document)return i.apply(this,arguments)}}qo.addEventListener(e,t,ae?{capture:n,passive:r}:n)}function ei(e,t,n,r){(r||qo).removeEventListener(e,t._wrapper||t,n)}function ti(e,t){if(!r(e.data.on)||!r(t.data.on)){var n=t.data.on||{},o=e.data.on||{};qo=t.elm,Yo(n),_t(n,o,Qo,ei,Jo,t.context),qo=void 0}}var ni,ri={create:ti,update:ti};function oi(e,t){if(!r(e.data.domProps)||!r(t.data.domProps)){var n,i,a=t.elm,s=e.data.domProps||{},l=t.data.domProps||{};for(n in o(l.__ob__)&&(l=t.data.domProps=T({},l)),s)n in l||(a[n]="");for(n in l){if(i=l[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);ii(a,u)&&(a.value=u)}else if("innerHTML"===n&&oo(a.tagName)&&r(a.innerHTML)){ni=ni||document.createElement("div"),ni.innerHTML=""+i+"";var c=ni.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(c.firstChild)a.appendChild(c.firstChild)}else if(i!==s[n])try{a[n]=i}catch(Ca){}}}}function ii(e,t){return!e.composing&&("OPTION"===e.tagName||ai(e,t)||si(e,t))}function ai(e,t){var n=!0;try{n=document.activeElement!==e}catch(Ca){}return n&&e.value!==t}function si(e,t){var n=e.value,r=e._vModifiers;if(o(r)){if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}var li={create:oi,update:oi},ui=x((function(e){var t={},n=/;(?![^(]*\))/g,r=/:(.+)/;return e.split(n).forEach((function(e){if(e){var n=e.split(r);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}));function ci(e){var t=fi(e.style);return e.staticStyle?T(e.staticStyle,t):t}function fi(e){return Array.isArray(e)?M(e):"string"===typeof e?ui(e):e}function di(e,t){var n,r={};if(t){var o=e;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=ci(o.data))&&T(r,n)}(n=ci(e.data))&&T(r,n);var i=e;while(i=i.parent)i.data&&(n=ci(i.data))&&T(r,n);return r}var pi,hi=/^--/,vi=/\s*!important$/,mi=function(e,t,n){if(hi.test(t))e.style.setProperty(t,n);else if(vi.test(n))e.style.setProperty(E(t),n.replace(vi,""),"important");else{var r=gi(t);if(Array.isArray(n))for(var o=0,i=n.length;o-1?t.split(xi).forEach((function(t){return e.classList.add(t)})):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Ci(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(xi).forEach((function(t){return e.classList.remove(t)})):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?e.setAttribute("class",n):e.removeAttribute("class")}}function Si(e){if(e){if("object"===typeof e){var t={};return!1!==e.css&&T(t,Oi(e.name||"v")),T(t,e),t}return"string"===typeof e?Oi(e):void 0}}var Oi=x((function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}})),Ei=Y&&!te,ki="transition",$i="animation",ji="transition",Ai="transitionend",Ti="animation",Mi="animationend";Ei&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(ji="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ti="WebkitAnimation",Mi="webkitAnimationEnd"));var Pi=Y?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Li(e){Pi((function(){Pi(e)}))}function Ri(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),wi(e,t))}function Ni(e,t){e._transitionClasses&&g(e._transitionClasses,t),Ci(e,t)}function Ii(e,t,n){var r=Hi(e,t),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===ki?Ai:Mi,l=0,u=function(){e.removeEventListener(s,c),n()},c=function(t){t.target===e&&++l>=a&&u()};setTimeout((function(){l0&&(n=ki,c=a,f=i.length):t===$i?u>0&&(n=$i,c=u,f=l.length):(c=Math.max(a,u),n=c>0?a>u?ki:$i:null,f=n?n===ki?i.length:l.length:0);var d=n===ki&&Fi.test(r[ji+"Property"]);return{type:n,timeout:c,propCount:f,hasTransform:d}}function zi(e,t){while(e.length1}function qi(e,t){!0!==t.data.show&&Di(t)}var Ki=Y?{create:qi,activate:qi,remove:function(e,t){!0!==e.data.show?Wi(e,t):t()}}:{},Gi=[Vo,Ko,ri,li,_i,Ki],Xi=Gi.concat(zo),Yi=Mo({nodeOps:So,modules:Xi});te&&document.addEventListener("selectionchange",(function(){var e=document.activeElement;e&&e.vmodel&&oa(e,"input")}));var Ji={inserted:function(e,t,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?xt(n,"postpatch",(function(){Ji.componentUpdated(e,t,n)})):Zi(e,t,n.context),e._vOptions=[].map.call(e.options,ta)):("textarea"===n.tag||uo(e.type))&&(e._vModifiers=t.modifiers,t.modifiers.lazy||(e.addEventListener("compositionstart",na),e.addEventListener("compositionend",ra),e.addEventListener("change",ra),te&&(e.vmodel=!0)))},componentUpdated:function(e,t,n){if("select"===n.tag){Zi(e,t,n.context);var r=e._vOptions,o=e._vOptions=[].map.call(e.options,ta);if(o.some((function(e,t){return!N(e,r[t])}))){var i=e.multiple?t.value.some((function(e){return ea(e,o)})):t.value!==t.oldValue&&ea(t.value,o);i&&oa(e,"change")}}}};function Zi(e,t,n){Qi(e,t,n),(ee||ne)&&setTimeout((function(){Qi(e,t,n)}),0)}function Qi(e,t,n){var r=t.value,o=e.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,l=e.options.length;s-1,a.selected!==i&&(a.selected=i);else if(N(ta(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));o||(e.selectedIndex=-1)}}function ea(e,t){return t.every((function(t){return!N(t,e)}))}function ta(e){return"_value"in e?e._value:e.value}function na(e){e.target.composing=!0}function ra(e){e.target.composing&&(e.target.composing=!1,oa(e.target,"input"))}function oa(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function ia(e){return!e.componentInstance||e.data&&e.data.transition?e:ia(e.componentInstance._vnode)}var aa={bind:function(e,t,n){var r=t.value;n=ia(n);var o=n.data&&n.data.transition,i=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&o?(n.data.show=!0,Di(n,(function(){e.style.display=i}))):e.style.display=r?i:"none"},update:function(e,t,n){var r=t.value,o=t.oldValue;if(!r!==!o){n=ia(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?Di(n,(function(){e.style.display=e.__vOriginalDisplay})):Wi(n,(function(){e.style.display="none"}))):e.style.display=r?e.__vOriginalDisplay:"none"}},unbind:function(e,t,n,r,o){o||(e.style.display=e.__vOriginalDisplay)}},sa={model:Ji,show:aa},la={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ua(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?ua(Cn(t.children)):e}function ca(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var o=n._parentListeners;for(var i in o)t[C(i)]=o[i];return t}function fa(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}function da(e){while(e=e.parent)if(e.data.transition)return!0}function pa(e,t){return t.key===e.key&&t.tag===e.tag}var ha=function(e){return e.tag||wn(e)},va=function(e){return"show"===e.name},ma={name:"transition",props:la,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(ha),n.length)){0;var r=this.mode;0;var o=n[0];if(da(this.$vnode))return o;var i=ua(o);if(!i)return o;if(this._leaving)return fa(e,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var l=(i.data||(i.data={})).transition=ca(this),u=this._vnode,c=ua(u);if(i.data.directives&&i.data.directives.some(va)&&(i.data.show=!0),c&&c.data&&!pa(i,c)&&!wn(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var f=c.data.transition=T({},l);if("out-in"===r)return this._leaving=!0,xt(f,"afterLeave",(function(){t._leaving=!1,t.$forceUpdate()})),fa(e,o);if("in-out"===r){if(wn(i))return u;var d,p=function(){d()};xt(l,"afterEnter",p),xt(l,"enterCancelled",p),xt(f,"delayLeave",(function(e){d=e}))}}return o}}},ya=T({tag:String,moveClass:String},la);delete ya.mode;var ga={props:ya,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var o=Tn(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,o(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=ca(this),s=0;sn)t.push(arguments[n++]);return _[++b]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},r(b),b},v=function(e){delete _[e]},"process"==l(m)?r=function(e){m.nextTick(C(e))}:g&&g.now?r=function(e){g.now(C(e))}:y&&!d?(o=new y,i=o.port2,o.port1.onmessage=S,r=u(i.postMessage,i,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||s(O)||"file:"===p.protocol?r=x in f("script")?function(e){c.appendChild(f("script"))[x]=function(){c.removeChild(this),w(e)}}:function(e){setTimeout(C(e),0)}:(r=O,a.addEventListener("message",S,!1))),e.exports={set:h,clear:v}},"2d00":function(e,t,n){var r,o,i=n("da84"),a=n("342f"),s=i.process,l=s&&s.versions,u=l&&l.v8;u?(r=u.split("."),o=r[0]+r[1]):a&&(r=a.match(/Edge\/(\d+)/),(!r||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/),r&&(o=r[1]))),e.exports=o&&+o},"2f62":function(e,t,n){"use strict";(function(e){ +/*! + * vuex v3.5.1 + * (c) 2020 Evan You + * @license MIT + */ +function n(e){var t=Number(e.version.split(".")[0]);if(t>=2)e.mixin({beforeCreate:r});else{var n=e.prototype._init;e.prototype._init=function(e){void 0===e&&(e={}),e.init=e.init?[r].concat(e.init):r,n.call(this,e)}}function r(){var e=this.$options;e.store?this.$store="function"===typeof e.store?e.store():e.store:e.parent&&e.parent.$store&&(this.$store=e.parent.$store)}}var r="undefined"!==typeof window?window:"undefined"!==typeof e?e:{},o=r.__VUE_DEVTOOLS_GLOBAL_HOOK__;function i(e){o&&(e._devtoolHook=o,o.emit("vuex:init",e),o.on("vuex:travel-to-state",(function(t){e.replaceState(t)})),e.subscribe((function(e,t){o.emit("vuex:mutation",e,t)}),{prepend:!0}),e.subscribeAction((function(e,t){o.emit("vuex:action",e,t)}),{prepend:!0}))}function a(e,t){return e.filter(t)[0]}function s(e,t){if(void 0===t&&(t=[]),null===e||"object"!==typeof e)return e;var n=a(t,(function(t){return t.original===e}));if(n)return n.copy;var r=Array.isArray(e)?[]:{};return t.push({original:e,copy:r}),Object.keys(e).forEach((function(n){r[n]=s(e[n],t)})),r}function l(e,t){Object.keys(e).forEach((function(n){return t(e[n],n)}))}function u(e){return null!==e&&"object"===typeof e}function c(e){return e&&"function"===typeof e.then}function f(e,t){return function(){return e(t)}}var d=function(e,t){this.runtime=t,this._children=Object.create(null),this._rawModule=e;var n=e.state;this.state=("function"===typeof n?n():n)||{}},p={namespaced:{configurable:!0}};p.namespaced.get=function(){return!!this._rawModule.namespaced},d.prototype.addChild=function(e,t){this._children[e]=t},d.prototype.removeChild=function(e){delete this._children[e]},d.prototype.getChild=function(e){return this._children[e]},d.prototype.hasChild=function(e){return e in this._children},d.prototype.update=function(e){this._rawModule.namespaced=e.namespaced,e.actions&&(this._rawModule.actions=e.actions),e.mutations&&(this._rawModule.mutations=e.mutations),e.getters&&(this._rawModule.getters=e.getters)},d.prototype.forEachChild=function(e){l(this._children,e)},d.prototype.forEachGetter=function(e){this._rawModule.getters&&l(this._rawModule.getters,e)},d.prototype.forEachAction=function(e){this._rawModule.actions&&l(this._rawModule.actions,e)},d.prototype.forEachMutation=function(e){this._rawModule.mutations&&l(this._rawModule.mutations,e)},Object.defineProperties(d.prototype,p);var h=function(e){this.register([],e,!1)};function v(e,t,n){if(t.update(n),n.modules)for(var r in n.modules){if(!t.getChild(r))return void 0;v(e.concat(r),t.getChild(r),n.modules[r])}}h.prototype.get=function(e){return e.reduce((function(e,t){return e.getChild(t)}),this.root)},h.prototype.getNamespace=function(e){var t=this.root;return e.reduce((function(e,n){return t=t.getChild(n),e+(t.namespaced?n+"/":"")}),"")},h.prototype.update=function(e){v([],this.root,e)},h.prototype.register=function(e,t,n){var r=this;void 0===n&&(n=!0);var o=new d(t,n);if(0===e.length)this.root=o;else{var i=this.get(e.slice(0,-1));i.addChild(e[e.length-1],o)}t.modules&&l(t.modules,(function(t,o){r.register(e.concat(o),t,n)}))},h.prototype.unregister=function(e){var t=this.get(e.slice(0,-1)),n=e[e.length-1],r=t.getChild(n);r&&r.runtime&&t.removeChild(n)},h.prototype.isRegistered=function(e){var t=this.get(e.slice(0,-1)),n=e[e.length-1];return t.hasChild(n)};var m;var y=function(e){var t=this;void 0===e&&(e={}),!m&&"undefined"!==typeof window&&window.Vue&&T(window.Vue);var n=e.plugins;void 0===n&&(n=[]);var r=e.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new h(e),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new m,this._makeLocalGettersCache=Object.create(null);var o=this,a=this,s=a.dispatch,l=a.commit;this.dispatch=function(e,t){return s.call(o,e,t)},this.commit=function(e,t,n){return l.call(o,e,t,n)},this.strict=r;var u=this._modules.root.state;w(this,u,[],this._modules.root),x(this,u),n.forEach((function(e){return e(t)}));var c=void 0!==e.devtools?e.devtools:m.config.devtools;c&&i(this)},g={state:{configurable:!0}};function b(e,t,n){return t.indexOf(e)<0&&(n&&n.prepend?t.unshift(e):t.push(e)),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function _(e,t){e._actions=Object.create(null),e._mutations=Object.create(null),e._wrappedGetters=Object.create(null),e._modulesNamespaceMap=Object.create(null);var n=e.state;w(e,n,[],e._modules.root,!0),x(e,n,t)}function x(e,t,n){var r=e._vm;e.getters={},e._makeLocalGettersCache=Object.create(null);var o=e._wrappedGetters,i={};l(o,(function(t,n){i[n]=f(t,e),Object.defineProperty(e.getters,n,{get:function(){return e._vm[n]},enumerable:!0})}));var a=m.config.silent;m.config.silent=!0,e._vm=new m({data:{$$state:t},computed:i}),m.config.silent=a,e.strict&&$(e),r&&(n&&e._withCommit((function(){r._data.$$state=null})),m.nextTick((function(){return r.$destroy()})))}function w(e,t,n,r,o){var i=!n.length,a=e._modules.getNamespace(n);if(r.namespaced&&(e._modulesNamespaceMap[a],e._modulesNamespaceMap[a]=r),!i&&!o){var s=j(t,n.slice(0,-1)),l=n[n.length-1];e._withCommit((function(){m.set(s,l,r.state)}))}var u=r.context=C(e,a,n);r.forEachMutation((function(t,n){var r=a+n;O(e,r,t,u)})),r.forEachAction((function(t,n){var r=t.root?n:a+n,o=t.handler||t;E(e,r,o,u)})),r.forEachGetter((function(t,n){var r=a+n;k(e,r,t,u)})),r.forEachChild((function(r,i){w(e,t,n.concat(i),r,o)}))}function C(e,t,n){var r=""===t,o={dispatch:r?e.dispatch:function(n,r,o){var i=A(n,r,o),a=i.payload,s=i.options,l=i.type;return s&&s.root||(l=t+l),e.dispatch(l,a)},commit:r?e.commit:function(n,r,o){var i=A(n,r,o),a=i.payload,s=i.options,l=i.type;s&&s.root||(l=t+l),e.commit(l,a,s)}};return Object.defineProperties(o,{getters:{get:r?function(){return e.getters}:function(){return S(e,t)}},state:{get:function(){return j(e.state,n)}}}),o}function S(e,t){if(!e._makeLocalGettersCache[t]){var n={},r=t.length;Object.keys(e.getters).forEach((function(o){if(o.slice(0,r)===t){var i=o.slice(r);Object.defineProperty(n,i,{get:function(){return e.getters[o]},enumerable:!0})}})),e._makeLocalGettersCache[t]=n}return e._makeLocalGettersCache[t]}function O(e,t,n,r){var o=e._mutations[t]||(e._mutations[t]=[]);o.push((function(t){n.call(e,r.state,t)}))}function E(e,t,n,r){var o=e._actions[t]||(e._actions[t]=[]);o.push((function(t){var o=n.call(e,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:e.getters,rootState:e.state},t);return c(o)||(o=Promise.resolve(o)),e._devtoolHook?o.catch((function(t){throw e._devtoolHook.emit("vuex:error",t),t})):o}))}function k(e,t,n,r){e._wrappedGetters[t]||(e._wrappedGetters[t]=function(e){return n(r.state,r.getters,e.state,e.getters)})}function $(e){e._vm.$watch((function(){return this._data.$$state}),(function(){0}),{deep:!0,sync:!0})}function j(e,t){return t.reduce((function(e,t){return e[t]}),e)}function A(e,t,n){return u(e)&&e.type&&(n=t,t=e,e=e.type),{type:e,payload:t,options:n}}function T(e){m&&e===m||(m=e,n(m))}g.state.get=function(){return this._vm._data.$$state},g.state.set=function(e){0},y.prototype.commit=function(e,t,n){var r=this,o=A(e,t,n),i=o.type,a=o.payload,s=(o.options,{type:i,payload:a}),l=this._mutations[i];l&&(this._withCommit((function(){l.forEach((function(e){e(a)}))})),this._subscribers.slice().forEach((function(e){return e(s,r.state)})))},y.prototype.dispatch=function(e,t){var n=this,r=A(e,t),o=r.type,i=r.payload,a={type:o,payload:i},s=this._actions[o];if(s){try{this._actionSubscribers.slice().filter((function(e){return e.before})).forEach((function(e){return e.before(a,n.state)}))}catch(u){0}var l=s.length>1?Promise.all(s.map((function(e){return e(i)}))):s[0](i);return new Promise((function(e,t){l.then((function(t){try{n._actionSubscribers.filter((function(e){return e.after})).forEach((function(e){return e.after(a,n.state)}))}catch(u){0}e(t)}),(function(e){try{n._actionSubscribers.filter((function(e){return e.error})).forEach((function(t){return t.error(a,n.state,e)}))}catch(u){0}t(e)}))}))}},y.prototype.subscribe=function(e,t){return b(e,this._subscribers,t)},y.prototype.subscribeAction=function(e,t){var n="function"===typeof e?{before:e}:e;return b(n,this._actionSubscribers,t)},y.prototype.watch=function(e,t,n){var r=this;return this._watcherVM.$watch((function(){return e(r.state,r.getters)}),t,n)},y.prototype.replaceState=function(e){var t=this;this._withCommit((function(){t._vm._data.$$state=e}))},y.prototype.registerModule=function(e,t,n){void 0===n&&(n={}),"string"===typeof e&&(e=[e]),this._modules.register(e,t),w(this,this.state,e,this._modules.get(e),n.preserveState),x(this,this.state)},y.prototype.unregisterModule=function(e){var t=this;"string"===typeof e&&(e=[e]),this._modules.unregister(e),this._withCommit((function(){var n=j(t.state,e.slice(0,-1));m.delete(n,e[e.length-1])})),_(this)},y.prototype.hasModule=function(e){return"string"===typeof e&&(e=[e]),this._modules.isRegistered(e)},y.prototype.hotUpdate=function(e){this._modules.update(e),_(this,!0)},y.prototype._withCommit=function(e){var t=this._committing;this._committing=!0,e(),this._committing=t},Object.defineProperties(y.prototype,g);var M=H((function(e,t){var n={};return I(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=this.$store.state,n=this.$store.getters;if(e){var r=z(this.$store,"mapState",e);if(!r)return;t=r.context.state,n=r.context.getters}return"function"===typeof o?o.call(this,t,n):t[o]},n[r].vuex=!0})),n})),P=H((function(e,t){var n={};return I(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=[],n=arguments.length;while(n--)t[n]=arguments[n];var r=this.$store.commit;if(e){var i=z(this.$store,"mapMutations",e);if(!i)return;r=i.context.commit}return"function"===typeof o?o.apply(this,[r].concat(t)):r.apply(this.$store,[o].concat(t))}})),n})),L=H((function(e,t){var n={};return I(t).forEach((function(t){var r=t.key,o=t.val;o=e+o,n[r]=function(){if(!e||z(this.$store,"mapGetters",e))return this.$store.getters[o]},n[r].vuex=!0})),n})),R=H((function(e,t){var n={};return I(t).forEach((function(t){var r=t.key,o=t.val;n[r]=function(){var t=[],n=arguments.length;while(n--)t[n]=arguments[n];var r=this.$store.dispatch;if(e){var i=z(this.$store,"mapActions",e);if(!i)return;r=i.context.dispatch}return"function"===typeof o?o.apply(this,[r].concat(t)):r.apply(this.$store,[o].concat(t))}})),n})),N=function(e){return{mapState:M.bind(null,e),mapGetters:L.bind(null,e),mapMutations:P.bind(null,e),mapActions:R.bind(null,e)}};function I(e){return F(e)?Array.isArray(e)?e.map((function(e){return{key:e,val:e}})):Object.keys(e).map((function(t){return{key:t,val:e[t]}})):[]}function F(e){return Array.isArray(e)||u(e)}function H(e){return function(t,n){return"string"!==typeof t?(n=t,t=""):"/"!==t.charAt(t.length-1)&&(t+="/"),e(t,n)}}function z(e,t,n){var r=e._modulesNamespaceMap[n];return r}function B(e){void 0===e&&(e={});var t=e.collapsed;void 0===t&&(t=!0);var n=e.filter;void 0===n&&(n=function(e,t,n){return!0});var r=e.transformer;void 0===r&&(r=function(e){return e});var o=e.mutationTransformer;void 0===o&&(o=function(e){return e});var i=e.actionFilter;void 0===i&&(i=function(e,t){return!0});var a=e.actionTransformer;void 0===a&&(a=function(e){return e});var l=e.logMutations;void 0===l&&(l=!0);var u=e.logActions;void 0===u&&(u=!0);var c=e.logger;return void 0===c&&(c=console),function(e){var f=s(e.state);"undefined"!==typeof c&&(l&&e.subscribe((function(e,i){var a=s(i);if(n(e,f,a)){var l=V(),u=o(e),d="mutation "+e.type+l;D(c,d,t),c.log("%c prev state","color: #9E9E9E; font-weight: bold",r(f)),c.log("%c mutation","color: #03A9F4; font-weight: bold",u),c.log("%c next state","color: #4CAF50; font-weight: bold",r(a)),W(c)}f=a})),u&&e.subscribeAction((function(e,n){if(i(e,n)){var r=V(),o=a(e),s="action "+e.type+r;D(c,s,t),c.log("%c action","color: #03A9F4; font-weight: bold",o),W(c)}})))}}function D(e,t,n){var r=n?e.groupCollapsed:e.group;try{r.call(e,t)}catch(o){e.log(t)}}function W(e){try{e.groupEnd()}catch(t){e.log("—— log end ——")}}function V(){var e=new Date;return" @ "+q(e.getHours(),2)+":"+q(e.getMinutes(),2)+":"+q(e.getSeconds(),2)+"."+q(e.getMilliseconds(),3)}function U(e,t){return new Array(t+1).join(e)}function q(e,t){return U("0",t-e.toString().length)+e}var K={Store:y,install:T,version:"3.5.1",mapState:M,mapMutations:P,mapGetters:L,mapActions:R,createNamespacedHelpers:N,createLogger:B};t["a"]=K}).call(this,n("c8ba"))},"342f":function(e,t,n){var r=n("d066");e.exports=r("navigator","userAgent")||""},"35a1":function(e,t,n){var r=n("f5df"),o=n("3f8c"),i=n("b622"),a=i("iterator");e.exports=function(e){if(void 0!=e)return e[a]||e["@@iterator"]||o[r(e)]}},"37e8":function(e,t,n){var r=n("83ab"),o=n("9bf2"),i=n("825a"),a=n("df75");e.exports=r?Object.defineProperties:function(e,t){i(e);var n,r=a(t),s=r.length,l=0;while(s>l)o.f(e,n=r[l++],t[n]);return e}},"38a0":function(e,t,n){},"3bbe":function(e,t,n){var r=n("861d");e.exports=function(e){if(!r(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},"3c4e":function(e,t,n){"use strict";var r=function(e){return o(e)&&!i(e)};function o(e){return!!e&&"object"===typeof e}function i(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||l(e)}var a="function"===typeof Symbol&&Symbol.for,s=a?Symbol.for("react.element"):60103;function l(e){return e.$$typeof===s}function u(e){return Array.isArray(e)?[]:{}}function c(e,t){var n=t&&!0===t.clone;return n&&r(e)?p(u(e),e,t):e}function f(e,t,n){var o=e.slice();return t.forEach((function(t,i){"undefined"===typeof o[i]?o[i]=c(t,n):r(t)?o[i]=p(e[i],t,n):-1===e.indexOf(t)&&o.push(c(t,n))})),o}function d(e,t,n){var o={};return r(e)&&Object.keys(e).forEach((function(t){o[t]=c(e[t],n)})),Object.keys(t).forEach((function(i){r(t[i])&&e[i]?o[i]=p(e[i],t[i],n):o[i]=c(t[i],n)})),o}function p(e,t,n){var r=Array.isArray(t),o=Array.isArray(e),i=n||{arrayMerge:f},a=r===o;if(a){if(r){var s=i.arrayMerge||f;return s(e,t,n)}return d(e,t,n)}return c(t,n)}p.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce((function(e,n){return p(e,n,t)}))};var h=p;e.exports=h},"3f8c":function(e,t){e.exports={}},4010:function(e,t,n){"use strict";t.__esModule=!0,t.removeResizeListener=t.addResizeListener=void 0;var r=n("6dd8"),o=i(r);function i(e){return e&&e.__esModule?e:{default:e}}var a="undefined"===typeof window,s=function(e){var t=e,n=Array.isArray(t),r=0;for(t=n?t:t[Symbol.iterator]();;){var o;if(n){if(r>=t.length)break;o=t[r++]}else{if(r=t.next(),r.done)break;o=r.value}var i=o,a=i.target.__resizeListeners__||[];a.length&&a.forEach((function(e){e()}))}};t.addResizeListener=function(e,t){a||(e.__resizeListeners__||(e.__resizeListeners__=[],e.__ro__=new o.default(s),e.__ro__.observe(e)),e.__resizeListeners__.push(t))},t.removeResizeListener=function(e,t){e&&e.__resizeListeners__&&(e.__resizeListeners__.splice(e.__resizeListeners__.indexOf(t),1),e.__resizeListeners__.length||e.__ro__.disconnect())}},"417f":function(e,t,n){"use strict";t.__esModule=!0;var r=n("2b0e"),o=a(r),i=n("5924");function a(e){return e&&e.__esModule?e:{default:e}}var s=[],l="@@clickoutsideContext",u=void 0,c=0;function f(e,t,n){return function(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};!(n&&n.context&&r.target&&o.target)||e.contains(r.target)||e.contains(o.target)||e===r.target||n.context.popperElm&&(n.context.popperElm.contains(r.target)||n.context.popperElm.contains(o.target))||(t.expression&&e[l].methodName&&n.context[e[l].methodName]?n.context[e[l].methodName]():e[l].bindingFn&&e[l].bindingFn())}}!o.default.prototype.$isServer&&(0,i.on)(document,"mousedown",(function(e){return u=e})),!o.default.prototype.$isServer&&(0,i.on)(document,"mouseup",(function(e){s.forEach((function(t){return t[l].documentHandler(e,u)}))})),t.default={bind:function(e,t,n){s.push(e);var r=c++;e[l]={id:r,documentHandler:f(e,t,n),methodName:t.expression,bindingFn:t.value}},update:function(e,t,n){e[l].documentHandler=f(e,t,n),e[l].methodName=t.expression,e[l].bindingFn=t.value},unbind:function(e){for(var t=s.length,n=0;n0){var r=t[t.length-1];if(r.id===e){if(r.modalClass){var o=r.modalClass.trim().split(/\s+/);o.forEach((function(e){return(0,i.removeClass)(n,e)}))}t.pop(),t.length>0&&(n.style.zIndex=t[t.length-1].zIndex)}else for(var a=t.length-1;a>=0;a--)if(t[a].id===e){t.splice(a,1);break}}0===t.length&&(this.modalFade&&(0,i.addClass)(n,"v-modal-leave"),setTimeout((function(){0===t.length&&(n.parentNode&&n.parentNode.removeChild(n),n.style.display="none",d.modalDom=void 0),(0,i.removeClass)(n,"v-modal-leave")}),200))}};Object.defineProperty(d,"zIndex",{configurable:!0,get:function(){return l||(u=u||(o.default.prototype.$ELEMENT||{}).zIndex||2e3,l=!0),u},set:function(e){u=e}});var p=function(){if(!o.default.prototype.$isServer&&d.modalStack.length>0){var e=d.modalStack[d.modalStack.length-1];if(!e)return;var t=d.getInstance(e.id);return t}};o.default.prototype.$isServer||window.addEventListener("keydown",(function(e){if(27===e.keyCode){var t=p();t&&t.closeOnPressEscape&&(t.handleClose?t.handleClose():t.handleAction?t.handleAction("cancel"):t.close())}})),t.default=d},"4d64":function(e,t,n){var r=n("fc6a"),o=n("50c4"),i=n("23cb"),a=function(e){return function(t,n,a){var s,l=r(t),u=o(l.length),c=i(a,u);if(e&&n!=n){while(u>c)if(s=l[c++],s!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===n)return e||c||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},"50c4":function(e,t,n){var r=n("a691"),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},5128:function(e,t,n){"use strict";t.__esModule=!0,t.PopupManager=void 0;var r=n("2b0e"),o=d(r),i=n("7f4d"),a=d(i),s=n("4b26"),l=d(s),u=n("e62d"),c=d(u),f=n("5924");function d(e){return e&&e.__esModule?e:{default:e}}var p=1,h=void 0;t.default={props:{visible:{type:Boolean,default:!1},openDelay:{},closeDelay:{},zIndex:{},modal:{type:Boolean,default:!1},modalFade:{type:Boolean,default:!0},modalClass:{},modalAppendToBody:{type:Boolean,default:!1},lockScroll:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!1},closeOnClickModal:{type:Boolean,default:!1}},beforeMount:function(){this._popupId="popup-"+p++,l.default.register(this._popupId,this)},beforeDestroy:function(){l.default.deregister(this._popupId),l.default.closeModal(this._popupId),this.restoreBodyStyle()},data:function(){return{opened:!1,bodyPaddingRight:null,computedBodyPaddingRight:0,withoutHiddenClass:!0,rendered:!1}},watch:{visible:function(e){var t=this;if(e){if(this._opening)return;this.rendered?this.open():(this.rendered=!0,o.default.nextTick((function(){t.open()})))}else this.close()}},methods:{open:function(e){var t=this;this.rendered||(this.rendered=!0);var n=(0,a.default)({},this.$props||this,e);this._closeTimer&&(clearTimeout(this._closeTimer),this._closeTimer=null),clearTimeout(this._openTimer);var r=Number(n.openDelay);r>0?this._openTimer=setTimeout((function(){t._openTimer=null,t.doOpen(n)}),r):this.doOpen(n)},doOpen:function(e){if(!this.$isServer&&(!this.willOpen||this.willOpen())&&!this.opened){this._opening=!0;var t=this.$el,n=e.modal,r=e.zIndex;if(r&&(l.default.zIndex=r),n&&(this._closing&&(l.default.closeModal(this._popupId),this._closing=!1),l.default.openModal(this._popupId,l.default.nextZIndex(),this.modalAppendToBody?void 0:t,e.modalClass,e.modalFade),e.lockScroll)){this.withoutHiddenClass=!(0,f.hasClass)(document.body,"el-popup-parent--hidden"),this.withoutHiddenClass&&(this.bodyPaddingRight=document.body.style.paddingRight,this.computedBodyPaddingRight=parseInt((0,f.getStyle)(document.body,"paddingRight"),10)),h=(0,c.default)();var o=document.documentElement.clientHeight0&&(o||"scroll"===i)&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.computedBodyPaddingRight+h+"px"),(0,f.addClass)(document.body,"el-popup-parent--hidden")}"static"===getComputedStyle(t).position&&(t.style.position="absolute"),t.style.zIndex=l.default.nextZIndex(),this.opened=!0,this.onOpen&&this.onOpen(),this.doAfterOpen()}},doAfterOpen:function(){this._opening=!1},close:function(){var e=this;if(!this.willClose||this.willClose()){null!==this._openTimer&&(clearTimeout(this._openTimer),this._openTimer=null),clearTimeout(this._closeTimer);var t=Number(this.closeDelay);t>0?this._closeTimer=setTimeout((function(){e._closeTimer=null,e.doClose()}),t):this.doClose()}},doClose:function(){this._closing=!0,this.onClose&&this.onClose(),this.lockScroll&&setTimeout(this.restoreBodyStyle,200),this.opened=!1,this.doAfterClose()},doAfterClose:function(){l.default.closeModal(this._popupId),this._closing=!1},restoreBodyStyle:function(){this.modal&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.bodyPaddingRight,(0,f.removeClass)(document.body,"el-popup-parent--hidden")),this.withoutHiddenClass=!0}}},t.PopupManager=l.default},5135:function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},5466:function(e,t,n){},5692:function(e,t,n){var r=n("c430"),o=n("c6cd");(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.6.5",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},"56ef":function(e,t,n){var r=n("d066"),o=n("241c"),i=n("7418"),a=n("825a");e.exports=r("Reflect","ownKeys")||function(e){var t=o.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},5924:function(e,t,n){"use strict";t.__esModule=!0,t.isInContainer=t.getScrollContainer=t.isScroll=t.getStyle=t.once=t.off=t.on=void 0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.hasClass=v,t.addClass=m,t.removeClass=y,t.setStyle=b;var o=n("2b0e"),i=a(o);function a(e){return e&&e.__esModule?e:{default:e}}var s=i.default.prototype.$isServer,l=/([\:\-\_]+(.))/g,u=/^moz([A-Z])/,c=s?0:Number(document.documentMode),f=function(e){return(e||"").replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g,"")},d=function(e){return e.replace(l,(function(e,t,n,r){return r?n.toUpperCase():n})).replace(u,"Moz$1")},p=t.on=function(){return!s&&document.addEventListener?function(e,t,n){e&&t&&n&&e.addEventListener(t,n,!1)}:function(e,t,n){e&&t&&n&&e.attachEvent("on"+t,n)}}(),h=t.off=function(){return!s&&document.removeEventListener?function(e,t,n){e&&t&&e.removeEventListener(t,n,!1)}:function(e,t,n){e&&t&&e.detachEvent("on"+t,n)}}();t.once=function(e,t,n){var r=function r(){n&&n.apply(this,arguments),h(e,t,r)};p(e,t,r)};function v(e,t){if(!e||!t)return!1;if(-1!==t.indexOf(" "))throw new Error("className should not contain space.");return e.classList?e.classList.contains(t):(" "+e.className+" ").indexOf(" "+t+" ")>-1}function m(e,t){if(e){for(var n=e.className,r=(t||"").split(" "),o=0,i=r.length;or.top&&n.right>r.left&&n.lefte?u():!0!==t&&(o=setTimeout(r?c:u,void 0===r?e-s:e))}return"boolean"!==typeof t&&(r=n,n=t,t=void 0),a}},"5c6c":function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},"60da":function(e,t,n){"use strict";var r=n("83ab"),o=n("d039"),i=n("df75"),a=n("7418"),s=n("d1e7"),l=n("7b0b"),u=n("44ad"),c=Object.assign,f=Object.defineProperty;e.exports=!c||o((function(){if(r&&1!==c({b:1},c(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=c({},e)[n]||i(c({},t)).join("")!=o}))?function(e,t){var n=l(e),o=arguments.length,c=1,f=a.f,d=s.f;while(o>c){var p,h=u(arguments[c++]),v=f?i(h).concat(f(h)):i(h),m=v.length,y=0;while(m>y)p=v[y++],r&&!d.call(h,p)||(n[p]=h[p])}return n}:c},6167:function(e,t,n){"use strict";var r,o;"function"===typeof Symbol&&Symbol.iterator;(function(i,a){r=a,o="function"===typeof r?r.call(t,n,t,e):r,void 0===o||(e.exports=o)})(0,(function(){var e=window,t={placement:"bottom",gpuAcceleration:!0,offset:0,boundariesElement:"viewport",boundariesPadding:5,preventOverflowOrder:["left","right","top","bottom"],flipBehavior:"flip",arrowElement:"[x-arrow]",arrowOffset:0,modifiers:["shift","offset","preventOverflow","keepTogether","arrow","flip","applyStyle"],modifiersIgnored:[],forceAbsolute:!1};function n(e,n,r){this._reference=e.jquery?e[0]:e,this.state={};var o="undefined"===typeof n||null===n,i=n&&"[object Object]"===Object.prototype.toString.call(n);return this._popper=o||i?this.parse(i?n:{}):n.jquery?n[0]:n,this._options=Object.assign({},t,r),this._options.modifiers=this._options.modifiers.map(function(e){if(-1===this._options.modifiersIgnored.indexOf(e))return"applyStyle"===e&&this._popper.setAttribute("x-placement",this._options.placement),this.modifiers[e]||e}.bind(this)),this.state.position=this._getPosition(this._popper,this._reference),f(this._popper,{position:this.state.position,top:0}),this.update(),this._setupEventListeners(),this}function r(t){var n=t.style.display,r=t.style.visibility;t.style.display="block",t.style.visibility="hidden";t.offsetWidth;var o=e.getComputedStyle(t),i=parseFloat(o.marginTop)+parseFloat(o.marginBottom),a=parseFloat(o.marginLeft)+parseFloat(o.marginRight),s={width:t.offsetWidth+a,height:t.offsetHeight+i};return t.style.display=n,t.style.visibility=r,s}function o(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"};return e.replace(/left|right|bottom|top/g,(function(e){return t[e]}))}function i(e){var t=Object.assign({},e);return t.right=t.left+t.width,t.bottom=t.top+t.height,t}function a(e,t){var n,r=0;for(n in e){if(e[n]===t)return r;r++}return null}function s(t,n){var r=e.getComputedStyle(t,null);return r[n]}function l(t){var n=t.offsetParent;return n!==e.document.body&&n?n:e.document.documentElement}function u(t){var n=t.parentNode;return n?n===e.document?e.document.body.scrollTop||e.document.body.scrollLeft?e.document.body:e.document.documentElement:-1!==["scroll","auto"].indexOf(s(n,"overflow"))||-1!==["scroll","auto"].indexOf(s(n,"overflow-x"))||-1!==["scroll","auto"].indexOf(s(n,"overflow-y"))?n:u(t.parentNode):t}function c(t){return t!==e.document.body&&("fixed"===s(t,"position")||(t.parentNode?c(t.parentNode):t))}function f(e,t){function n(e){return""!==e&&!isNaN(parseFloat(e))&&isFinite(e)}Object.keys(t).forEach((function(r){var o="";-1!==["width","height","top","right","bottom","left"].indexOf(r)&&n(t[r])&&(o="px"),e.style[r]=t[r]+o}))}function d(e){var t={};return e&&"[object Function]"===t.toString.call(e)}function p(e){var t={width:e.offsetWidth,height:e.offsetHeight,left:e.offsetLeft,top:e.offsetTop};return t.right=t.left+t.width,t.bottom=t.top+t.height,t}function h(e){var t=e.getBoundingClientRect(),n=-1!=navigator.userAgent.indexOf("MSIE"),r=n&&"HTML"===e.tagName?-e.scrollTop:t.top;return{left:t.left,top:r,right:t.right,bottom:t.bottom,width:t.right-t.left,height:t.bottom-r}}function v(e,t,n){var r=h(e),o=h(t);if(n){var i=u(t);o.top+=i.scrollTop,o.bottom+=i.scrollTop,o.left+=i.scrollLeft,o.right+=i.scrollLeft}var a={top:r.top-o.top,left:r.left-o.left,bottom:r.top-o.top+r.height,right:r.left-o.left+r.width,width:r.width,height:r.height};return a}function m(t){for(var n=["","ms","webkit","moz","o"],r=0;r1&&console.warn("WARNING: the given `parent` query("+t.parent+") matched more than one element, the first one will be used"),0===a.length)throw"ERROR: the given `parent` doesn't exists!";a=a[0]}return a.length>1&&a instanceof Element===!1&&(console.warn("WARNING: you have passed as parent a list of elements, the first one will be used"),a=a[0]),a.appendChild(o),o;function s(e,t){t.forEach((function(t){e.classList.add(t)}))}function l(e,t){t.forEach((function(t){e.setAttribute(t.split(":")[0],t.split(":")[1]||"")}))}},n.prototype._getPosition=function(e,t){var n=l(t);if(this._options.forceAbsolute)return"absolute";var r=c(t,n);return r?"fixed":"absolute"},n.prototype._getOffsets=function(e,t,n){n=n.split("-")[0];var o={};o.position=this.state.position;var i="fixed"===o.position,a=v(t,l(e),i),s=r(e);return-1!==["right","left"].indexOf(n)?(o.top=a.top+a.height/2-s.height/2,o.left="left"===n?a.left-s.width:a.right):(o.left=a.left+a.width/2-s.width/2,o.top="top"===n?a.top-s.height:a.bottom),o.width=s.width,o.height=s.height,{popper:o,reference:a}},n.prototype._setupEventListeners=function(){if(this.state.updateBound=this.update.bind(this),e.addEventListener("resize",this.state.updateBound),"window"!==this._options.boundariesElement){var t=u(this._reference);t!==e.document.body&&t!==e.document.documentElement||(t=e),t.addEventListener("scroll",this.state.updateBound),this.state.scrollTarget=t}},n.prototype._removeEventListeners=function(){e.removeEventListener("resize",this.state.updateBound),"window"!==this._options.boundariesElement&&this.state.scrollTarget&&(this.state.scrollTarget.removeEventListener("scroll",this.state.updateBound),this.state.scrollTarget=null),this.state.updateBound=null},n.prototype._getBoundaries=function(t,n,r){var o,i,a={};if("window"===r){var s=e.document.body,c=e.document.documentElement;i=Math.max(s.scrollHeight,s.offsetHeight,c.clientHeight,c.scrollHeight,c.offsetHeight),o=Math.max(s.scrollWidth,s.offsetWidth,c.clientWidth,c.scrollWidth,c.offsetWidth),a={top:0,right:o,bottom:i,left:0}}else if("viewport"===r){var f=l(this._popper),d=u(this._popper),h=p(f),v=function(e){return e==document.body?Math.max(document.documentElement.scrollTop,document.body.scrollTop):e.scrollTop},m=function(e){return e==document.body?Math.max(document.documentElement.scrollLeft,document.body.scrollLeft):e.scrollLeft},y="fixed"===t.offsets.popper.position?0:v(d),g="fixed"===t.offsets.popper.position?0:m(d);a={top:0-(h.top-y),right:e.document.documentElement.clientWidth-(h.left-g),bottom:e.document.documentElement.clientHeight-(h.top-y),left:0-(h.left-g)}}else a=l(this._popper)===r?{top:0,left:0,right:r.clientWidth,bottom:r.clientHeight}:p(r);return a.left+=n,a.right-=n,a.top=a.top+n,a.bottom=a.bottom-n,a},n.prototype.runModifiers=function(e,t,n){var r=t.slice();return void 0!==n&&(r=this._options.modifiers.slice(0,a(this._options.modifiers,n))),r.forEach(function(t){d(t)&&(e=t.call(this,e))}.bind(this)),e},n.prototype.isModifierRequired=function(e,t){var n=a(this._options.modifiers,e);return!!this._options.modifiers.slice(0,n).filter((function(e){return e===t})).length},n.prototype.modifiers={},n.prototype.modifiers.applyStyle=function(e){var t,n={position:e.offsets.popper.position},r=Math.round(e.offsets.popper.left),o=Math.round(e.offsets.popper.top);return this._options.gpuAcceleration&&(t=m("transform"))?(n[t]="translate3d("+r+"px, "+o+"px, 0)",n.top=0,n.left=0):(n.left=r,n.top=o),Object.assign(n,e.styles),f(this._popper,n),this._popper.setAttribute("x-placement",e.placement),this.isModifierRequired(this.modifiers.applyStyle,this.modifiers.arrow)&&e.offsets.arrow&&f(e.arrowElement,e.offsets.arrow),e},n.prototype.modifiers.shift=function(e){var t=e.placement,n=t.split("-")[0],r=t.split("-")[1];if(r){var o=e.offsets.reference,a=i(e.offsets.popper),s={y:{start:{top:o.top},end:{top:o.top+o.height-a.height}},x:{start:{left:o.left},end:{left:o.left+o.width-a.width}}},l=-1!==["bottom","top"].indexOf(n)?"x":"y";e.offsets.popper=Object.assign(a,s[l][r])}return e},n.prototype.modifiers.preventOverflow=function(e){var t=this._options.preventOverflowOrder,n=i(e.offsets.popper),r={left:function(){var t=n.left;return n.lefte.boundaries.right&&(t=Math.min(n.left,e.boundaries.right-n.width)),{left:t}},top:function(){var t=n.top;return n.tope.boundaries.bottom&&(t=Math.min(n.top,e.boundaries.bottom-n.height)),{top:t}}};return t.forEach((function(t){e.offsets.popper=Object.assign(n,r[t]())})),e},n.prototype.modifiers.keepTogether=function(e){var t=i(e.offsets.popper),n=e.offsets.reference,r=Math.floor;return t.rightr(n.right)&&(e.offsets.popper.left=r(n.right)),t.bottomr(n.bottom)&&(e.offsets.popper.top=r(n.bottom)),e},n.prototype.modifiers.flip=function(e){if(!this.isModifierRequired(this.modifiers.flip,this.modifiers.preventOverflow))return console.warn("WARNING: preventOverflow modifier is required by flip modifier in order to work, be sure to include it before flip!"),e;if(e.flipped&&e.placement===e._originalPlacement)return e;var t=e.placement.split("-")[0],n=o(t),r=e.placement.split("-")[1]||"",a=[];return a="flip"===this._options.flipBehavior?[t,n]:this._options.flipBehavior,a.forEach(function(s,l){if(t===s&&a.length!==l+1){t=e.placement.split("-")[0],n=o(t);var u=i(e.offsets.popper),c=-1!==["right","bottom"].indexOf(t);(c&&Math.floor(e.offsets.reference[t])>Math.floor(u[n])||!c&&Math.floor(e.offsets.reference[t])s[p]&&(e.offsets.popper[f]+=l[f]+h-s[p]);var v=l[f]+(n||l[c]/2-h/2),m=v-s[f];return m=Math.max(Math.min(s[c]-h-8,m),8),o[f]=m,o[d]="",e.offsets.arrow=o,e.arrowElement=t,e},Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:function(e){if(void 0===e||null===e)throw new TypeError("Cannot convert first argument to object");for(var t=Object(e),n=1;n-1}},openDelay:{type:Number,default:0},closeDelay:{type:Number,default:200},title:String,disabled:Boolean,content:String,reference:{},popperClass:String,width:{},visibleArrow:{default:!0},arrowOffset:{type:Number,default:0},transition:{type:String,default:"fade-in-linear"},tabindex:{type:Number,default:0}},computed:{tooltipId:function(){return"el-popover-"+Object(l["generateId"])()}},watch:{showPopper:function(e){this.disabled||(e?this.$emit("show"):this.$emit("hide"))}},mounted:function(){var e=this,t=this.referenceElm=this.reference||this.$refs.reference,n=this.popper||this.$refs.popper;!t&&this.$slots.reference&&this.$slots.reference[0]&&(t=this.referenceElm=this.$slots.reference[0].elm),t&&(Object(s["addClass"])(t,"el-popover__reference"),t.setAttribute("aria-describedby",this.tooltipId),t.setAttribute("tabindex",this.tabindex),n.setAttribute("tabindex",0),"click"!==this.trigger&&(Object(s["on"])(t,"focusin",(function(){e.handleFocus();var n=t.__vue__;n&&"function"===typeof n.focus&&n.focus()})),Object(s["on"])(n,"focusin",this.handleFocus),Object(s["on"])(t,"focusout",this.handleBlur),Object(s["on"])(n,"focusout",this.handleBlur)),Object(s["on"])(t,"keydown",this.handleKeydown),Object(s["on"])(t,"click",this.handleClick)),"click"===this.trigger?(Object(s["on"])(t,"click",this.doToggle),Object(s["on"])(document,"click",this.handleDocumentClick)):"hover"===this.trigger?(Object(s["on"])(t,"mouseenter",this.handleMouseEnter),Object(s["on"])(n,"mouseenter",this.handleMouseEnter),Object(s["on"])(t,"mouseleave",this.handleMouseLeave),Object(s["on"])(n,"mouseleave",this.handleMouseLeave)):"focus"===this.trigger&&(this.tabindex<0&&console.warn("[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key"),t.querySelector("input, textarea")?(Object(s["on"])(t,"focusin",this.doShow),Object(s["on"])(t,"focusout",this.doClose)):(Object(s["on"])(t,"mousedown",this.doShow),Object(s["on"])(t,"mouseup",this.doClose)))},beforeDestroy:function(){this.cleanup()},deactivated:function(){this.cleanup()},methods:{doToggle:function(){this.showPopper=!this.showPopper},doShow:function(){this.showPopper=!0},doClose:function(){this.showPopper=!1},handleFocus:function(){Object(s["addClass"])(this.referenceElm,"focusing"),"click"!==this.trigger&&"focus"!==this.trigger||(this.showPopper=!0)},handleClick:function(){Object(s["removeClass"])(this.referenceElm,"focusing")},handleBlur:function(){Object(s["removeClass"])(this.referenceElm,"focusing"),"click"!==this.trigger&&"focus"!==this.trigger||(this.showPopper=!1)},handleMouseEnter:function(){var e=this;clearTimeout(this._timer),this.openDelay?this._timer=setTimeout((function(){e.showPopper=!0}),this.openDelay):this.showPopper=!0},handleKeydown:function(e){27===e.keyCode&&"manual"!==this.trigger&&this.doClose()},handleMouseLeave:function(){var e=this;clearTimeout(this._timer),this.closeDelay?this._timer=setTimeout((function(){e.showPopper=!1}),this.closeDelay):this.showPopper=!1},handleDocumentClick:function(e){var t=this.reference||this.$refs.reference,n=this.popper||this.$refs.popper;!t&&this.$slots.reference&&this.$slots.reference[0]&&(t=this.referenceElm=this.$slots.reference[0].elm),this.$el&&t&&!this.$el.contains(e.target)&&!t.contains(e.target)&&n&&!n.contains(e.target)&&(this.showPopper=!1)},handleAfterEnter:function(){this.$emit("after-enter")},handleAfterLeave:function(){this.$emit("after-leave"),this.doDestroy()},cleanup:function(){(this.openDelay||this.closeDelay)&&clearTimeout(this._timer)}},destroyed:function(){var e=this.reference;Object(s["off"])(e,"click",this.doToggle),Object(s["off"])(e,"mouseup",this.doClose),Object(s["off"])(e,"mousedown",this.doShow),Object(s["off"])(e,"focusin",this.doShow),Object(s["off"])(e,"focusout",this.doClose),Object(s["off"])(e,"mousedown",this.doShow),Object(s["off"])(e,"mouseup",this.doClose),Object(s["off"])(e,"mouseleave",this.handleMouseLeave),Object(s["off"])(e,"mouseenter",this.handleMouseEnter),Object(s["off"])(document,"click",this.handleDocumentClick)}},c=u,f=n(0),d=Object(f["a"])(c,r,o,!1,null,null,null);d.options.__file="packages/popover/src/main.vue";var p=d.exports,h=function(e,t,n){var r=t.expression?t.value:t.arg,o=n.context.$refs[r];o&&(Array.isArray(o)?o[0].$refs.reference=e:o.$refs.reference=e)},v={bind:function(e,t,n){h(e,t,n)},inserted:function(e,t,n){h(e,t,n)}},m=n(7),y=n.n(m);y.a.directive("popover",v),p.install=function(e){e.directive("popover",v),e.component(p.name,p)},p.directive=v;t["default"]=p}})},"6b7c":function(e,t,n){"use strict";t.__esModule=!0;var r=n("4897");t.default={methods:{t:function(){for(var e=arguments.length,t=Array(e),n=0;n0},e.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),c?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t,r=u.some((function(e){return!!~n.indexOf(e)}));r&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),d=function(e,t){for(var n=0,r=Object.keys(t);n0},e}(),$="undefined"!==typeof WeakMap?new WeakMap:new n,j=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=f.getInstance(),r=new k(t,n,this);$.set(this,r)}return e}();["observe","unobserve","disconnect"].forEach((function(e){j.prototype[e]=function(){var t;return(t=$.get(this))[e].apply(t,arguments)}}));var A=function(){return"undefined"!==typeof o.ResizeObserver?o.ResizeObserver:j}();t["default"]=A}.call(this,n("c8ba"))},"6ed5":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=77)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},10:function(e,t){e.exports=n("f3ad")},13:function(e,t){e.exports=n("eedf")},15:function(e,t){e.exports=n("5128")},19:function(e,t){e.exports=n("4897")},2:function(e,t){e.exports=n("5924")},23:function(e,t){e.exports=n("41f8")},47:function(e,t){e.exports=n("722f")},6:function(e,t){e.exports=n("6b7c")},7:function(e,t){e.exports=n("2b0e")},77:function(e,t,n){"use strict";n.r(t);var r=n(7),o=n.n(r),i=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"msgbox-fade"}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],staticClass:"el-message-box__wrapper",attrs:{tabindex:"-1",role:"dialog","aria-modal":"true","aria-label":e.title||"dialog"},on:{click:function(t){return t.target!==t.currentTarget?null:e.handleWrapperClick(t)}}},[n("div",{staticClass:"el-message-box",class:[e.customClass,e.center&&"el-message-box--center"]},[null!==e.title?n("div",{staticClass:"el-message-box__header"},[n("div",{staticClass:"el-message-box__title"},[e.icon&&e.center?n("div",{class:["el-message-box__status",e.icon]}):e._e(),n("span",[e._v(e._s(e.title))])]),e.showClose?n("button",{staticClass:"el-message-box__headerbtn",attrs:{type:"button","aria-label":"Close"},on:{click:function(t){e.handleAction(e.distinguishCancelAndClose?"close":"cancel")},keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction(e.distinguishCancelAndClose?"close":"cancel")}}},[n("i",{staticClass:"el-message-box__close el-icon-close"})]):e._e()]):e._e(),n("div",{staticClass:"el-message-box__content"},[n("div",{staticClass:"el-message-box__container"},[e.icon&&!e.center&&""!==e.message?n("div",{class:["el-message-box__status",e.icon]}):e._e(),""!==e.message?n("div",{staticClass:"el-message-box__message"},[e._t("default",[e.dangerouslyUseHTMLString?n("p",{domProps:{innerHTML:e._s(e.message)}}):n("p",[e._v(e._s(e.message))])])],2):e._e()]),n("div",{directives:[{name:"show",rawName:"v-show",value:e.showInput,expression:"showInput"}],staticClass:"el-message-box__input"},[n("el-input",{ref:"input",attrs:{type:e.inputType,placeholder:e.inputPlaceholder},nativeOn:{keydown:function(t){return!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleInputEnter(t)}},model:{value:e.inputValue,callback:function(t){e.inputValue=t},expression:"inputValue"}}),n("div",{staticClass:"el-message-box__errormsg",style:{visibility:e.editorErrorMessage?"visible":"hidden"}},[e._v(e._s(e.editorErrorMessage))])],1)]),n("div",{staticClass:"el-message-box__btns"},[e.showCancelButton?n("el-button",{class:[e.cancelButtonClasses],attrs:{loading:e.cancelButtonLoading,round:e.roundButton,size:"small"},on:{keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction("cancel")}},nativeOn:{click:function(t){e.handleAction("cancel")}}},[e._v("\n "+e._s(e.cancelButtonText||e.t("el.messagebox.cancel"))+"\n ")]):e._e(),n("el-button",{directives:[{name:"show",rawName:"v-show",value:e.showConfirmButton,expression:"showConfirmButton"}],ref:"confirm",class:[e.confirmButtonClasses],attrs:{loading:e.confirmButtonLoading,round:e.roundButton,size:"small"},on:{keydown:function(t){if(!("button"in t)&&e._k(t.keyCode,"enter",13,t.key,"Enter"))return null;e.handleAction("confirm")}},nativeOn:{click:function(t){e.handleAction("confirm")}}},[e._v("\n "+e._s(e.confirmButtonText||e.t("el.messagebox.confirm"))+"\n ")])],1)])])])},a=[];i._withStripped=!0;var s=n(15),l=n.n(s),u=n(6),c=n.n(u),f=n(10),d=n.n(f),p=n(13),h=n.n(p),v=n(2),m=n(19),y=n(47),g=n.n(y),b=void 0,_={success:"success",info:"info",warning:"warning",error:"error"},x={mixins:[l.a,c.a],props:{modal:{default:!0},lockScroll:{default:!0},showClose:{type:Boolean,default:!0},closeOnClickModal:{default:!0},closeOnPressEscape:{default:!0},closeOnHashChange:{default:!0},center:{default:!1,type:Boolean},roundButton:{default:!1,type:Boolean}},components:{ElInput:d.a,ElButton:h.a},computed:{icon:function(){var e=this.type,t=this.iconClass;return t||(e&&_[e]?"el-icon-"+_[e]:"")},confirmButtonClasses:function(){return"el-button--primary "+this.confirmButtonClass},cancelButtonClasses:function(){return""+this.cancelButtonClass}},methods:{getSafeClose:function(){var e=this,t=this.uid;return function(){e.$nextTick((function(){t===e.uid&&e.doClose()}))}},doClose:function(){var e=this;this.visible&&(this.visible=!1,this._closing=!0,this.onClose&&this.onClose(),b.closeDialog(),this.lockScroll&&setTimeout(this.restoreBodyStyle,200),this.opened=!1,this.doAfterClose(),setTimeout((function(){e.action&&e.callback(e.action,e)})))},handleWrapperClick:function(){this.closeOnClickModal&&this.handleAction(this.distinguishCancelAndClose?"close":"cancel")},handleInputEnter:function(){if("textarea"!==this.inputType)return this.handleAction("confirm")},handleAction:function(e){("prompt"!==this.$type||"confirm"!==e||this.validate())&&(this.action=e,"function"===typeof this.beforeClose?(this.close=this.getSafeClose(),this.beforeClose(e,this,this.close)):this.doClose())},validate:function(){if("prompt"===this.$type){var e=this.inputPattern;if(e&&!e.test(this.inputValue||""))return this.editorErrorMessage=this.inputErrorMessage||Object(m["t"])("el.messagebox.error"),Object(v["addClass"])(this.getInputElement(),"invalid"),!1;var t=this.inputValidator;if("function"===typeof t){var n=t(this.inputValue);if(!1===n)return this.editorErrorMessage=this.inputErrorMessage||Object(m["t"])("el.messagebox.error"),Object(v["addClass"])(this.getInputElement(),"invalid"),!1;if("string"===typeof n)return this.editorErrorMessage=n,Object(v["addClass"])(this.getInputElement(),"invalid"),!1}}return this.editorErrorMessage="",Object(v["removeClass"])(this.getInputElement(),"invalid"),!0},getFirstFocus:function(){var e=this.$el.querySelector(".el-message-box__btns .el-button"),t=this.$el.querySelector(".el-message-box__btns .el-message-box__title");return e||t},getInputElement:function(){var e=this.$refs.input.$refs;return e.input||e.textarea},handleClose:function(){this.handleAction("close")}},watch:{inputValue:{immediate:!0,handler:function(e){var t=this;this.$nextTick((function(n){"prompt"===t.$type&&null!==e&&t.validate()}))}},visible:function(e){var t=this;e&&(this.uid++,"alert"!==this.$type&&"confirm"!==this.$type||this.$nextTick((function(){t.$refs.confirm.$el.focus()})),this.focusAfterClosed=document.activeElement,b=new g.a(this.$el,this.focusAfterClosed,this.getFirstFocus())),"prompt"===this.$type&&(e?setTimeout((function(){t.$refs.input&&t.$refs.input.$el&&t.getInputElement().focus()}),500):(this.editorErrorMessage="",Object(v["removeClass"])(this.getInputElement(),"invalid")))}},mounted:function(){var e=this;this.$nextTick((function(){e.closeOnHashChange&&window.addEventListener("hashchange",e.close)}))},beforeDestroy:function(){this.closeOnHashChange&&window.removeEventListener("hashchange",this.close),setTimeout((function(){b.closeDialog()}))},data:function(){return{uid:1,title:void 0,message:"",type:"",iconClass:"",customClass:"",showInput:!1,inputValue:null,inputPlaceholder:"",inputType:"text",inputPattern:null,inputValidator:null,inputErrorMessage:"",showConfirmButton:!0,showCancelButton:!1,action:"",confirmButtonText:"",cancelButtonText:"",confirmButtonLoading:!1,cancelButtonLoading:!1,confirmButtonClass:"",confirmButtonDisabled:!1,cancelButtonClass:"",editorErrorMessage:null,callback:null,dangerouslyUseHTMLString:!1,focusAfterClosed:null,isOnComposition:!1,distinguishCancelAndClose:!1}}},w=x,C=n(0),S=Object(C["a"])(w,i,a,!1,null,null,null);S.options.__file="packages/message-box/src/main.vue";var O=S.exports,E=n(9),k=n.n(E),$=n(23),j="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},A={title:null,message:"",type:"",iconClass:"",showInput:!1,showClose:!0,modalFade:!0,lockScroll:!0,closeOnClickModal:!0,closeOnPressEscape:!0,closeOnHashChange:!0,inputValue:null,inputPlaceholder:"",inputType:"text",inputPattern:null,inputValidator:null,inputErrorMessage:"",showConfirmButton:!0,showCancelButton:!1,confirmButtonPosition:"right",confirmButtonHighlight:!1,cancelButtonHighlight:!1,confirmButtonText:"",cancelButtonText:"",confirmButtonClass:"",cancelButtonClass:"",customClass:"",beforeClose:null,dangerouslyUseHTMLString:!1,center:!1,roundButton:!1,distinguishCancelAndClose:!1},T=o.a.extend(O),M=void 0,P=void 0,L=[],R=function(e){if(M){var t=M.callback;"function"===typeof t&&(P.showInput?t(P.inputValue,e):t(e)),M.resolve&&("confirm"===e?P.showInput?M.resolve({value:P.inputValue,action:e}):M.resolve(e):!M.reject||"cancel"!==e&&"close"!==e||M.reject(e))}},N=function(){P=new T({el:document.createElement("div")}),P.callback=R},I=function e(){if(P||N(),P.action="",(!P.visible||P.closeTimer)&&L.length>0){M=L.shift();var t=M.options;for(var n in t)t.hasOwnProperty(n)&&(P[n]=t[n]);void 0===t.callback&&(P.callback=R);var r=P.callback;P.callback=function(t,n){r(t,n),e()},Object($["isVNode"])(P.message)?(P.$slots.default=[P.message],P.message=null):delete P.$slots.default,["modal","showClose","closeOnClickModal","closeOnPressEscape","closeOnHashChange"].forEach((function(e){void 0===P[e]&&(P[e]=!0)})),document.body.appendChild(P.$el),o.a.nextTick((function(){P.visible=!0}))}},F=function e(t,n){if(!o.a.prototype.$isServer){if("string"===typeof t||Object($["isVNode"])(t)?(t={message:t},"string"===typeof arguments[1]&&(t.title=arguments[1])):t.callback&&!n&&(n=t.callback),"undefined"!==typeof Promise)return new Promise((function(r,o){L.push({options:k()({},A,e.defaults,t),callback:n,resolve:r,reject:o}),I()}));L.push({options:k()({},A,e.defaults,t),callback:n}),I()}};F.setDefaults=function(e){F.defaults=e},F.alert=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,$type:"alert",closeOnPressEscape:!1,closeOnClickModal:!1},n))},F.confirm=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,$type:"confirm",showCancelButton:!0},n))},F.prompt=function(e,t,n){return"object"===("undefined"===typeof t?"undefined":j(t))?(n=t,t=""):void 0===t&&(t=""),F(k()({title:t,message:e,showCancelButton:!0,showInput:!0,$type:"prompt"},n))},F.close=function(){P.doClose(),P.visible=!1,L=[],M=null};var H=F;t["default"]=H},9:function(e,t){e.exports=n("7f4d")}})},"6eeb":function(e,t,n){var r=n("da84"),o=n("9112"),i=n("5135"),a=n("ce4e"),s=n("8925"),l=n("69f3"),u=l.get,c=l.enforce,f=String(String).split("String");(e.exports=function(e,t,n,s){var l=!!s&&!!s.unsafe,u=!!s&&!!s.enumerable,d=!!s&&!!s.noTargetGet;"function"==typeof n&&("string"!=typeof t||i(n,"name")||o(n,"name",t),c(n).source=f.join("string"==typeof t?t:"")),e!==r?(l?!d&&e[t]&&(u=!0):delete e[t],u?e[t]=n:o(e,t,n)):u?e[t]=n:a(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&u(this).source||s(this)}))},"722f":function(e,t,n){"use strict";t.__esModule=!0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=n("e452"),i=a(o);function a(e){return e&&e.__esModule?e:{default:e}}var s,l=l||{};l.Dialog=function(e,t,n){var o=this;if(this.dialogNode=e,null===this.dialogNode||"dialog"!==this.dialogNode.getAttribute("role"))throw new Error("Dialog() requires a DOM element with ARIA role of dialog.");"string"===typeof t?this.focusAfterClosed=document.getElementById(t):"object"===("undefined"===typeof t?"undefined":r(t))?this.focusAfterClosed=t:this.focusAfterClosed=null,"string"===typeof n?this.focusFirst=document.getElementById(n):"object"===("undefined"===typeof n?"undefined":r(n))?this.focusFirst=n:this.focusFirst=null,this.focusFirst?this.focusFirst.focus():i.default.focusFirstDescendant(this.dialogNode),this.lastFocus=document.activeElement,s=function(e){o.trapFocus(e)},this.addListeners()},l.Dialog.prototype.addListeners=function(){document.addEventListener("focus",s,!0)},l.Dialog.prototype.removeListeners=function(){document.removeEventListener("focus",s,!0)},l.Dialog.prototype.closeDialog=function(){var e=this;this.removeListeners(),this.focusAfterClosed&&setTimeout((function(){e.focusAfterClosed.focus()}))},l.Dialog.prototype.trapFocus=function(e){i.default.IgnoreUtilFocusChanges||(this.dialogNode.contains(e.target)?this.lastFocus=e.target:(i.default.focusFirstDescendant(this.dialogNode),this.lastFocus===document.activeElement&&i.default.focusLastDescendant(this.dialogNode),this.lastFocus=document.activeElement))},t.default=l.Dialog},7418:function(e,t){t.f=Object.getOwnPropertySymbols},"76b9":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=110)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},110:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"el-breadcrumb",attrs:{"aria-label":"Breadcrumb",role:"navigation"}},[e._t("default")],2)},o=[];r._withStripped=!0;var i={name:"ElBreadcrumb",props:{separator:{type:String,default:"/"},separatorClass:{type:String,default:""}},provide:function(){return{elBreadcrumb:this}},mounted:function(){var e=this.$el.querySelectorAll(".el-breadcrumb__item");e.length&&e[e.length-1].setAttribute("aria-current","page")}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/breadcrumb/src/breadcrumb.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},7839:function(e,t){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"7b0b":function(e,t,n){var r=n("1d80");e.exports=function(e){return Object(r(e))}},"7b3e":function(e,t,n){"use strict";var r,o=n("a3de"); +/** + * Checks if an event is supported in the current execution environment. + * + * NOTE: This will not work correctly for non-generic events such as `change`, + * `reset`, `load`, `error`, and `select`. + * + * Borrows from Modernizr. + * + * @param {string} eventNameSuffix Event name, e.g. "click". + * @param {?boolean} capture Check if the capture phase is supported. + * @return {boolean} True if the event is supported. + * @internal + * @license Modernizr 3.0.0pre (Custom Build) | MIT + */ +function i(e,t){if(!o.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,i=n in document;if(!i){var a=document.createElement("div");a.setAttribute(n,"return;"),i="function"===typeof a[n]}return!i&&r&&"wheel"===e&&(i=document.implementation.hasFeature("Events.wheel","3.0")),i}o.canUseDOM&&(r=document.implementation&&document.implementation.hasFeature&&!0!==document.implementation.hasFeature("","")),e.exports=i},"7bc3":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=118)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},118:function(e,t,n){"use strict";n.r(t);var r=function(e,t){var n=t._c;return n("div",t._g(t._b({class:[t.data.staticClass,"el-divider","el-divider--"+t.props.direction]},"div",t.data.attrs,!1),t.listeners),[t.slots().default&&"vertical"!==t.props.direction?n("div",{class:["el-divider__text","is-"+t.props.contentPosition]},[t._t("default")],2):t._e()])},o=[];r._withStripped=!0;var i={name:"ElDivider",props:{direction:{type:String,default:"horizontal",validator:function(e){return-1!==["horizontal","vertical"].indexOf(e)}},contentPosition:{type:String,default:"center",validator:function(e){return-1!==["left","center","right"].indexOf(e)}}}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!0,null,null,null);l.options.__file="packages/divider/src/main.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},"7c73":function(e,t,n){var r,o=n("825a"),i=n("37e8"),a=n("7839"),s=n("d012"),l=n("1be4"),u=n("cc12"),c=n("f772"),f=">",d="<",p="prototype",h="script",v=c("IE_PROTO"),m=function(){},y=function(e){return d+h+f+e+d+"/"+h+f},g=function(e){e.write(y("")),e.close();var t=e.parentWindow.Object;return e=null,t},b=function(){var e,t=u("iframe"),n="java"+h+":";return t.style.display="none",l.appendChild(t),t.src=String(n),e=t.contentWindow.document,e.open(),e.write(y("document.F=Object")),e.close(),e.F},_=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(t){}_=r?g(r):b();var e=a.length;while(e--)delete _[p][a[e]];return _()};s[v]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(m[p]=o(e),n=new m,m[p]=null,n[v]=e):n=_(),void 0===t?n:i(n,t)}},"7dd0":function(e,t,n){"use strict";var r=n("23e7"),o=n("9ed3"),i=n("e163"),a=n("d2bb"),s=n("d44e"),l=n("9112"),u=n("6eeb"),c=n("b622"),f=n("c430"),d=n("3f8c"),p=n("ae93"),h=p.IteratorPrototype,v=p.BUGGY_SAFARI_ITERATORS,m=c("iterator"),y="keys",g="values",b="entries",_=function(){return this};e.exports=function(e,t,n,c,p,x,w){o(n,t,c);var C,S,O,E=function(e){if(e===p&&T)return T;if(!v&&e in j)return j[e];switch(e){case y:return function(){return new n(this,e)};case g:return function(){return new n(this,e)};case b:return function(){return new n(this,e)}}return function(){return new n(this)}},k=t+" Iterator",$=!1,j=e.prototype,A=j[m]||j["@@iterator"]||p&&j[p],T=!v&&A||E(p),M="Array"==t&&j.entries||A;if(M&&(C=i(M.call(new e)),h!==Object.prototype&&C.next&&(f||i(C)===h||(a?a(C,h):"function"!=typeof C[m]&&l(C,m,_)),s(C,k,!0,!0),f&&(d[k]=_))),p==g&&A&&A.name!==g&&($=!0,T=function(){return A.call(this)}),f&&!w||j[m]===T||l(j,m,T),d[t]=T,p)if(S={values:E(g),keys:x?T:E(y),entries:E(b)},w)for(O in S)(v||$||!(O in j))&&u(j,O,S[O]);else r({target:t,proto:!0,forced:v||$},S);return S}},"7f4d":function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e){for(var t=1,n=arguments.length;t0&&void 0!==arguments[0]?arguments[0]:"";return String(e).replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")};var h=t.arrayFindIndex=function(e,t){for(var n=0;n!==e.length;++n)if(t(e[n]))return n;return-1},v=(t.arrayFind=function(e,t){var n=h(e,t);return-1!==n?e[n]:void 0},t.coerceTruthyValueToArray=function(e){return Array.isArray(e)?e:e?[e]:[]},t.isIE=function(){return!i.default.prototype.$isServer&&!isNaN(Number(document.documentMode))},t.isEdge=function(){return!i.default.prototype.$isServer&&navigator.userAgent.indexOf("Edge")>-1},t.isFirefox=function(){return!i.default.prototype.$isServer&&!!window.navigator.userAgent.match(/firefox/i)},t.autoprefixer=function(e){if("object"!==("undefined"===typeof e?"undefined":r(e)))return e;var t=["transform","transition","animation"],n=["ms-","webkit-"];return t.forEach((function(t){var r=e[t];t&&r&&n.forEach((function(n){e[n+t]=r}))})),e},t.kebabCase=function(e){var t=/([^-])([A-Z])/g;return e.replace(t,"$1-$2").replace(t,"$1-$2").toLowerCase()},t.capitalize=function(e){return(0,a.isString)(e)?e.charAt(0).toUpperCase()+e.slice(1):e},t.looseEqual=function(e,t){var n=(0,a.isObject)(e),r=(0,a.isObject)(t);return n&&r?JSON.stringify(e)===JSON.stringify(t):!n&&!r&&String(e)===String(t)}),m=t.arrayEquals=function(e,t){if(e=e||[],t=t||[],e.length!==t.length)return!1;for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};if(!o.a.prototype.$isServer){if(e=_()({},w,e),"string"===typeof e.target&&(e.target=document.querySelector(e.target)),e.target=e.target||document.body,e.target!==document.body?e.fullscreen=!1:e.body=!0,e.fullscreen&&C)return C;var t=e.body?document.body:e.target,n=new x({el:document.createElement("div"),data:e});return S(e,t,n),"absolute"!==n.originalPosition&&"fixed"!==n.originalPosition&&Object(d["addClass"])(t,"el-loading-parent--relative"),e.fullscreen&&e.lock&&Object(d["addClass"])(t,"el-loading-parent--hidden"),t.appendChild(n.$el),o.a.nextTick((function(){n.visible=!0})),e.fullscreen&&(C=n),n}},E=O;t["default"]={install:function(e){e.use(g),e.prototype.$loading=E},directive:g,service:E}},7:function(e,t){e.exports=n("2b0e")},9:function(e,t){e.exports=n("7f4d")}})},"8bbc":function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=124)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},124:function(e,t,n){"use strict";n.r(t);var r,o,i={name:"ElTag",props:{text:String,closable:Boolean,type:String,hit:Boolean,disableTransitions:Boolean,color:String,size:String,effect:{type:String,default:"light",validator:function(e){return-1!==["dark","light","plain"].indexOf(e)}}},methods:{handleClose:function(e){e.stopPropagation(),this.$emit("close",e)},handleClick:function(e){this.$emit("click",e)}},computed:{tagSize:function(){return this.size||(this.$ELEMENT||{}).size}},render:function(e){var t=this.type,n=this.tagSize,r=this.hit,o=this.effect,i=["el-tag",t?"el-tag--"+t:"",n?"el-tag--"+n:"",o?"el-tag--"+o:"",r&&"is-hit"],a=e("span",{class:i,style:{backgroundColor:this.color},on:{click:this.handleClick}},[this.$slots.default,this.closable&&e("i",{class:"el-tag__close el-icon-close",on:{click:this.handleClose}})]);return this.disableTransitions?a:e("transition",{attrs:{name:"el-zoom-in-center"}},[a])}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/tag/src/tag.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},"8c4f":function(e,t,n){"use strict"; +/*! + * vue-router v3.4.3 + * (c) 2020 Evan You + * @license MIT + */function r(e,t){0}function o(e,t){for(var n in t)e[n]=t[n];return e}var i={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(e,t){var n=t.props,r=t.children,i=t.parent,s=t.data;s.routerView=!0;var l=i.$createElement,u=n.name,c=i.$route,f=i._routerViewCache||(i._routerViewCache={}),d=0,p=!1;while(i&&i._routerRoot!==i){var h=i.$vnode?i.$vnode.data:{};h.routerView&&d++,h.keepAlive&&i._directInactive&&i._inactive&&(p=!0),i=i.$parent}if(s.routerViewDepth=d,p){var v=f[u],m=v&&v.component;return m?(v.configProps&&a(m,s,v.route,v.configProps),l(m,s,r)):l()}var y=c.matched[d],g=y&&y.components[u];if(!y||!g)return f[u]=null,l();f[u]={component:g},s.registerRouteInstance=function(e,t){var n=y.instances[u];(t&&n!==e||!t&&n===e)&&(y.instances[u]=t)},(s.hook||(s.hook={})).prepatch=function(e,t){y.instances[u]=t.componentInstance},s.hook.init=function(e){e.data.keepAlive&&e.componentInstance&&e.componentInstance!==y.instances[u]&&(y.instances[u]=e.componentInstance)};var b=y.props&&y.props[u];return b&&(o(f[u],{route:c,configProps:b}),a(g,s,c,b)),l(g,s,r)}};function a(e,t,n,r){var i=t.props=s(n,r);if(i){i=t.props=o({},i);var a=t.attrs=t.attrs||{};for(var l in i)e.props&&l in e.props||(a[l]=i[l],delete i[l])}}function s(e,t){switch(typeof t){case"undefined":return;case"object":return t;case"function":return t(e);case"boolean":return t?e.params:void 0;default:0}}var l=/[!'()*]/g,u=function(e){return"%"+e.charCodeAt(0).toString(16)},c=/%2C/g,f=function(e){return encodeURIComponent(e).replace(l,u).replace(c,",")},d=decodeURIComponent;function p(e,t,n){void 0===t&&(t={});var r,o=n||v;try{r=o(e||"")}catch(s){r={}}for(var i in t){var a=t[i];r[i]=Array.isArray(a)?a.map(h):h(a)}return r}var h=function(e){return null==e||"object"===typeof e?e:String(e)};function v(e){var t={};return e=e.trim().replace(/^(\?|#|&)/,""),e?(e.split("&").forEach((function(e){var n=e.replace(/\+/g," ").split("="),r=d(n.shift()),o=n.length>0?d(n.join("=")):null;void 0===t[r]?t[r]=o:Array.isArray(t[r])?t[r].push(o):t[r]=[t[r],o]})),t):t}function m(e){var t=e?Object.keys(e).map((function(t){var n=e[t];if(void 0===n)return"";if(null===n)return f(t);if(Array.isArray(n)){var r=[];return n.forEach((function(e){void 0!==e&&(null===e?r.push(f(t)):r.push(f(t)+"="+f(e)))})),r.join("&")}return f(t)+"="+f(n)})).filter((function(e){return e.length>0})).join("&"):null;return t?"?"+t:""}var y=/\/?$/;function g(e,t,n,r){var o=r&&r.options.stringifyQuery,i=t.query||{};try{i=b(i)}catch(s){}var a={name:t.name||e&&e.name,meta:e&&e.meta||{},path:t.path||"/",hash:t.hash||"",query:i,params:t.params||{},fullPath:w(t,o),matched:e?x(e):[]};return n&&(a.redirectedFrom=w(n,o)),Object.freeze(a)}function b(e){if(Array.isArray(e))return e.map(b);if(e&&"object"===typeof e){var t={};for(var n in e)t[n]=b(e[n]);return t}return e}var _=g(null,{path:"/"});function x(e){var t=[];while(e)t.unshift(e),e=e.parent;return t}function w(e,t){var n=e.path,r=e.query;void 0===r&&(r={});var o=e.hash;void 0===o&&(o="");var i=t||m;return(n||"/")+i(r)+o}function C(e,t){return t===_?e===t:!!t&&(e.path&&t.path?e.path.replace(y,"")===t.path.replace(y,"")&&e.hash===t.hash&&S(e.query,t.query):!(!e.name||!t.name)&&(e.name===t.name&&e.hash===t.hash&&S(e.query,t.query)&&S(e.params,t.params)))}function S(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e===t;var n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every((function(n){var r=e[n],o=t[n];return null==r||null==o?r===o:"object"===typeof r&&"object"===typeof o?S(r,o):String(r)===String(o)}))}function O(e,t){return 0===e.path.replace(y,"/").indexOf(t.path.replace(y,"/"))&&(!t.hash||e.hash===t.hash)&&E(e.query,t.query)}function E(e,t){for(var n in t)if(!(n in e))return!1;return!0}function k(e,t,n){var r=e.charAt(0);if("/"===r)return e;if("?"===r||"#"===r)return t+e;var o=t.split("/");n&&o[o.length-1]||o.pop();for(var i=e.replace(/^\//,"").split("/"),a=0;a=0&&(t=e.slice(r),e=e.slice(0,r));var o=e.indexOf("?");return o>=0&&(n=e.slice(o+1),e=e.slice(0,o)),{path:e,query:n,hash:t}}function j(e){return e.replace(/\/\//g,"/")}var A=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)},T=Y,M=I,P=F,L=B,R=X,N=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function I(e,t){var n,r=[],o=0,i=0,a="",s=t&&t.delimiter||"/";while(null!=(n=N.exec(e))){var l=n[0],u=n[1],c=n.index;if(a+=e.slice(i,c),i=c+l.length,u)a+=u[1];else{var f=e[i],d=n[2],p=n[3],h=n[4],v=n[5],m=n[6],y=n[7];a&&(r.push(a),a="");var g=null!=d&&null!=f&&f!==d,b="+"===m||"*"===m,_="?"===m||"*"===m,x=n[2]||s,w=h||v;r.push({name:p||o++,prefix:d||"",delimiter:x,optional:_,repeat:b,partial:g,asterisk:!!y,pattern:w?W(w):y?".*":"[^"+D(x)+"]+?"})}}return i1||!w.length)return 0===w.length?e():e("span",{},w)}if("a"===this.tag)x.on=_,x.attrs={href:l,"aria-current":y};else{var S=ae(this.$slots.default);if(S){S.isStatic=!1;var E=S.data=o({},S.data);for(var k in E.on=E.on||{},E.on){var $=E.on[k];k in _&&(E.on[k]=Array.isArray($)?$:[$])}for(var j in _)j in E.on?E.on[j].push(_[j]):E.on[j]=b;var A=S.data.attrs=o({},S.data.attrs);A.href=l,A["aria-current"]=y}else x.on=_}return e(this.tag,x,this.$slots.default)}};function ie(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&(void 0===e.button||0===e.button)){if(e.currentTarget&&e.currentTarget.getAttribute){var t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function ae(e){if(e)for(var t,n=0;n-1&&(s.params[d]=n.params[d]);return s.path=Z(u.path,s.params,'named route "'+l+'"'),c(u,s,a)}if(s.path){s.params={};for(var p=0;p=e.length?n():e[o]?t(e[o],(function(){r(o+1)})):r(o+1)};r(0)}var Fe={redirected:2,aborted:4,cancelled:8,duplicated:16};function He(e,t){return We(e,t,Fe.redirected,'Redirected when going from "'+e.fullPath+'" to "'+Ue(t)+'" via a navigation guard.')}function ze(e,t){var n=We(e,t,Fe.duplicated,'Avoided redundant navigation to current location: "'+e.fullPath+'".');return n.name="NavigationDuplicated",n}function Be(e,t){return We(e,t,Fe.cancelled,'Navigation cancelled from "'+e.fullPath+'" to "'+t.fullPath+'" with a new navigation.')}function De(e,t){return We(e,t,Fe.aborted,'Navigation aborted from "'+e.fullPath+'" to "'+t.fullPath+'" via a navigation guard.')}function We(e,t,n,r){var o=new Error(r);return o._isRouter=!0,o.from=e,o.to=t,o.type=n,o}var Ve=["params","query","hash"];function Ue(e){if("string"===typeof e)return e;if("path"in e)return e.path;var t={};return Ve.forEach((function(n){n in e&&(t[n]=e[n])})),JSON.stringify(t,null,2)}function qe(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}function Ke(e,t){return qe(e)&&e._isRouter&&(null==t||e.type===t)}function Ge(e){return function(t,n,r){var o=!1,i=0,a=null;Xe(e,(function(e,t,n,s){if("function"===typeof e&&void 0===e.cid){o=!0,i++;var l,u=Qe((function(t){Ze(t)&&(t=t.default),e.resolved="function"===typeof t?t:ee.extend(t),n.components[s]=t,i--,i<=0&&r()})),c=Qe((function(e){var t="Failed to resolve async component "+s+": "+e;a||(a=qe(e)?e:new Error(t),r(a))}));try{l=e(u,c)}catch(d){c(d)}if(l)if("function"===typeof l.then)l.then(u,c);else{var f=l.component;f&&"function"===typeof f.then&&f.then(u,c)}}})),o||r()}}function Xe(e,t){return Ye(e.map((function(e){return Object.keys(e.components).map((function(n){return t(e.components[n],e.instances[n],e,n)}))})))}function Ye(e){return Array.prototype.concat.apply([],e)}var Je="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag;function Ze(e){return e.__esModule||Je&&"Module"===e[Symbol.toStringTag]}function Qe(e){var t=!1;return function(){var n=[],r=arguments.length;while(r--)n[r]=arguments[r];if(!t)return t=!0,e.apply(this,n)}}var et=function(e,t){this.router=e,this.base=tt(t),this.current=_,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function tt(e){if(!e)if(le){var t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^https?:\/\/[^\/]+/,"")}else e="/";return"/"!==e.charAt(0)&&(e="/"+e),e.replace(/\/$/,"")}function nt(e,t){var n,r=Math.max(e.length,t.length);for(n=0;n0)){var t=this.router,n=t.options.scrollBehavior,r=Le&&n;r&&this.listeners.push(we());var o=function(){var n=e.current,o=dt(e.base);e.current===_&&o===e._startLocation||e.transitionTo(o,(function(e){r&&Ce(t,e,n,!0)}))};window.addEventListener("popstate",o),this.listeners.push((function(){window.removeEventListener("popstate",o)}))}},t.prototype.go=function(e){window.history.go(e)},t.prototype.push=function(e,t,n){var r=this,o=this,i=o.current;this.transitionTo(e,(function(e){Re(j(r.base+e.fullPath)),Ce(r.router,e,i,!1),t&&t(e)}),n)},t.prototype.replace=function(e,t,n){var r=this,o=this,i=o.current;this.transitionTo(e,(function(e){Ne(j(r.base+e.fullPath)),Ce(r.router,e,i,!1),t&&t(e)}),n)},t.prototype.ensureURL=function(e){if(dt(this.base)!==this.current.fullPath){var t=j(this.base+this.current.fullPath);e?Re(t):Ne(t)}},t.prototype.getCurrentLocation=function(){return dt(this.base)},t}(et);function dt(e){var t=decodeURI(window.location.pathname);return e&&0===t.toLowerCase().indexOf(e.toLowerCase())&&(t=t.slice(e.length)),(t||"/")+window.location.search+window.location.hash}var pt=function(e){function t(t,n,r){e.call(this,t,n),r&&ht(this.base)||vt()}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.setupListeners=function(){var e=this;if(!(this.listeners.length>0)){var t=this.router,n=t.options.scrollBehavior,r=Le&&n;r&&this.listeners.push(we());var o=function(){var t=e.current;vt()&&e.transitionTo(mt(),(function(n){r&&Ce(e.router,n,t,!0),Le||bt(n.fullPath)}))},i=Le?"popstate":"hashchange";window.addEventListener(i,o),this.listeners.push((function(){window.removeEventListener(i,o)}))}},t.prototype.push=function(e,t,n){var r=this,o=this,i=o.current;this.transitionTo(e,(function(e){gt(e.fullPath),Ce(r.router,e,i,!1),t&&t(e)}),n)},t.prototype.replace=function(e,t,n){var r=this,o=this,i=o.current;this.transitionTo(e,(function(e){bt(e.fullPath),Ce(r.router,e,i,!1),t&&t(e)}),n)},t.prototype.go=function(e){window.history.go(e)},t.prototype.ensureURL=function(e){var t=this.current.fullPath;mt()!==t&&(e?gt(t):bt(t))},t.prototype.getCurrentLocation=function(){return mt()},t}(et);function ht(e){var t=dt(e);if(!/^\/#/.test(t))return window.location.replace(j(e+"/#"+t)),!0}function vt(){var e=mt();return"/"===e.charAt(0)||(bt("/"+e),!1)}function mt(){var e=window.location.href,t=e.indexOf("#");if(t<0)return"";e=e.slice(t+1);var n=e.indexOf("?");if(n<0){var r=e.indexOf("#");e=r>-1?decodeURI(e.slice(0,r))+e.slice(r):decodeURI(e)}else e=decodeURI(e.slice(0,n))+e.slice(n);return e}function yt(e){var t=window.location.href,n=t.indexOf("#"),r=n>=0?t.slice(0,n):t;return r+"#"+e}function gt(e){Le?Re(yt(e)):window.location.hash=e}function bt(e){Le?Ne(yt(e)):window.location.replace(yt(e))}var _t=function(e){function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.push=function(e,t,n){var r=this;this.transitionTo(e,(function(e){r.stack=r.stack.slice(0,r.index+1).concat(e),r.index++,t&&t(e)}),n)},t.prototype.replace=function(e,t,n){var r=this;this.transitionTo(e,(function(e){r.stack=r.stack.slice(0,r.index).concat(e),t&&t(e)}),n)},t.prototype.go=function(e){var t=this,n=this.index+e;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){t.index=n,t.updateRoute(r)}),(function(e){Ke(e,Fe.duplicated)&&(t.index=n)}))}},t.prototype.getCurrentLocation=function(){var e=this.stack[this.stack.length-1];return e?e.fullPath:"/"},t.prototype.ensureURL=function(){},t}(et),xt=function(e){void 0===e&&(e={}),this.app=null,this.apps=[],this.options=e,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=pe(e.routes||[],this);var t=e.mode||"hash";switch(this.fallback="history"===t&&!Le&&!1!==e.fallback,this.fallback&&(t="hash"),le||(t="abstract"),this.mode=t,t){case"history":this.history=new ft(this,e.base);break;case"hash":this.history=new pt(this,e.base,this.fallback);break;case"abstract":this.history=new _t(this,e.base);break;default:0}},wt={currentRoute:{configurable:!0}};function Ct(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function St(e,t,n){var r="hash"===n?"#"+t:t;return e?j(e+"/"+r):r}xt.prototype.match=function(e,t,n){return this.matcher.match(e,t,n)},wt.currentRoute.get=function(){return this.history&&this.history.current},xt.prototype.init=function(e){var t=this;if(this.apps.push(e),e.$once("hook:destroyed",(function(){var n=t.apps.indexOf(e);n>-1&&t.apps.splice(n,1),t.app===e&&(t.app=t.apps[0]||null),t.app||t.history.teardownListeners()})),!this.app){this.app=e;var n=this.history;if(n instanceof ft||n instanceof pt){var r=function(e){var r=n.current,o=t.options.scrollBehavior,i=Le&&o;i&&"fullPath"in e&&Ce(t,e,r,!1)},o=function(e){n.setupListeners(),r(e)};n.transitionTo(n.getCurrentLocation(),o,o)}n.listen((function(e){t.apps.forEach((function(t){t._route=e}))}))}},xt.prototype.beforeEach=function(e){return Ct(this.beforeHooks,e)},xt.prototype.beforeResolve=function(e){return Ct(this.resolveHooks,e)},xt.prototype.afterEach=function(e){return Ct(this.afterHooks,e)},xt.prototype.onReady=function(e,t){this.history.onReady(e,t)},xt.prototype.onError=function(e){this.history.onError(e)},xt.prototype.push=function(e,t,n){var r=this;if(!t&&!n&&"undefined"!==typeof Promise)return new Promise((function(t,n){r.history.push(e,t,n)}));this.history.push(e,t,n)},xt.prototype.replace=function(e,t,n){var r=this;if(!t&&!n&&"undefined"!==typeof Promise)return new Promise((function(t,n){r.history.replace(e,t,n)}));this.history.replace(e,t,n)},xt.prototype.go=function(e){this.history.go(e)},xt.prototype.back=function(){this.go(-1)},xt.prototype.forward=function(){this.go(1)},xt.prototype.getMatchedComponents=function(e){var t=e?e.matched?e:this.resolve(e).route:this.currentRoute;return t?[].concat.apply([],t.matched.map((function(e){return Object.keys(e.components).map((function(t){return e.components[t]}))}))):[]},xt.prototype.resolve=function(e,t,n){t=t||this.history.current;var r=Q(e,t,n,this),o=this.match(r,t),i=o.redirectedFrom||o.fullPath,a=this.history.base,s=St(a,i,this.mode);return{location:r,route:o,href:s,normalizedTo:r,resolved:o}},xt.prototype.addRoutes=function(e){this.matcher.addRoutes(e),this.history.current!==_&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(xt.prototype,wt),xt.install=se,xt.version="3.4.3",xt.isNavigationFailure=Ke,xt.NavigationFailureType=Fe,le&&window.Vue&&window.Vue.use(xt),t["a"]=xt},"8eb7":function(e,t){var n,r,o,i,a,s,l,u,c,f,d,p,h,v,m,y=!1;function g(){if(!y){y=!0;var e=navigator.userAgent,t=/(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(e),g=/(Mac OS X)|(Windows)|(Linux)/.exec(e);if(p=/\b(iPhone|iP[ao]d)/.exec(e),h=/\b(iP[ao]d)/.exec(e),f=/Android/i.exec(e),v=/FBAN\/\w+;/i.exec(e),m=/Mobile/i.exec(e),d=!!/Win64/.exec(e),t){n=t[1]?parseFloat(t[1]):t[5]?parseFloat(t[5]):NaN,n&&document&&document.documentMode&&(n=document.documentMode);var b=/(?:Trident\/(\d+.\d+))/.exec(e);s=b?parseFloat(b[1])+4:n,r=t[2]?parseFloat(t[2]):NaN,o=t[3]?parseFloat(t[3]):NaN,i=t[4]?parseFloat(t[4]):NaN,i?(t=/(?:Chrome\/(\d+\.\d+))/.exec(e),a=t&&t[1]?parseFloat(t[1]):NaN):a=NaN}else n=r=o=a=i=NaN;if(g){if(g[1]){var _=/(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(e);l=!_||parseFloat(_[1].replace("_","."))}else l=!1;u=!!g[2],c=!!g[3]}else l=u=c=!1}}var b={ie:function(){return g()||n},ieCompatibilityMode:function(){return g()||s>n},ie64:function(){return b.ie()&&d},firefox:function(){return g()||r},opera:function(){return g()||o},webkit:function(){return g()||i},safari:function(){return b.webkit()},chrome:function(){return g()||a},windows:function(){return g()||u},osx:function(){return g()||l},linux:function(){return g()||c},iphone:function(){return g()||p},mobile:function(){return g()||p||h||f||m},nativeApp:function(){return g()||v},android:function(){return g()||f},ipad:function(){return g()||h}};e.exports=b},"8f24":function(e,t,n){},"90e3":function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++n+r).toString(36)}},9112:function(e,t,n){var r=n("83ab"),o=n("9bf2"),i=n("5c6c");e.exports=r?function(e,t,n){return o.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},"94ca":function(e,t,n){var r=n("d039"),o=/#|\.prototype\./,i=function(e,t){var n=s[a(e)];return n==u||n!=l&&("function"==typeof t?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},s=i.data={},l=i.NATIVE="N",u=i.POLYFILL="P";e.exports=i},9619:function(e,t,n){var r=n("597f"),o=n("0e15");e.exports={throttle:r,debounce:o}},"9bdd":function(e,t,n){var r=n("825a");e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(a){var i=e["return"];throw void 0!==i&&r(i.call(e)),a}}},"9bf2":function(e,t,n){var r=n("83ab"),o=n("0cfb"),i=n("825a"),a=n("c04e"),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(i(e),t=a(t,!0),i(n),o)try{return s(e,t,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},"9d7e":function(e,t,n){"use strict";t.__esModule=!0;var r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.default=function(e){function t(e){for(var t=arguments.length,n=Array(t>1?t-1:0),a=1;a0?r:n)(e)}},a742:function(e,t,n){"use strict";function r(e){return"[object String]"===Object.prototype.toString.call(e)}function o(e){return"[object Object]"===Object.prototype.toString.call(e)}function i(e){return e&&e.nodeType===Node.ELEMENT_NODE}t.__esModule=!0,t.isString=r,t.isObject=o,t.isHtmlElement=i;t.isFunction=function(e){var t={};return e&&"[object Function]"===t.toString.call(e)},t.isUndefined=function(e){return void 0===e},t.isDefined=function(e){return void 0!==e&&null!==e}},a79d:function(e,t,n){"use strict";var r=n("23e7"),o=n("c430"),i=n("fea9"),a=n("d039"),s=n("d066"),l=n("4840"),u=n("cdf9"),c=n("6eeb"),f=!!i&&a((function(){i.prototype["finally"].call({then:function(){}},(function(){}))}));r({target:"Promise",proto:!0,real:!0,forced:f},{finally:function(e){var t=l(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return u(t,e()).then((function(){return n}))}:e,n?function(n){return u(t,e()).then((function(){throw n}))}:e)}}),o||"function"!=typeof i||i.prototype["finally"]||c(i.prototype,"finally",s("Promise").prototype["finally"])},ad41:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=56)}([function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},,function(e,t){e.exports=n("5924")},function(e,t){e.exports=n("8122")},,function(e,t){e.exports=n("e974")},function(e,t){e.exports=n("6b7c")},function(e,t){e.exports=n("2b0e")},function(e,t,n){"use strict";n.d(t,"b",(function(){return i})),n.d(t,"i",(function(){return s})),n.d(t,"d",(function(){return l})),n.d(t,"e",(function(){return u})),n.d(t,"c",(function(){return c})),n.d(t,"g",(function(){return f})),n.d(t,"f",(function(){return d})),n.d(t,"h",(function(){return h})),n.d(t,"l",(function(){return v})),n.d(t,"k",(function(){return m})),n.d(t,"j",(function(){return y})),n.d(t,"a",(function(){return g})),n.d(t,"m",(function(){return b})),n.d(t,"n",(function(){return _}));var r=n(3),o="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(e){var t=e.target;while(t&&"HTML"!==t.tagName.toUpperCase()){if("TD"===t.tagName.toUpperCase())return t;t=t.parentNode}return null},a=function(e){return null!==e&&"object"===("undefined"===typeof e?"undefined":o(e))},s=function(e,t,n,o,i){if(!t&&!o&&(!i||Array.isArray(i)&&!i.length))return e;n="string"===typeof n?"descending"===n?-1:1:n&&n<0?-1:1;var s=o?null:function(n,o){return i?(Array.isArray(i)||(i=[i]),i.map((function(t){return"string"===typeof t?Object(r["getValueByPath"])(n,t):t(n,o,e)}))):("$key"!==t&&a(n)&&"$value"in n&&(n=n.$value),[a(n)?Object(r["getValueByPath"])(n,t):n])},l=function(e,t){if(o)return o(e.value,t.value);for(var n=0,r=e.key.length;nt.key[n])return 1}return 0};return e.map((function(e,t){return{value:e,index:t,key:s?s(e,t):null}})).sort((function(e,t){var r=l(e,t);return r||(r=e.index-t.index),r*n})).map((function(e){return e.value}))},l=function(e,t){var n=null;return e.columns.forEach((function(e){e.id===t&&(n=e)})),n},u=function(e,t){for(var n=null,r=0;r2&&void 0!==arguments[2]?arguments[2]:"children",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"hasChildren",o=function(e){return!(Array.isArray(e)&&e.length)};function i(e,a,s){t(e,a,s),a.forEach((function(e){if(e[r])t(e,null,s+1);else{var a=e[n];o(a)||i(e,a,s+1)}}))}e.forEach((function(e){if(e[r])t(e,null,0);else{var a=e[n];o(a)||i(e,a,0)}}))}},function(e,t){e.exports=n("7f4d")},,function(e,t){e.exports=n("2bb5")},function(e,t){e.exports=n("417f")},,function(e,t){e.exports=n("14e9")},function(e,t){e.exports=n("5128")},function(e,t){e.exports=n("4010")},function(e,t){e.exports=n("0e15")},function(e,t){e.exports=n("dcdc")},,,,,,,,,,,function(e,t){e.exports=n("299c")},,,,,,,,,,function(e,t){e.exports=n("e62d")},function(e,t){e.exports=n("7fc1")},,,function(e,t){e.exports=n("9619")},,,function(e,t){e.exports=n("c098")},,,,,,,,,,function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"el-table",class:[{"el-table--fit":e.fit,"el-table--striped":e.stripe,"el-table--border":e.border||e.isGroup,"el-table--hidden":e.isHidden,"el-table--group":e.isGroup,"el-table--fluid-height":e.maxHeight,"el-table--scrollable-x":e.layout.scrollX,"el-table--scrollable-y":e.layout.scrollY,"el-table--enable-row-hover":!e.store.states.isComplex,"el-table--enable-row-transition":0!==(e.store.states.data||[]).length&&(e.store.states.data||[]).length<100},e.tableSize?"el-table--"+e.tableSize:""],on:{mouseleave:function(t){e.handleMouseLeave(t)}}},[n("div",{ref:"hiddenColumns",staticClass:"hidden-columns"},[e._t("default")],2),e.showHeader?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleHeaderFooterMousewheel,expression:"handleHeaderFooterMousewheel"}],ref:"headerWrapper",staticClass:"el-table__header-wrapper"},[n("table-header",{ref:"tableHeader",style:{width:e.layout.bodyWidth?e.layout.bodyWidth+"px":""},attrs:{store:e.store,border:e.border,"default-sort":e.defaultSort}})],1):e._e(),n("div",{ref:"bodyWrapper",staticClass:"el-table__body-wrapper",class:[e.layout.scrollX?"is-scrolling-"+e.scrollPosition:"is-scrolling-none"],style:[e.bodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{context:e.context,store:e.store,stripe:e.stripe,"row-class-name":e.rowClassName,"row-style":e.rowStyle,highlight:e.highlightCurrentRow}}),e.data&&0!==e.data.length?e._e():n("div",{ref:"emptyBlock",staticClass:"el-table__empty-block",style:e.emptyBlockStyle},[n("span",{staticClass:"el-table__empty-text"},[e._t("empty",[e._v(e._s(e.emptyText||e.t("el.table.emptyText")))])],2)]),e.$slots.append?n("div",{ref:"appendWrapper",staticClass:"el-table__append-wrapper"},[e._t("append")],2):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"},{name:"mousewheel",rawName:"v-mousewheel",value:e.handleHeaderFooterMousewheel,expression:"handleHeaderFooterMousewheel"}],ref:"footerWrapper",staticClass:"el-table__footer-wrapper"},[n("table-footer",{style:{width:e.layout.bodyWidth?e.layout.bodyWidth+"px":""},attrs:{store:e.store,border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,"default-sort":e.defaultSort}})],1):e._e(),e.fixedColumns.length>0?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleFixedMousewheel,expression:"handleFixedMousewheel"}],ref:"fixedWrapper",staticClass:"el-table__fixed",style:[{width:e.layout.fixedWidth?e.layout.fixedWidth+"px":""},e.fixedHeight]},[e.showHeader?n("div",{ref:"fixedHeaderWrapper",staticClass:"el-table__fixed-header-wrapper"},[n("table-header",{ref:"fixedTableHeader",style:{width:e.bodyWidth},attrs:{fixed:"left",border:e.border,store:e.store}})],1):e._e(),n("div",{ref:"fixedBodyWrapper",staticClass:"el-table__fixed-body-wrapper",style:[{top:e.layout.headerHeight+"px"},e.fixedBodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{fixed:"left",store:e.store,stripe:e.stripe,highlight:e.highlightCurrentRow,"row-class-name":e.rowClassName,"row-style":e.rowStyle}}),e.$slots.append?n("div",{staticClass:"el-table__append-gutter",style:{height:e.layout.appendHeight+"px"}}):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"}],ref:"fixedFooterWrapper",staticClass:"el-table__fixed-footer-wrapper"},[n("table-footer",{style:{width:e.bodyWidth},attrs:{fixed:"left",border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,store:e.store}})],1):e._e()]):e._e(),e.rightFixedColumns.length>0?n("div",{directives:[{name:"mousewheel",rawName:"v-mousewheel",value:e.handleFixedMousewheel,expression:"handleFixedMousewheel"}],ref:"rightFixedWrapper",staticClass:"el-table__fixed-right",style:[{width:e.layout.rightFixedWidth?e.layout.rightFixedWidth+"px":"",right:e.layout.scrollY?(e.border?e.layout.gutterWidth:e.layout.gutterWidth||0)+"px":""},e.fixedHeight]},[e.showHeader?n("div",{ref:"rightFixedHeaderWrapper",staticClass:"el-table__fixed-header-wrapper"},[n("table-header",{ref:"rightFixedTableHeader",style:{width:e.bodyWidth},attrs:{fixed:"right",border:e.border,store:e.store}})],1):e._e(),n("div",{ref:"rightFixedBodyWrapper",staticClass:"el-table__fixed-body-wrapper",style:[{top:e.layout.headerHeight+"px"},e.fixedBodyHeight]},[n("table-body",{style:{width:e.bodyWidth},attrs:{fixed:"right",store:e.store,stripe:e.stripe,"row-class-name":e.rowClassName,"row-style":e.rowStyle,highlight:e.highlightCurrentRow}}),e.$slots.append?n("div",{staticClass:"el-table__append-gutter",style:{height:e.layout.appendHeight+"px"}}):e._e()],1),e.showSummary?n("div",{directives:[{name:"show",rawName:"v-show",value:e.data&&e.data.length>0,expression:"data && data.length > 0"}],ref:"rightFixedFooterWrapper",staticClass:"el-table__fixed-footer-wrapper"},[n("table-footer",{style:{width:e.bodyWidth},attrs:{fixed:"right",border:e.border,"sum-text":e.sumText||e.t("el.table.sumText"),"summary-method":e.summaryMethod,store:e.store}})],1):e._e()]):e._e(),e.rightFixedColumns.length>0?n("div",{ref:"rightFixedPatch",staticClass:"el-table__fixed-right-patch",style:{width:e.layout.scrollY?e.layout.gutterWidth+"px":"0",height:e.layout.headerHeight+"px"}}):e._e(),n("div",{directives:[{name:"show",rawName:"v-show",value:e.resizeProxyVisible,expression:"resizeProxyVisible"}],ref:"resizeProxy",staticClass:"el-table__column-resize-proxy"})])},o=[];r._withStripped=!0;var i=n(18),a=n.n(i),s=n(43),l=n(16),u=n(46),c=n.n(u),f="undefined"!==typeof navigator&&navigator.userAgent.toLowerCase().indexOf("firefox")>-1,d=function(e,t){e&&e.addEventListener&&e.addEventListener(f?"DOMMouseScroll":"mousewheel",(function(e){var n=c()(e);t&&t.apply(this,[e,n])}))},p={bind:function(e,t){d(e,t.value)}},h=n(6),v=n.n(h),m=n(11),y=n.n(m),g=n(7),b=n.n(g),_=n(9),x=n.n(_),w=n(8),C={data:function(){return{states:{defaultExpandAll:!1,expandRows:[]}}},methods:{updateExpandRows:function(){var e=this.states,t=e.data,n=void 0===t?[]:t,r=e.rowKey,o=e.defaultExpandAll,i=e.expandRows;if(o)this.states.expandRows=n.slice();else if(r){var a=Object(w["f"])(i,r);this.states.expandRows=n.reduce((function(e,t){var n=Object(w["g"])(t,r),o=a[n];return o&&e.push(t),e}),[])}else this.states.expandRows=[]},toggleRowExpansion:function(e,t){var n=Object(w["m"])(this.states.expandRows,e,t);n&&(this.table.$emit("expand-change",e,this.states.expandRows.slice()),this.scheduleLayout())},setExpandRowKeys:function(e){this.assertRowKey();var t=this.states,n=t.data,r=t.rowKey,o=Object(w["f"])(n,r);this.states.expandRows=e.reduce((function(e,t){var n=o[t];return n&&e.push(n.row),e}),[])},isRowExpanded:function(e){var t=this.states,n=t.expandRows,r=void 0===n?[]:n,o=t.rowKey;if(o){var i=Object(w["f"])(r,o);return!!i[Object(w["g"])(e,o)]}return-1!==r.indexOf(e)}}},S=n(3),O={data:function(){return{states:{_currentRowKey:null,currentRow:null}}},methods:{setCurrentRowKey:function(e){this.assertRowKey(),this.states._currentRowKey=e,this.setCurrentRowByKey(e)},restoreCurrentRowKey:function(){this.states._currentRowKey=null},setCurrentRowByKey:function(e){var t=this.states,n=t.data,r=void 0===n?[]:n,o=t.rowKey,i=null;o&&(i=Object(S["arrayFind"])(r,(function(t){return Object(w["g"])(t,o)===e}))),t.currentRow=i},updateCurrentRow:function(e){var t=this.states,n=this.table,r=t.currentRow;if(e&&e!==r)return t.currentRow=e,void n.$emit("current-change",e,r);!e&&r&&(t.currentRow=null,n.$emit("current-change",null,r))},updateCurrentRowData:function(){var e=this.states,t=this.table,n=e.rowKey,r=e._currentRowKey,o=e.data||[],i=e.currentRow;if(-1===o.indexOf(i)&&i){if(n){var a=Object(w["g"])(i,n);this.setCurrentRowByKey(a)}else e.currentRow=null;null===e.currentRow&&t.$emit("current-change",null,i)}else r&&(this.setCurrentRowByKey(r),this.restoreCurrentRowKey())}}},E=Object.assign||function(e){for(var t=1;t0&&t[0]&&"selection"===t[0].type&&!t[0].fixed&&(t[0].fixed=!0,e.fixedColumns.unshift(t[0]));var n=t.filter((function(e){return!e.fixed}));e.originColumns=[].concat(e.fixedColumns).concat(n).concat(e.rightFixedColumns);var r=j(n),o=j(e.fixedColumns),i=j(e.rightFixedColumns);e.leafColumnsLength=r.length,e.fixedLeafColumnsLength=o.length,e.rightFixedLeafColumnsLength=i.length,e.columns=[].concat(o).concat(r).concat(i),e.isComplex=e.fixedColumns.length>0||e.rightFixedColumns.length>0},scheduleLayout:function(e){e&&this.updateColumns(),this.table.debouncedUpdateLayout()},isSelected:function(e){var t=this.states.selection,n=void 0===t?[]:t;return n.indexOf(e)>-1},clearSelection:function(){var e=this.states;e.isAllSelected=!1;var t=e.selection;t.length&&(e.selection=[],this.table.$emit("selection-change",[]))},cleanSelection:function(){var e=this.states,t=e.data,n=e.rowKey,r=e.selection,o=void 0;if(n){o=[];var i=Object(w["f"])(r,n),a=Object(w["f"])(t,n);for(var s in i)i.hasOwnProperty(s)&&!a[s]&&o.push(i[s].row)}else o=r.filter((function(e){return-1===t.indexOf(e)}));if(o.length){var l=r.filter((function(e){return-1===o.indexOf(e)}));e.selection=l,this.table.$emit("selection-change",l.slice())}},toggleRowSelection:function(e,t){var n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=Object(w["m"])(this.states.selection,e,t);if(r){var o=(this.states.selection||[]).slice();n&&this.table.$emit("select",o,e),this.table.$emit("selection-change",o)}},_toggleAllSelection:function(){var e=this.states,t=e.data,n=void 0===t?[]:t,r=e.selection,o=e.selectOnIndeterminate?!e.isAllSelected:!(e.isAllSelected||r.length);e.isAllSelected=o;var i=!1;n.forEach((function(t,n){e.selectable?e.selectable.call(null,t,n)&&Object(w["m"])(r,t,o)&&(i=!0):Object(w["m"])(r,t,o)&&(i=!0)})),i&&this.table.$emit("selection-change",r?r.slice():[]),this.table.$emit("select-all",r)},updateSelectionByRowKey:function(){var e=this.states,t=e.selection,n=e.rowKey,r=e.data,o=Object(w["f"])(t,n);r.forEach((function(e){var r=Object(w["g"])(e,n),i=o[r];i&&(t[i.index]=e)}))},updateAllSelected:function(){var e=this.states,t=e.selection,n=e.rowKey,r=e.selectable,o=e.data||[];if(0!==o.length){var i=void 0;n&&(i=Object(w["f"])(t,n));for(var a=function(e){return i?!!i[Object(w["g"])(e,n)]:-1!==t.indexOf(e)},s=!0,l=0,u=0,c=o.length;u1?n-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:{};if(!e)throw new Error("Table is required.");var n=new T;return n.table=e,n.toggleAllSelection=P()(10,n._toggleAllSelection),Object.keys(t).forEach((function(e){n.states[e]=t[e]})),n}function R(e){var t={};return Object.keys(e).forEach((function(n){var r=e[n],o=void 0;"string"===typeof r?o=function(){return this.store.states[r]}:"function"===typeof r?o=function(){return r.call(this,this.store.states)}:console.error("invalid value type"),o&&(t[n]=o)})),t}var N=n(39),I=n.n(N);function F(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var H=function(){function e(t){for(var n in F(this,e),this.observers=[],this.table=null,this.store=null,this.columns=null,this.fit=!0,this.showHeader=!0,this.height=null,this.scrollX=!1,this.scrollY=!1,this.bodyWidth=null,this.fixedWidth=null,this.rightFixedWidth=null,this.tableHeight=null,this.headerHeight=44,this.appendHeight=0,this.footerHeight=44,this.viewportHeight=null,this.bodyHeight=null,this.fixedBodyHeight=null,this.gutterWidth=I()(),t)t.hasOwnProperty(n)&&(this[n]=t[n]);if(!this.table)throw new Error("table is required for Table Layout");if(!this.store)throw new Error("store is required for Table Layout")}return e.prototype.updateScrollY=function(){var e=this.height;if(null===e)return!1;var t=this.table.bodyWrapper;if(this.table.$el&&t){var n=t.querySelector(".el-table__body"),r=this.scrollY,o=n.offsetHeight>this.bodyHeight;return this.scrollY=o,r!==o}return!1},e.prototype.setHeight=function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"height";if(!b.a.prototype.$isServer){var r=this.table.$el;if(e=Object(w["j"])(e),this.height=e,!r&&(e||0===e))return b.a.nextTick((function(){return t.setHeight(e,n)}));"number"===typeof e?(r.style[n]=e+"px",this.updateElsHeight()):"string"===typeof e&&(r.style[n]=e,this.updateElsHeight())}},e.prototype.setMaxHeight=function(e){this.setHeight(e,"max-height")},e.prototype.getFlattenColumns=function(){var e=[],t=this.table.columns;return t.forEach((function(t){t.isColumnGroup?e.push.apply(e,t.columns):e.push(t)})),e},e.prototype.updateElsHeight=function(){var e=this;if(!this.table.$ready)return b.a.nextTick((function(){return e.updateElsHeight()}));var t=this.table.$refs,n=t.headerWrapper,r=t.appendWrapper,o=t.footerWrapper;if(this.appendHeight=r?r.offsetHeight:0,!this.showHeader||n){var i=n?n.querySelector(".el-table__header tr"):null,a=this.headerDisplayNone(i),s=this.headerHeight=this.showHeader?n.offsetHeight:0;if(this.showHeader&&!a&&n.offsetWidth>0&&(this.table.columns||[]).length>0&&s<2)return b.a.nextTick((function(){return e.updateElsHeight()}));var l=this.tableHeight=this.table.$el.clientHeight,u=this.footerHeight=o?o.offsetHeight:0;null!==this.height&&(this.bodyHeight=l-s-u+(o?1:0)),this.fixedBodyHeight=this.scrollX?this.bodyHeight-this.gutterWidth:this.bodyHeight;var c=!(this.store.states.data&&this.store.states.data.length);this.viewportHeight=this.scrollX?l-(c?0:this.gutterWidth):l,this.updateScrollY(),this.notifyObservers("scrollable")}},e.prototype.headerDisplayNone=function(e){if(!e)return!0;var t=e;while("DIV"!==t.tagName){if("none"===getComputedStyle(t).display)return!0;t=t.parentElement}return!1},e.prototype.updateColumnsWidth=function(){if(!b.a.prototype.$isServer){var e=this.fit,t=this.table.$el.clientWidth,n=0,r=this.getFlattenColumns(),o=r.filter((function(e){return"number"!==typeof e.width}));if(r.forEach((function(e){"number"===typeof e.width&&e.realWidth&&(e.realWidth=null)})),o.length>0&&e){r.forEach((function(e){n+=e.width||e.minWidth||80}));var i=this.scrollY?this.gutterWidth:0;if(n<=t-i){this.scrollX=!1;var a=t-i-n;if(1===o.length)o[0].realWidth=(o[0].minWidth||80)+a;else{var s=o.reduce((function(e,t){return e+(t.minWidth||80)}),0),l=a/s,u=0;o.forEach((function(e,t){if(0!==t){var n=Math.floor((e.minWidth||80)*l);u+=n,e.realWidth=(e.minWidth||80)+n}})),o[0].realWidth=(o[0].minWidth||80)+a-u}}else this.scrollX=!0,o.forEach((function(e){e.realWidth=e.minWidth}));this.bodyWidth=Math.max(n,t),this.table.resizeState.width=this.bodyWidth}else r.forEach((function(e){e.width||e.minWidth?e.realWidth=e.width||e.minWidth:e.realWidth=80,n+=e.realWidth})),this.scrollX=n>t,this.bodyWidth=n;var c=this.store.states.fixedColumns;if(c.length>0){var f=0;c.forEach((function(e){f+=e.realWidth||e.width})),this.fixedWidth=f}var d=this.store.states.rightFixedColumns;if(d.length>0){var p=0;d.forEach((function(e){p+=e.realWidth||e.width})),this.rightFixedWidth=p}this.notifyObservers("columns")}},e.prototype.addObserver=function(e){this.observers.push(e)},e.prototype.removeObserver=function(e){var t=this.observers.indexOf(e);-1!==t&&this.observers.splice(t,1)},e.prototype.notifyObservers=function(e){var t=this,n=this.observers;n.forEach((function(n){switch(e){case"columns":n.onColumnsChange(t);break;case"scrollable":n.onScrollableChange(t);break;default:throw new Error("Table Layout don't have event "+e+".")}}))},e}(),z=H,B=n(2),D=n(29),W=n.n(D),V={created:function(){this.tableLayout.addObserver(this)},destroyed:function(){this.tableLayout.removeObserver(this)},computed:{tableLayout:function(){var e=this.layout;if(!e&&this.table&&(e=this.table.layout),!e)throw new Error("Can not find table layout.");return e}},mounted:function(){this.onColumnsChange(this.tableLayout),this.onScrollableChange(this.tableLayout)},updated:function(){this.__updated__||(this.onColumnsChange(this.tableLayout),this.onScrollableChange(this.tableLayout),this.__updated__=!0)},methods:{onColumnsChange:function(e){var t=this.$el.querySelectorAll("colgroup > col");if(t.length){var n=e.getFlattenColumns(),r={};n.forEach((function(e){r[e.id]=e}));for(var o=0,i=t.length;o col[name=gutter]"),n=0,r=t.length;n=this.leftFixedLeafCount:"right"===this.fixed?e=this.columnsCount-this.rightFixedLeafCount},getSpan:function(e,t,n,r){var o=1,i=1,a=this.table.spanMethod;if("function"===typeof a){var s=a({row:e,column:t,rowIndex:n,columnIndex:r});Array.isArray(s)?(o=s[0],i=s[1]):"object"===("undefined"===typeof s?"undefined":U(s))&&(o=s.rowspan,i=s.colspan)}return{rowspan:o,colspan:i}},getRowStyle:function(e,t){var n=this.table.rowStyle;return"function"===typeof n?n.call(null,{row:e,rowIndex:t}):n||null},getRowClass:function(e,t){var n=["el-table__row"];this.table.highlightCurrentRow&&e===this.store.states.currentRow&&n.push("current-row"),this.stripe&&t%2===1&&n.push("el-table__row--striped");var r=this.table.rowClassName;return"string"===typeof r?n.push(r):"function"===typeof r&&n.push(r.call(null,{row:e,rowIndex:t})),this.store.states.expandRows.indexOf(e)>-1&&n.push("expanded"),n},getCellStyle:function(e,t,n,r){var o=this.table.cellStyle;return"function"===typeof o?o.call(null,{rowIndex:e,columnIndex:t,row:n,column:r}):o},getCellClass:function(e,t,n,r){var o=[r.id,r.align,r.className];this.isColumnHidden(t)&&o.push("is-hidden");var i=this.table.cellClassName;return"string"===typeof i?o.push(i):"function"===typeof i&&o.push(i.call(null,{rowIndex:e,columnIndex:t,row:n,column:r})),o.join(" ")},getColspanRealWidth:function(e,t,n){if(t<1)return e[n].realWidth;var r=e.map((function(e){var t=e.realWidth;return t})).slice(n,n+t);return r.reduce((function(e,t){return e+t}),-1)},handleCellMouseEnter:function(e,t){var n=this.table,r=Object(w["b"])(e);if(r){var o=Object(w["c"])(n,r),i=n.hoverState={cell:r,column:o,row:t};n.$emit("cell-mouse-enter",i.row,i.column,i.cell,e)}var a=e.target.querySelector(".cell");if(Object(B["hasClass"])(a,"el-tooltip")&&a.childNodes.length){var s=document.createRange();s.setStart(a,0),s.setEnd(a,a.childNodes.length);var l=s.getBoundingClientRect().width,u=(parseInt(Object(B["getStyle"])(a,"paddingLeft"),10)||0)+(parseInt(Object(B["getStyle"])(a,"paddingRight"),10)||0);if((l+u>a.offsetWidth||a.scrollWidth>a.offsetWidth)&&this.$refs.tooltip){var c=this.$refs.tooltip;this.tooltipContent=r.innerText||r.textContent,c.referenceElm=r,c.$refs.popper&&(c.$refs.popper.style.display="none"),c.doDestroy(),c.setExpectedState(!0),this.activateTooltip(c)}}},handleCellMouseLeave:function(e){var t=this.$refs.tooltip;t&&(t.setExpectedState(!1),t.handleClosePopper());var n=Object(w["b"])(e);if(n){var r=this.table.hoverState||{};this.table.$emit("cell-mouse-leave",r.row,r.column,r.cell,e)}},handleMouseEnter:P()(30,(function(e){this.store.commit("setHoverRow",e)})),handleMouseLeave:P()(30,(function(){this.store.commit("setHoverRow",null)})),handleContextMenu:function(e,t){this.handleEvent(e,t,"contextmenu")},handleDoubleClick:function(e,t){this.handleEvent(e,t,"dblclick")},handleClick:function(e,t){this.store.commit("setCurrentRow",t),this.handleEvent(e,t,"click")},handleEvent:function(e,t,n){var r=this.table,o=Object(w["b"])(e),i=void 0;o&&(i=Object(w["c"])(r,o),i&&r.$emit("cell-"+n,t,i,o,e)),r.$emit("row-"+n,t,i,e)},rowRender:function(e,t,n){var r=this,o=this.$createElement,i=this.treeIndent,a=this.columns,s=this.firstDefaultColumnIndex,l=a.map((function(e,t){return r.isColumnHidden(t)})),u=this.getRowClass(e,t),c=!0;n&&(u.push("el-table__row--level-"+n.level),c=n.display);var f=c?null:{display:"none"};return o("tr",{style:[f,this.getRowStyle(e,t)],class:u,key:this.getKeyOfRow(e,t),on:{dblclick:function(t){return r.handleDoubleClick(t,e)},click:function(t){return r.handleClick(t,e)},contextmenu:function(t){return r.handleContextMenu(t,e)},mouseenter:function(e){return r.handleMouseEnter(t)},mouseleave:this.handleMouseLeave}},[a.map((function(u,c){var f=r.getSpan(e,u,t,c),d=f.rowspan,p=f.colspan;if(!d||!p)return null;var h=q({},u);h.realWidth=r.getColspanRealWidth(a,p,c);var v={store:r.store,_self:r.context||r.table.$vnode.context,column:h,row:e,$index:t};return c===s&&n&&(v.treeNode={indent:n.level*i,level:n.level},"boolean"===typeof n.expanded&&(v.treeNode.expanded=n.expanded,"loading"in n&&(v.treeNode.loading=n.loading),"noLazyChildren"in n&&(v.treeNode.noLazyChildren=n.noLazyChildren))),o("td",{style:r.getCellStyle(t,c,e,u),class:r.getCellClass(t,c,e,u),attrs:{rowspan:d,colspan:p},on:{mouseenter:function(t){return r.handleCellMouseEnter(t,e)},mouseleave:r.handleCellMouseLeave}},[u.renderCell.call(r._renderProxy,r.$createElement,v,l[c])])}))])},wrappedRowRender:function(e,t){var n=this,r=this.$createElement,o=this.store,i=o.isRowExpanded,a=o.assertRowKey,s=o.states,l=s.treeData,u=s.lazyTreeNodeMap,c=s.childrenColumnName,f=s.rowKey;if(this.hasExpandColumn&&i(e)){var d=this.table.renderExpanded,p=this.rowRender(e,t);return d?[[p,r("tr",{key:"expanded-row__"+p.key},[r("td",{attrs:{colspan:this.columnsCount},class:"el-table__expanded-cell"},[d(this.$createElement,{row:e,$index:t,store:this.store})])])]]:(console.error("[Element Error]renderExpanded is required."),p)}if(Object.keys(l).length){a();var h=Object(w["g"])(e,f),v=l[h],m=null;v&&(m={expanded:v.expanded,level:v.level,display:!0},"boolean"===typeof v.lazy&&("boolean"===typeof v.loaded&&v.loaded&&(m.noLazyChildren=!(v.children&&v.children.length)),m.loading=v.loading));var y=[this.rowRender(e,t,m)];if(v){var g=0,b=function e(r,o){r&&r.length&&o&&r.forEach((function(r){var i={display:o.display&&o.expanded,level:o.level+1},a=Object(w["g"])(r,f);if(void 0===a||null===a)throw new Error("for nested data item, row-key is required.");if(v=q({},l[a]),v&&(i.expanded=v.expanded,v.level=v.level||i.level,v.display=!(!v.expanded||!i.display),"boolean"===typeof v.lazy&&("boolean"===typeof v.loaded&&v.loaded&&(i.noLazyChildren=!(v.children&&v.children.length)),i.loading=v.loading)),g++,y.push(n.rowRender(r,t+g,i)),v){var s=u[a]||r[c];e(s,v)}}))};v.display=!0;var _=u[h]||e[c];b(_,v)}return y}return this.rowRender(e,t)}}},G=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("transition",{attrs:{name:"el-zoom-in-top"}},[e.multiple?n("div",{directives:[{name:"clickoutside",rawName:"v-clickoutside",value:e.handleOutsideClick,expression:"handleOutsideClick"},{name:"show",rawName:"v-show",value:e.showPopper,expression:"showPopper"}],staticClass:"el-table-filter"},[n("div",{staticClass:"el-table-filter__content"},[n("el-scrollbar",{attrs:{"wrap-class":"el-table-filter__wrap"}},[n("el-checkbox-group",{staticClass:"el-table-filter__checkbox-group",model:{value:e.filteredValue,callback:function(t){e.filteredValue=t},expression:"filteredValue"}},e._l(e.filters,(function(t){return n("el-checkbox",{key:t.value,attrs:{label:t.value}},[e._v(e._s(t.text))])})),1)],1)],1),n("div",{staticClass:"el-table-filter__bottom"},[n("button",{class:{"is-disabled":0===e.filteredValue.length},attrs:{disabled:0===e.filteredValue.length},on:{click:e.handleConfirm}},[e._v(e._s(e.t("el.table.confirmFilter")))]),n("button",{on:{click:e.handleReset}},[e._v(e._s(e.t("el.table.resetFilter")))])])]):n("div",{directives:[{name:"clickoutside",rawName:"v-clickoutside",value:e.handleOutsideClick,expression:"handleOutsideClick"},{name:"show",rawName:"v-show",value:e.showPopper,expression:"showPopper"}],staticClass:"el-table-filter"},[n("ul",{staticClass:"el-table-filter__list"},[n("li",{staticClass:"el-table-filter__list-item",class:{"is-active":void 0===e.filterValue||null===e.filterValue},on:{click:function(t){e.handleSelect(null)}}},[e._v(e._s(e.t("el.table.clearFilter")))]),e._l(e.filters,(function(t){return n("li",{key:t.value,staticClass:"el-table-filter__list-item",class:{"is-active":e.isActive(t)},attrs:{label:t.value},on:{click:function(n){e.handleSelect(t.value)}}},[e._v(e._s(t.text))])}))],2)])])},X=[];G._withStripped=!0;var Y=n(5),J=n.n(Y),Z=n(15),Q=n(12),ee=n.n(Q),te=[];!b.a.prototype.$isServer&&document.addEventListener("click",(function(e){te.forEach((function(t){var n=e.target;t&&t.$el&&(n===t.$el||t.$el.contains(n)||t.handleOutsideClick&&t.handleOutsideClick(e))}))}));var ne={open:function(e){e&&te.push(e)},close:function(e){var t=te.indexOf(e);-1!==t&&te.splice(e,1)}},re=n(40),oe=n.n(re),ie=n(14),ae=n.n(ie),se={name:"ElTableFilterPanel",mixins:[J.a,v.a],directives:{Clickoutside:ee.a},components:{ElCheckbox:a.a,ElCheckboxGroup:oe.a,ElScrollbar:ae.a},props:{placement:{type:String,default:"bottom-end"}},methods:{isActive:function(e){return e.value===this.filterValue},handleOutsideClick:function(){var e=this;setTimeout((function(){e.showPopper=!1}),16)},handleConfirm:function(){this.confirmFilter(this.filteredValue),this.handleOutsideClick()},handleReset:function(){this.filteredValue=[],this.confirmFilter(this.filteredValue),this.handleOutsideClick()},handleSelect:function(e){this.filterValue=e,"undefined"!==typeof e&&null!==e?this.confirmFilter(this.filteredValue):this.confirmFilter([]),this.handleOutsideClick()},confirmFilter:function(e){this.table.store.commit("filterChange",{column:this.column,values:e}),this.table.store.updateAllSelected()}},data:function(){return{table:null,cell:null,column:null}},computed:{filters:function(){return this.column&&this.column.filters},filterValue:{get:function(){return(this.column.filteredValue||[])[0]},set:function(e){this.filteredValue&&("undefined"!==typeof e&&null!==e?this.filteredValue.splice(0,1,e):this.filteredValue.splice(0,1))}},filteredValue:{get:function(){return this.column&&this.column.filteredValue||[]},set:function(e){this.column&&(this.column.filteredValue=e)}},multiple:function(){return!this.column||this.column.filterMultiple}},mounted:function(){var e=this;this.popperElm=this.$el,this.referenceElm=this.cell,this.table.bodyWrapper.addEventListener("scroll",(function(){e.updatePopper()})),this.$watch("showPopper",(function(t){e.column&&(e.column.filterOpened=t),t?ne.open(e):ne.close(e)}))},watch:{showPopper:function(e){!0===e&&parseInt(this.popperJS._popper.style.zIndex,10)1;return o&&(this.$parent.isGroup=!0),e("table",{class:"el-table__header",attrs:{cellspacing:"0",cellpadding:"0",border:"0"}},[e("colgroup",[this.columns.map((function(t){return e("col",{attrs:{name:t.id},key:t.id})})),this.hasGutter?e("col",{attrs:{name:"gutter"}}):""]),e("thead",{class:[{"is-group":o,"has-gutter":this.hasGutter}]},[this._l(r,(function(n,r){return e("tr",{style:t.getHeaderRowStyle(r),class:t.getHeaderRowClass(r)},[n.map((function(o,i){return e("th",{attrs:{colspan:o.colSpan,rowspan:o.rowSpan},on:{mousemove:function(e){return t.handleMouseMove(e,o)},mouseout:t.handleMouseOut,mousedown:function(e){return t.handleMouseDown(e,o)},click:function(e){return t.handleHeaderClick(e,o)},contextmenu:function(e){return t.handleHeaderContextMenu(e,o)}},style:t.getHeaderCellStyle(r,i,n,o),class:t.getHeaderCellClass(r,i,n,o),key:o.id},[e("div",{class:["cell",o.filteredValue&&o.filteredValue.length>0?"highlight":"",o.labelClassName]},[o.renderHeader?o.renderHeader.call(t._renderProxy,e,{column:o,$index:i,store:t.store,_self:t.$parent.$vnode.context}):o.label,o.sortable?e("span",{class:"caret-wrapper",on:{click:function(e){return t.handleSortClick(e,o)}}},[e("i",{class:"sort-caret ascending",on:{click:function(e){return t.handleSortClick(e,o,"ascending")}}}),e("i",{class:"sort-caret descending",on:{click:function(e){return t.handleSortClick(e,o,"descending")}}})]):"",o.filterable?e("span",{class:"el-table__column-filter-trigger",on:{click:function(e){return t.handleFilterClick(e,o)}}},[e("i",{class:["el-icon-arrow-down",o.filterOpened?"el-icon-arrow-up":""]})]):""])])})),t.hasGutter?e("th",{class:"gutter"}):""])}))])])},props:{fixed:String,store:{required:!0},border:Boolean,defaultSort:{type:Object,default:function(){return{prop:"",order:""}}}},components:{ElCheckbox:a.a},computed:de({table:function(){return this.$parent},hasGutter:function(){return!this.fixed&&this.tableLayout.gutterWidth}},R({columns:"columns",isAllSelected:"isAllSelected",leftFixedLeafCount:"fixedLeafColumnsLength",rightFixedLeafCount:"rightFixedLeafColumnsLength",columnsCount:function(e){return e.columns.length},leftFixedCount:function(e){return e.fixedColumns.length},rightFixedCount:function(e){return e.rightFixedColumns.length}})),created:function(){this.filterPanels={}},mounted:function(){var e=this;this.$nextTick((function(){var t=e.defaultSort,n=t.prop,r=t.order,o=!0;e.store.commit("sort",{prop:n,order:r,init:o})}))},beforeDestroy:function(){var e=this.filterPanels;for(var t in e)e.hasOwnProperty(t)&&e[t]&&e[t].$destroy(!0)},methods:{isCellHidden:function(e,t){for(var n=0,r=0;r=this.leftFixedLeafCount:"right"===this.fixed?n=this.columnsCount-this.rightFixedLeafCount},getHeaderRowStyle:function(e){var t=this.table.headerRowStyle;return"function"===typeof t?t.call(null,{rowIndex:e}):t},getHeaderRowClass:function(e){var t=[],n=this.table.headerRowClassName;return"string"===typeof n?t.push(n):"function"===typeof n&&t.push(n.call(null,{rowIndex:e})),t.join(" ")},getHeaderCellStyle:function(e,t,n,r){var o=this.table.headerCellStyle;return"function"===typeof o?o.call(null,{rowIndex:e,columnIndex:t,row:n,column:r}):o},getHeaderCellClass:function(e,t,n,r){var o=[r.id,r.order,r.headerAlign,r.className,r.labelClassName];0===e&&this.isCellHidden(t,n)&&o.push("is-hidden"),r.children||o.push("is-leaf"),r.sortable&&o.push("is-sortable");var i=this.table.headerCellClassName;return"string"===typeof i?o.push(i):"function"===typeof i&&o.push(i.call(null,{rowIndex:e,columnIndex:t,row:n,column:r})),o.join(" ")},toggleAllSelection:function(e){e.stopPropagation(),this.store.commit("toggleAllSelection")},handleFilterClick:function(e,t){e.stopPropagation();var n=e.target,r="TH"===n.tagName?n:n.parentNode;if(!Object(B["hasClass"])(r,"noclick")){r=r.querySelector(".el-table__column-filter-trigger")||r;var o=this.$parent,i=this.filterPanels[t.id];i&&t.filterOpened?i.showPopper=!1:(i||(i=new b.a(fe),this.filterPanels[t.id]=i,t.filterPlacement&&(i.placement=t.filterPlacement),i.table=o,i.cell=r,i.column=t,!this.$isServer&&i.$mount(document.createElement("div"))),setTimeout((function(){i.showPopper=!0}),16))}},handleHeaderClick:function(e,t){!t.filters&&t.sortable?this.handleSortClick(e,t):t.filterable&&!t.sortable&&this.handleFilterClick(e,t),this.$parent.$emit("header-click",t,e)},handleHeaderContextMenu:function(e,t){this.$parent.$emit("header-contextmenu",t,e)},handleMouseDown:function(e,t){var n=this;if(!this.$isServer&&!(t.children&&t.children.length>0)&&this.draggingColumn&&this.border){this.dragging=!0,this.$parent.resizeProxyVisible=!0;var r=this.$parent,o=r.$el,i=o.getBoundingClientRect().left,a=this.$el.querySelector("th."+t.id),s=a.getBoundingClientRect(),l=s.left-i+30;Object(B["addClass"])(a,"noclick"),this.dragState={startMouseLeft:e.clientX,startLeft:s.right-i,startColumnLeft:s.left-i,tableLeft:i};var u=r.$refs.resizeProxy;u.style.left=this.dragState.startLeft+"px",document.onselectstart=function(){return!1},document.ondragstart=function(){return!1};var c=function(e){var t=e.clientX-n.dragState.startMouseLeft,r=n.dragState.startLeft+t;u.style.left=Math.max(l,r)+"px"},f=function o(){if(n.dragging){var i=n.dragState,s=i.startColumnLeft,l=i.startLeft,f=parseInt(u.style.left,10),d=f-s;t.width=t.realWidth=d,r.$emit("header-dragend",t.width,l-s,t,e),n.store.scheduleLayout(),document.body.style.cursor="",n.dragging=!1,n.draggingColumn=null,n.dragState={},r.resizeProxyVisible=!1}document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",o),document.onselectstart=null,document.ondragstart=null,setTimeout((function(){Object(B["removeClass"])(a,"noclick")}),0)};document.addEventListener("mousemove",c),document.addEventListener("mouseup",f)}},handleMouseMove:function(e,t){if(!(t.children&&t.children.length>0)){var n=e.target;while(n&&"TH"!==n.tagName)n=n.parentNode;if(t&&t.resizable&&!this.dragging&&this.border){var r=n.getBoundingClientRect(),o=document.body.style;r.width>12&&r.right-e.pageX<8?(o.cursor="col-resize",Object(B["hasClass"])(n,"is-sortable")&&(n.style.cursor="col-resize"),this.draggingColumn=t):this.dragging||(o.cursor="",Object(B["hasClass"])(n,"is-sortable")&&(n.style.cursor="pointer"),this.draggingColumn=null)}}},handleMouseOut:function(){this.$isServer||(document.body.style.cursor="")},toggleOrder:function(e){var t=e.order,n=e.sortOrders;if(""===t)return n[0];var r=n.indexOf(t||null);return n[r>n.length-2?0:r+1]},handleSortClick:function(e,t,n){e.stopPropagation();var r=t.order===n?null:n||this.toggleOrder(t),o=e.target;while(o&&"TH"!==o.tagName)o=o.parentNode;if(o&&"TH"===o.tagName&&Object(B["hasClass"])(o,"noclick"))Object(B["removeClass"])(o,"noclick");else if(t.sortable){var i=this.store.states,a=i.sortProp,s=void 0,l=i.sortingColumn;(l!==t||l===t&&null===l.order)&&(l&&(l.order=null),i.sortingColumn=t,a=t.property),s=t.order=r||null,i.sortProp=a,i.sortOrder=s,this.store.commit("changeSortCondition")}}},data:function(){return{draggingColumn:null,dragging:!1,dragState:{}}}},me=Object.assign||function(e){for(var t=1;t=this.leftFixedLeafCount;if("right"===this.fixed){for(var r=0,o=0;o=this.columnsCount-this.rightFixedCount)},getRowClasses:function(e,t){var n=[e.id,e.align,e.labelClassName];return e.className&&n.push(e.className),this.isCellHidden(t,this.columns,e)&&n.push("is-hidden"),e.children||n.push("is-leaf"),n}}},ge=Object.assign||function(e){for(var t=1;t0){var r=n.scrollTop;t.pixelY<0&&0!==r&&e.preventDefault(),t.pixelY>0&&n.scrollHeight-n.clientHeight>r&&e.preventDefault(),n.scrollTop+=Math.ceil(t.pixelY/5)}else n.scrollLeft+=Math.ceil(t.pixelX/5)},handleHeaderFooterMousewheel:function(e,t){var n=t.pixelX,r=t.pixelY;Math.abs(n)>=Math.abs(r)&&(this.bodyWrapper.scrollLeft+=t.pixelX/5)},syncPostion:Object(s["throttle"])(20,(function(){var e=this.bodyWrapper,t=e.scrollLeft,n=e.scrollTop,r=e.offsetWidth,o=e.scrollWidth,i=this.$refs,a=i.headerWrapper,s=i.footerWrapper,l=i.fixedBodyWrapper,u=i.rightFixedBodyWrapper;a&&(a.scrollLeft=t),s&&(s.scrollLeft=t),l&&(l.scrollTop=n),u&&(u.scrollTop=n);var c=o-r-1;this.scrollPosition=t>=c?"right":0===t?"left":"middle"})),bindEvents:function(){this.bodyWrapper.addEventListener("scroll",this.syncPostion,{passive:!0}),this.fit&&Object(l["addResizeListener"])(this.$el,this.resizeListener)},unbindEvents:function(){this.bodyWrapper.removeEventListener("scroll",this.syncPostion,{passive:!0}),this.fit&&Object(l["removeResizeListener"])(this.$el,this.resizeListener)},resizeListener:function(){if(this.$ready){var e=!1,t=this.$el,n=this.resizeState,r=n.width,o=n.height,i=t.offsetWidth;r!==i&&(e=!0);var a=t.offsetHeight;(this.height||this.shouldUpdateHeight)&&o!==a&&(e=!0),e&&(this.resizeState.width=i,this.resizeState.height=a,this.doLayout())}},doLayout:function(){this.shouldUpdateHeight&&this.layout.updateElsHeight(),this.layout.updateColumnsWidth()},sort:function(e,t){this.store.commit("sort",{prop:e,order:t})},toggleAllSelection:function(){this.store.commit("toggleAllSelection")}},computed:ge({tableSize:function(){return this.size||(this.$ELEMENT||{}).size},bodyWrapper:function(){return this.$refs.bodyWrapper},shouldUpdateHeight:function(){return this.height||this.maxHeight||this.fixedColumns.length>0||this.rightFixedColumns.length>0},bodyWidth:function(){var e=this.layout,t=e.bodyWidth,n=e.scrollY,r=e.gutterWidth;return t?t-(n?r:0)+"px":""},bodyHeight:function(){var e=this.layout,t=e.headerHeight,n=void 0===t?0:t,r=e.bodyHeight,o=e.footerHeight,i=void 0===o?0:o;if(this.height)return{height:r?r+"px":""};if(this.maxHeight){var a=Object(w["j"])(this.maxHeight);if("number"===typeof a)return{"max-height":a-i-(this.showHeader?n:0)+"px"}}return{}},fixedBodyHeight:function(){if(this.height)return{height:this.layout.fixedBodyHeight?this.layout.fixedBodyHeight+"px":""};if(this.maxHeight){var e=Object(w["j"])(this.maxHeight);if("number"===typeof e)return e=this.layout.scrollX?e-this.layout.gutterWidth:e,this.showHeader&&(e-=this.layout.headerHeight),e-=this.layout.footerHeight,{"max-height":e+"px"}}return{}},fixedHeight:function(){return this.maxHeight?this.showSummary?{bottom:0}:{bottom:this.layout.scrollX&&this.data.length?this.layout.gutterWidth+"px":""}:this.showSummary?{height:this.layout.tableHeight?this.layout.tableHeight+"px":""}:{height:this.layout.viewportHeight?this.layout.viewportHeight+"px":""}},emptyBlockStyle:function(){if(this.data&&this.data.length)return null;var e="100%";return this.layout.appendHeight&&(e="calc(100% - "+this.layout.appendHeight+"px)"),{width:this.bodyWidth,height:e}}},R({selection:"selection",columns:"columns",tableData:"data",fixedColumns:"fixedColumns",rightFixedColumns:"rightFixedColumns"})),watch:{height:{immediate:!0,handler:function(e){this.layout.setHeight(e)}},maxHeight:{immediate:!0,handler:function(e){this.layout.setMaxHeight(e)}},currentRowKey:{immediate:!0,handler:function(e){this.rowKey&&this.store.setCurrentRowKey(e)}},data:{immediate:!0,handler:function(e){this.store.commit("setData",e)}},expandRowKeys:{immediate:!0,handler:function(e){e&&this.store.setExpandRowKeysAdapter(e)}}},created:function(){var e=this;this.tableId="el-table_"+be++,this.debouncedUpdateLayout=Object(s["debounce"])(50,(function(){return e.doLayout()}))},mounted:function(){var e=this;this.bindEvents(),this.store.updateColumns(),this.doLayout(),this.resizeState={width:this.$el.offsetWidth,height:this.$el.offsetHeight},this.store.states.columns.forEach((function(t){t.filteredValue&&t.filteredValue.length&&e.store.commit("filterChange",{column:t,values:t.filteredValue,silent:!0})})),this.$ready=!0},destroyed:function(){this.unbindEvents()},data:function(){var e=this.treeProps,t=e.hasChildren,n=void 0===t?"hasChildren":t,r=e.children,o=void 0===r?"children":r;this.store=L(this,{rowKey:this.rowKey,defaultExpandAll:this.defaultExpandAll,selectOnIndeterminate:this.selectOnIndeterminate,indent:this.indent,lazy:this.lazy,lazyColumnIdentifier:n,childrenColumnName:o});var i=new z({store:this.store,table:this,fit:this.fit,showHeader:this.showHeader});return{layout:i,isHidden:!1,renderExpanded:null,resizeProxyVisible:!1,resizeState:{width:null,height:null},isGroup:!1,scrollPosition:"left"}}},xe=_e,we=Object(ue["a"])(xe,r,o,!1,null,null,null);we.options.__file="packages/table/src/table.vue";var Ce=we.exports;Ce.install=function(e){e.component(Ce.name,Ce)};t["default"]=Ce}])},ad6d:function(e,t,n){"use strict";var r=n("825a");e.exports=function(){var e=r(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},ae93:function(e,t,n){"use strict";var r,o,i,a=n("e163"),s=n("9112"),l=n("5135"),u=n("b622"),c=n("c430"),f=u("iterator"),d=!1,p=function(){return this};[].keys&&(i=[].keys(),"next"in i?(o=a(a(i)),o!==Object.prototype&&(r=o)):d=!0),void 0==r&&(r={}),c||l(r,f)||s(r,f,p),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:d}},b041:function(e,t,n){"use strict";var r=n("00ee"),o=n("f5df");e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},b575:function(e,t,n){var r,o,i,a,s,l,u,c,f=n("da84"),d=n("06cf").f,p=n("c6b6"),h=n("2cf4").set,v=n("1cdc"),m=f.MutationObserver||f.WebKitMutationObserver,y=f.process,g=f.Promise,b="process"==p(y),_=d(f,"queueMicrotask"),x=_&&_.value;x||(r=function(){var e,t;b&&(e=y.domain)&&e.exit();while(o){t=o.fn,o=o.next;try{t()}catch(n){throw o?a():i=void 0,n}}i=void 0,e&&e.enter()},b?a=function(){y.nextTick(r)}:m&&!v?(s=!0,l=document.createTextNode(""),new m(r).observe(l,{characterData:!0}),a=function(){l.data=s=!s}):g&&g.resolve?(u=g.resolve(void 0),c=u.then,a=function(){c.call(u,r)}):a=function(){h.call(f,r)}),e.exports=x||function(e){var t={fn:e,next:void 0};i&&(i.next=t),o||(o=t,a()),i=t}},b622:function(e,t,n){var r=n("da84"),o=n("5692"),i=n("5135"),a=n("90e3"),s=n("4930"),l=n("fdbf"),u=o("wks"),c=r.Symbol,f=l?c:c&&c.withoutSetter||a;e.exports=function(e){return i(u,e)||(s&&i(c,e)?u[e]=c[e]:u[e]=f("Symbol."+e)),u[e]}},b84d:function(e,t,n){},be4f:function(e,t,n){},c04e:function(e,t,n){var r=n("861d");e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},c098:function(e,t,n){e.exports=n("d4af")},c216:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=122)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},122:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("span",{staticClass:"el-breadcrumb__item"},[n("span",{ref:"link",class:["el-breadcrumb__inner",e.to?"is-link":""],attrs:{role:"link"}},[e._t("default")],2),e.separatorClass?n("i",{staticClass:"el-breadcrumb__separator",class:e.separatorClass}):n("span",{staticClass:"el-breadcrumb__separator",attrs:{role:"presentation"}},[e._v(e._s(e.separator))])])},o=[];r._withStripped=!0;var i={name:"ElBreadcrumbItem",props:{to:{},replace:Boolean},data:function(){return{separator:"",separatorClass:""}},inject:["elBreadcrumb"],mounted:function(){var e=this;this.separator=this.elBreadcrumb.separator,this.separatorClass=this.elBreadcrumb.separatorClass;var t=this.$refs.link;t.setAttribute("role","link"),t.addEventListener("click",(function(t){var n=e.to,r=e.$router;n&&r&&(e.replace?r.replace(n):r.push(n))}))}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/breadcrumb/src/breadcrumb-item.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},c430:function(e,t){e.exports=!1},c56a:function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:300,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!e||!t)throw new Error("instance & callback is required");var o=!1,i=function(){o||(o=!0,t&&t.apply(null,arguments))};r?e.$once("after-leave",i):e.$on("after-leave",i),setTimeout((function(){i()}),n+100)}},c6b6:function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},c6cd:function(e,t,n){var r=n("da84"),o=n("ce4e"),i="__core-js_shared__",a=r[i]||o(i,{});e.exports=a},c8ba:function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(r){"object"===typeof window&&(n=window)}e.exports=n},ca84:function(e,t,n){var r=n("5135"),o=n("fc6a"),i=n("4d64").indexOf,a=n("d012");e.exports=function(e,t){var n,s=o(e),l=0,u=[];for(n in s)!r(a,n)&&r(s,n)&&u.push(n);while(t.length>l)r(s,n=t[l++])&&(~i(u,n)||u.push(n));return u}},cbb5:function(e,t,n){},cc12:function(e,t,n){var r=n("da84"),o=n("861d"),i=r.document,a=o(i)&&o(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},cca6:function(e,t,n){var r=n("23e7"),o=n("60da");r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},cdf9:function(e,t,n){var r=n("825a"),o=n("861d"),i=n("f069");e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e),a=n.resolve;return a(t),n.promise}},ce4e:function(e,t,n){var r=n("da84"),o=n("9112");e.exports=function(e,t){try{o(r,e,t)}catch(n){r[e]=t}return t}},d010:function(e,t,n){"use strict";function r(e,t,n){this.$children.forEach((function(o){var i=o.$options.componentName;i===e?o.$emit.apply(o,[t].concat(n)):r.apply(o,[e,t].concat([n]))}))}t.__esModule=!0,t.default={methods:{dispatch:function(e,t,n){var r=this.$parent||this.$root,o=r.$options.componentName;while(r&&(!o||o!==e))r=r.$parent,r&&(o=r.$options.componentName);r&&r.$emit.apply(r,[t].concat(n))},broadcast:function(e,t,n){r.call(this,e,t,n)}}}},d012:function(e,t){e.exports={}},d039:function(e,t){e.exports=function(e){try{return!!e()}catch(t){return!0}}},d066:function(e,t,n){var r=n("428f"),o=n("da84"),i=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},d1e7:function(e,t,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);t.f=i?function(e){var t=o(this,e);return!!t&&t.enumerable}:r},d2bb:function(e,t,n){var r=n("825a"),o=n("3bbe");e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,e.call(n,[]),t=n instanceof Array}catch(i){}return function(n,i){return r(n),o(i),t?e.call(n,i):n.__proto__=i,n}}():void 0)},d397:function(e,t,n){"use strict";function r(e){return void 0!==e&&null!==e}function o(e){var t=/([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi;return t.test(e)}t.__esModule=!0,t.isDef=r,t.isKorean=o},d3b7:function(e,t,n){var r=n("00ee"),o=n("6eeb"),i=n("b041");r||o(Object.prototype,"toString",i,{unsafe:!0})},d44e:function(e,t,n){var r=n("9bf2").f,o=n("5135"),i=n("b622"),a=i("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,a)&&r(e,a,{configurable:!0,value:t})}},d4af:function(e,t,n){"use strict";var r=n("8eb7"),o=n("7b3e"),i=10,a=40,s=800;function l(e){var t=0,n=0,r=0,o=0;return"detail"in e&&(n=e.detail),"wheelDelta"in e&&(n=-e.wheelDelta/120),"wheelDeltaY"in e&&(n=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(t=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(t=n,n=0),r=t*i,o=n*i,"deltaY"in e&&(o=e.deltaY),"deltaX"in e&&(r=e.deltaX),(r||o)&&e.deltaMode&&(1==e.deltaMode?(r*=a,o*=a):(r*=s,o*=s)),r&&!t&&(t=r<1?-1:1),o&&!n&&(n=o<1?-1:1),{spinX:t,spinY:n,pixelX:r,pixelY:o}}l.getEventType=function(){return r.firefox()?"DOMMouseScroll":o("wheel")?"wheel":"mousewheel"},e.exports=l},da84:function(e,t,n){(function(t){var n=function(e){return e&&e.Math==Math&&e};e.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof t&&t)||Function("return this")()}).call(this,n("c8ba"))},dcdc:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=83)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},4:function(e,t){e.exports=n("d010")},83:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("label",{staticClass:"el-checkbox",class:[e.border&&e.checkboxSize?"el-checkbox--"+e.checkboxSize:"",{"is-disabled":e.isDisabled},{"is-bordered":e.border},{"is-checked":e.isChecked}],attrs:{id:e.id}},[n("span",{staticClass:"el-checkbox__input",class:{"is-disabled":e.isDisabled,"is-checked":e.isChecked,"is-indeterminate":e.indeterminate,"is-focus":e.focus},attrs:{tabindex:!!e.indeterminate&&0,role:!!e.indeterminate&&"checkbox","aria-checked":!!e.indeterminate&&"mixed"}},[n("span",{staticClass:"el-checkbox__inner"}),e.trueLabel||e.falseLabel?n("input",{directives:[{name:"model",rawName:"v-model",value:e.model,expression:"model"}],staticClass:"el-checkbox__original",attrs:{type:"checkbox","aria-hidden":e.indeterminate?"true":"false",name:e.name,disabled:e.isDisabled,"true-value":e.trueLabel,"false-value":e.falseLabel},domProps:{checked:Array.isArray(e.model)?e._i(e.model,null)>-1:e._q(e.model,e.trueLabel)},on:{change:[function(t){var n=e.model,r=t.target,o=r.checked?e.trueLabel:e.falseLabel;if(Array.isArray(n)){var i=null,a=e._i(n,i);r.checked?a<0&&(e.model=n.concat([i])):a>-1&&(e.model=n.slice(0,a).concat(n.slice(a+1)))}else e.model=o},e.handleChange],focus:function(t){e.focus=!0},blur:function(t){e.focus=!1}}}):n("input",{directives:[{name:"model",rawName:"v-model",value:e.model,expression:"model"}],staticClass:"el-checkbox__original",attrs:{type:"checkbox","aria-hidden":e.indeterminate?"true":"false",disabled:e.isDisabled,name:e.name},domProps:{value:e.label,checked:Array.isArray(e.model)?e._i(e.model,e.label)>-1:e.model},on:{change:[function(t){var n=e.model,r=t.target,o=!!r.checked;if(Array.isArray(n)){var i=e.label,a=e._i(n,i);r.checked?a<0&&(e.model=n.concat([i])):a>-1&&(e.model=n.slice(0,a).concat(n.slice(a+1)))}else e.model=o},e.handleChange],focus:function(t){e.focus=!0},blur:function(t){e.focus=!1}}})]),e.$slots.default||e.label?n("span",{staticClass:"el-checkbox__label"},[e._t("default"),e.$slots.default?e._e():[e._v(e._s(e.label))]],2):e._e()])},o=[];r._withStripped=!0;var i=n(4),a=n.n(i),s={name:"ElCheckbox",mixins:[a.a],inject:{elForm:{default:""},elFormItem:{default:""}},componentName:"ElCheckbox",data:function(){return{selfModel:!1,focus:!1,isLimitExceeded:!1}},computed:{model:{get:function(){return this.isGroup?this.store:void 0!==this.value?this.value:this.selfModel},set:function(e){this.isGroup?(this.isLimitExceeded=!1,void 0!==this._checkboxGroup.min&&e.lengththis._checkboxGroup.max&&(this.isLimitExceeded=!0),!1===this.isLimitExceeded&&this.dispatch("ElCheckboxGroup","input",[e])):(this.$emit("input",e),this.selfModel=e)}},isChecked:function(){return"[object Boolean]"==={}.toString.call(this.model)?this.model:Array.isArray(this.model)?this.model.indexOf(this.label)>-1:null!==this.model&&void 0!==this.model?this.model===this.trueLabel:void 0},isGroup:function(){var e=this.$parent;while(e){if("ElCheckboxGroup"===e.$options.componentName)return this._checkboxGroup=e,!0;e=e.$parent}return!1},store:function(){return this._checkboxGroup?this._checkboxGroup.value:this.value},isLimitDisabled:function(){var e=this._checkboxGroup,t=e.max,n=e.min;return!(!t&&!n)&&this.model.length>=t&&!this.isChecked||this.model.length<=n&&this.isChecked},isDisabled:function(){return this.isGroup?this._checkboxGroup.disabled||this.disabled||(this.elForm||{}).disabled||this.isLimitDisabled:this.disabled||(this.elForm||{}).disabled},_elFormItemSize:function(){return(this.elFormItem||{}).elFormItemSize},checkboxSize:function(){var e=this.size||this._elFormItemSize||(this.$ELEMENT||{}).size;return this.isGroup&&this._checkboxGroup.checkboxGroupSize||e}},props:{value:{},label:{},indeterminate:Boolean,disabled:Boolean,checked:Boolean,name:String,trueLabel:[String,Number],falseLabel:[String,Number],id:String,controls:String,border:Boolean,size:String},methods:{addToStore:function(){Array.isArray(this.model)&&-1===this.model.indexOf(this.label)?this.model.push(this.label):this.model=this.trueLabel||!0},handleChange:function(e){var t=this;if(!this.isLimitExceeded){var n=void 0;n=e.target.checked?void 0===this.trueLabel||this.trueLabel:void 0!==this.falseLabel&&this.falseLabel,this.$emit("change",n,e),this.$nextTick((function(){t.isGroup&&t.dispatch("ElCheckboxGroup","change",[t._checkboxGroup.value])}))}}},created:function(){this.checked&&this.addToStore()},mounted:function(){this.indeterminate&&this.$el.setAttribute("aria-controls",this.controls)},watch:{value:function(e){this.dispatch("ElFormItem","el.form.change",e)}}},l=s,u=n(0),c=Object(u["a"])(l,r,o,!1,null,null,null);c.options.__file="packages/checkbox/src/checkbox.vue";var f=c.exports;f.install=function(e){e.component(f.name,f)};t["default"]=f}})},df75:function(e,t,n){var r=n("ca84"),o=n("7839");e.exports=Object.keys||function(e){return r(e,o)}},e163:function(e,t,n){var r=n("5135"),o=n("7b0b"),i=n("f772"),a=n("e177"),s=i("IE_PROTO"),l=Object.prototype;e.exports=a?Object.getPrototypeOf:function(e){return e=o(e),r(e,s)?e[s]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?l:null}},e177:function(e,t,n){var r=n("d039");e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},e260:function(e,t,n){"use strict";var r=n("fc6a"),o=n("44d2"),i=n("3f8c"),a=n("69f3"),s=n("7dd0"),l="Array Iterator",u=a.set,c=a.getterFor(l);e.exports=s(Array,"Array",(function(e,t){u(this,{type:l,target:r(e),index:0,kind:t})}),(function(){var e=c(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},e2cc:function(e,t,n){var r=n("6eeb");e.exports=function(e,t,n){for(var o in t)r(e,o,t[o],n);return e}},e3ea:function(e,t,n){},e452:function(e,t,n){"use strict";t.__esModule=!0;var r=r||{};r.Utils=r.Utils||{},r.Utils.focusFirstDescendant=function(e){for(var t=0;t=0;t--){var n=e.childNodes[t];if(r.Utils.attemptFocus(n)||r.Utils.focusLastDescendant(n))return!0}return!1},r.Utils.attemptFocus=function(e){if(!r.Utils.isFocusable(e))return!1;r.Utils.IgnoreUtilFocusChanges=!0;try{e.focus()}catch(t){}return r.Utils.IgnoreUtilFocusChanges=!1,document.activeElement===e},r.Utils.isFocusable=function(e){if(e.tabIndex>0||0===e.tabIndex&&null!==e.getAttribute("tabIndex"))return!0;if(e.disabled)return!1;switch(e.nodeName){case"A":return!!e.href&&"ignore"!==e.rel;case"INPUT":return"hidden"!==e.type&&"file"!==e.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},r.Utils.triggerEvent=function(e,t){var n=void 0;n=/^mouse|click/.test(t)?"MouseEvents":/^key/.test(t)?"KeyboardEvent":"HTMLEvents";for(var r=document.createEvent(n),o=arguments.length,i=Array(o>2?o-2:0),a=2;a=51&&/native code/.test(H))return!1;var t=H.resolve(1),n=function(e){e((function(){}),(function(){}))},r=t.constructor={};return r[L]=n,!(t.then((function(){}))instanceof n)})),ne=te||!w((function(e){H.all(e)["catch"]((function(){}))})),re=function(e){var t;return!(!m(e)||"function"!=typeof(t=e.then))&&t},oe=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;O((function(){var o=t.value,i=t.state==J,a=0;while(r.length>a){var s,l,u,c=r[a++],f=i?c.ok:c.fail,d=c.resolve,p=c.reject,h=c.domain;try{f?(i||(t.rejection===ee&&le(e,t),t.rejection=Q),!0===f?s=o:(h&&h.enter(),s=f(o),h&&(h.exit(),u=!0)),s===c.promise?p(z("Promise-chain cycle")):(l=re(s))?l.call(s,d,p):d(s)):p(o)}catch(v){h&&!u&&h.exit(),p(v)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&ae(e,t)}))}},ie=function(e,t,n){var r,o;K?(r=B.createEvent("Event"),r.promise=t,r.reason=n,r.initEvent(e,!1,!0),u.dispatchEvent(r)):r={promise:t,reason:n},(o=u["on"+e])?o(r):e===G&&k("Unhandled promise rejection",n)},ae=function(e,t){S.call(u,(function(){var n,r=t.value,o=se(t);if(o&&(n=j((function(){q?D.emit("unhandledRejection",r,e):ie(G,e,r)})),t.rejection=q||se(t)?ee:Q,n.error))throw n.value}))},se=function(e){return e.rejection!==Q&&!e.parent},le=function(e,t){S.call(u,(function(){q?D.emit("rejectionHandled",e):ie(X,e,t.value)}))},ue=function(e,t,n,r){return function(o){e(t,n,o,r)}},ce=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=Z,oe(e,t,!0))},fe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw z("Promise can't be resolved itself");var o=re(n);o?O((function(){var r={done:!1};try{o.call(n,ue(fe,e,r,t),ue(ce,e,r,t))}catch(i){ce(e,r,i,t)}})):(t.value=n,t.state=J,oe(e,t,!1))}catch(i){ce(e,{done:!1},i,t)}}};te&&(H=function(e){g(this,H,R),y(e),r.call(this);var t=N(this);try{e(ue(fe,this,t),ue(ce,this,t))}catch(n){ce(this,t,n)}},r=function(e){I(this,{type:R,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:Y,value:void 0})},r.prototype=p(H.prototype,{then:function(e,t){var n=F(this),r=V(C(this,H));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=q?D.domain:void 0,n.parent=!0,n.reactions.push(r),n.state!=Y&&oe(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),o=function(){var e=new r,t=N(e);this.promise=e,this.resolve=ue(fe,e,t),this.reject=ue(ce,e,t)},$.f=V=function(e){return e===H||e===i?new o(e):U(e)},l||"function"!=typeof f||(a=f.prototype.then,d(f.prototype,"then",(function(e,t){var n=this;return new H((function(e,t){a.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof W&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return E(H,W.apply(u,arguments))}}))),s({global:!0,wrap:!0,forced:te},{Promise:H}),h(H,R,!1,!0),v(R),i=c(R),s({target:R,stat:!0,forced:te},{reject:function(e){var t=V(this);return t.reject.call(void 0,e),t.promise}}),s({target:R,stat:!0,forced:l||te},{resolve:function(e){return E(l&&this===i?H:this,e)}}),s({target:R,stat:!0,forced:ne},{all:function(e){var t=this,n=V(t),r=n.resolve,o=n.reject,i=j((function(){var n=y(t.resolve),i=[],a=0,s=1;x(e,(function(e){var l=a++,u=!1;i.push(void 0),s++,n.call(t,e).then((function(e){u||(u=!0,i[l]=e,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(e){var t=this,n=V(t),r=n.reject,o=j((function(){var o=y(t.resolve);x(e,(function(e){o.call(t,e).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},e893:function(e,t,n){var r=n("5135"),o=n("56ef"),i=n("06cf"),a=n("9bf2");e.exports=function(e,t){for(var n=o(t),s=a.f,l=i.f,u=0;u-1?"center "+n:n+" center"}},appendArrow:function(e){var t=void 0;if(!this.appended){for(var n in this.appended=!0,e.attributes)if(/^_v-/.test(e.attributes[n].name)){t=e.attributes[n].name;break}var r=document.createElement("div");t&&r.setAttribute(t,""),r.setAttribute("x-arrow",""),r.className="popper__arrow",e.appendChild(r)}}},beforeDestroy:function(){this.doDestroy(!0),this.popperElm&&this.popperElm.parentNode===document.body&&(this.popperElm.removeEventListener("click",l),document.body.removeChild(this.popperElm))},deactivated:function(){this.$options.beforeDestroy[0].call(this)}}},ecdf:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=130)}({130:function(e,t,n){"use strict";n.r(t);var r=n(3),o={default:{order:""},selection:{width:48,minWidth:48,realWidth:48,order:"",className:"el-table-column--selection"},expand:{width:48,minWidth:48,realWidth:48,order:""},index:{width:48,minWidth:48,realWidth:48,order:""}},i={selection:{renderHeader:function(e,t){var n=t.store;return e("el-checkbox",{attrs:{disabled:n.states.data&&0===n.states.data.length,indeterminate:n.states.selection.length>0&&!this.isAllSelected,value:this.isAllSelected},nativeOn:{click:this.toggleAllSelection}})},renderCell:function(e,t){var n=t.row,r=t.column,o=t.store,i=t.$index;return e("el-checkbox",{nativeOn:{click:function(e){return e.stopPropagation()}},attrs:{value:o.isSelected(n),disabled:!!r.selectable&&!r.selectable.call(null,n,i)},on:{input:function(){o.commit("rowSelectedChanged",n)}}})},sortable:!1,resizable:!1},index:{renderHeader:function(e,t){var n=t.column;return n.label||"#"},renderCell:function(e,t){var n=t.$index,r=t.column,o=n+1,i=r.index;return"number"===typeof i?o=n+i:"function"===typeof i&&(o=i(n)),e("div",[o])},sortable:!1},expand:{renderHeader:function(e,t){var n=t.column;return n.label||""},renderCell:function(e,t){var n=t.row,r=t.store,o=["el-table__expand-icon"];r.states.expandRows.indexOf(n)>-1&&o.push("el-table__expand-icon--expanded");var i=function(e){e.stopPropagation(),r.toggleRowExpansion(n)};return e("div",{class:o,on:{click:i}},[e("i",{class:"el-icon el-icon-arrow-right"})])},sortable:!1,resizable:!1,className:"el-table__expand-column"}};function a(e,t){var n=t.row,o=t.column,i=t.$index,a=o.property,s=a&&Object(r["getPropByPath"])(n,a).v;return o&&o.formatter?o.formatter(n,o,s,i):s}function s(e,t){var n=t.row,r=t.treeNode,o=t.store;if(!r)return null;var i=[],a=function(e){e.stopPropagation(),o.loadOrToggle(n)};if(r.indent&&i.push(e("span",{class:"el-table__indent",style:{"padding-left":r.indent+"px"}})),"boolean"!==typeof r.expanded||r.noLazyChildren)i.push(e("span",{class:"el-table__placeholder"}));else{var s=["el-table__expand-icon",r.expanded?"el-table__expand-icon--expanded":""],l=["el-icon-arrow-right"];r.loading&&(l=["el-icon-loading"]),i.push(e("div",{class:s,on:{click:a}},[e("i",{class:l})]))}return i}var l=n(8),u=n(18),c=n.n(u),f=Object.assign||function(e){for(var t=1;t-1}))}}},data:function(){return{isSubColumn:!1,columns:[]}},computed:{owner:function(){var e=this.$parent;while(e&&!e.tableId)e=e.$parent;return e},columnOrTableParent:function(){var e=this.$parent;while(e&&!e.tableId&&!e.columnId)e=e.$parent;return e},realWidth:function(){return Object(l["l"])(this.width)},realMinWidth:function(){return Object(l["k"])(this.minWidth)},realAlign:function(){return this.align?"is-"+this.align:null},realHeaderAlign:function(){return this.headerAlign?"is-"+this.headerAlign:this.realAlign}},methods:{getPropsData:function(){for(var e=this,t=arguments.length,n=Array(t),r=0;rt.key[n])return 1}return 0};return e.map((function(e,t){return{value:e,index:t,key:s?s(e,t):null}})).sort((function(e,t){var r=l(e,t);return r||(r=e.index-t.index),r*n})).map((function(e){return e.value}))},l=function(e,t){var n=null;return e.columns.forEach((function(e){e.id===t&&(n=e)})),n},u=function(e,t){for(var n=null,r=0;r2&&void 0!==arguments[2]?arguments[2]:"children",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"hasChildren",o=function(e){return!(Array.isArray(e)&&e.length)};function i(e,a,s){t(e,a,s),a.forEach((function(e){if(e[r])t(e,null,s+1);else{var a=e[n];o(a)||i(e,a,s+1)}}))}e.forEach((function(e){if(e[r])t(e,null,0);else{var a=e[n];o(a)||i(e,a,0)}}))}}})},eedf:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=97)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},97:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("button",{staticClass:"el-button",class:[e.type?"el-button--"+e.type:"",e.buttonSize?"el-button--"+e.buttonSize:"",{"is-disabled":e.buttonDisabled,"is-loading":e.loading,"is-plain":e.plain,"is-round":e.round,"is-circle":e.circle}],attrs:{disabled:e.buttonDisabled||e.loading,autofocus:e.autofocus,type:e.nativeType},on:{click:e.handleClick}},[e.loading?n("i",{staticClass:"el-icon-loading"}):e._e(),e.icon&&!e.loading?n("i",{class:e.icon}):e._e(),e.$slots.default?n("span",[e._t("default")],2):e._e()])},o=[];r._withStripped=!0;var i={name:"ElButton",inject:{elForm:{default:""},elFormItem:{default:""}},props:{type:{type:String,default:"default"},size:String,icon:{type:String,default:""},nativeType:{type:String,default:"button"},loading:Boolean,disabled:Boolean,plain:Boolean,autofocus:Boolean,round:Boolean,circle:Boolean},computed:{_elFormItemSize:function(){return(this.elFormItem||{}).elFormItemSize},buttonSize:function(){return this.size||this._elFormItemSize||(this.$ELEMENT||{}).size},buttonDisabled:function(){return this.disabled||(this.elForm||{}).disabled}},methods:{handleClick:function(e){this.$emit("click",e)}}},a=i,s=n(0),l=Object(s["a"])(a,r,o,!1,null,null,null);l.options.__file="packages/button/src/button.vue";var u=l.exports;u.install=function(e){e.component(u.name,u)};t["default"]=u}})},f069:function(e,t,n){"use strict";var r=n("1c0b"),o=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new o(e)}},f0d9:function(e,t,n){"use strict";t.__esModule=!0,t.default={el:{colorpicker:{confirm:"确定",clear:"清空"},datepicker:{now:"此刻",today:"今天",cancel:"取消",clear:"清空",confirm:"确定",selectDate:"选择日期",selectTime:"选择时间",startDate:"开始日期",startTime:"开始时间",endDate:"结束日期",endTime:"结束时间",prevYear:"前一年",nextYear:"后一年",prevMonth:"上个月",nextMonth:"下个月",year:"年",month1:"1 月",month2:"2 月",month3:"3 月",month4:"4 月",month5:"5 月",month6:"6 月",month7:"7 月",month8:"8 月",month9:"9 月",month10:"10 月",month11:"11 月",month12:"12 月",weeks:{sun:"日",mon:"一",tue:"二",wed:"三",thu:"四",fri:"五",sat:"六"},months:{jan:"一月",feb:"二月",mar:"三月",apr:"四月",may:"五月",jun:"六月",jul:"七月",aug:"八月",sep:"九月",oct:"十月",nov:"十一月",dec:"十二月"}},select:{loading:"加载中",noMatch:"无匹配数据",noData:"无数据",placeholder:"请选择"},cascader:{noMatch:"无匹配数据",loading:"加载中",placeholder:"请选择",noData:"暂无数据"},pagination:{goto:"前往",pagesize:"条/页",total:"共 {total} 条",pageClassifier:"页"},messagebox:{title:"提示",confirm:"确定",cancel:"取消",error:"输入的数据不合法!"},upload:{deleteTip:"按 delete 键可删除",delete:"删除",preview:"查看图片",continue:"继续上传"},table:{emptyText:"暂无数据",confirmFilter:"筛选",resetFilter:"重置",clearFilter:"全部",sumText:"合计"},tree:{emptyText:"暂无数据"},transfer:{noMatch:"无匹配数据",noData:"无数据",titles:["列表 1","列表 2"],filterPlaceholder:"请输入搜索内容",noCheckedFormat:"共 {total} 项",hasCheckedFormat:"已选 {checked}/{total} 项"},image:{error:"加载失败"},pageHeader:{title:"返回"},popconfirm:{confirmButtonText:"确定",cancelButtonText:"取消"}}}},f3ad:function(e,t,n){e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist/",n(n.s=76)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var l,u="function"===typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(l=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},u._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(e,t){return l.call(t),c(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:u}}n.d(t,"a",(function(){return r}))},11:function(e,t){e.exports=n("2bb5")},21:function(e,t){e.exports=n("d397")},4:function(e,t){e.exports=n("d010")},76:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:["textarea"===e.type?"el-textarea":"el-input",e.inputSize?"el-input--"+e.inputSize:"",{"is-disabled":e.inputDisabled,"is-exceed":e.inputExceed,"el-input-group":e.$slots.prepend||e.$slots.append,"el-input-group--append":e.$slots.append,"el-input-group--prepend":e.$slots.prepend,"el-input--prefix":e.$slots.prefix||e.prefixIcon,"el-input--suffix":e.$slots.suffix||e.suffixIcon||e.clearable||e.showPassword}],on:{mouseenter:function(t){e.hovering=!0},mouseleave:function(t){e.hovering=!1}}},["textarea"!==e.type?[e.$slots.prepend?n("div",{staticClass:"el-input-group__prepend"},[e._t("prepend")],2):e._e(),"textarea"!==e.type?n("input",e._b({ref:"input",staticClass:"el-input__inner",attrs:{tabindex:e.tabindex,type:e.showPassword?e.passwordVisible?"text":"password":e.type,disabled:e.inputDisabled,readonly:e.readonly,autocomplete:e.autoComplete||e.autocomplete,"aria-label":e.label},on:{compositionstart:e.handleCompositionStart,compositionupdate:e.handleCompositionUpdate,compositionend:e.handleCompositionEnd,input:e.handleInput,focus:e.handleFocus,blur:e.handleBlur,change:e.handleChange}},"input",e.$attrs,!1)):e._e(),e.$slots.prefix||e.prefixIcon?n("span",{staticClass:"el-input__prefix"},[e._t("prefix"),e.prefixIcon?n("i",{staticClass:"el-input__icon",class:e.prefixIcon}):e._e()],2):e._e(),e.getSuffixVisible()?n("span",{staticClass:"el-input__suffix"},[n("span",{staticClass:"el-input__suffix-inner"},[e.showClear&&e.showPwdVisible&&e.isWordLimitVisible?e._e():[e._t("suffix"),e.suffixIcon?n("i",{staticClass:"el-input__icon",class:e.suffixIcon}):e._e()],e.showClear?n("i",{staticClass:"el-input__icon el-icon-circle-close el-input__clear",on:{mousedown:function(e){e.preventDefault()},click:e.clear}}):e._e(),e.showPwdVisible?n("i",{staticClass:"el-input__icon el-icon-view el-input__clear",on:{click:e.handlePasswordVisible}}):e._e(),e.isWordLimitVisible?n("span",{staticClass:"el-input__count"},[n("span",{staticClass:"el-input__count-inner"},[e._v("\n "+e._s(e.textLength)+"/"+e._s(e.upperLimit)+"\n ")])]):e._e()],2),e.validateState?n("i",{staticClass:"el-input__icon",class:["el-input__validateIcon",e.validateIcon]}):e._e()]):e._e(),e.$slots.append?n("div",{staticClass:"el-input-group__append"},[e._t("append")],2):e._e()]:n("textarea",e._b({ref:"textarea",staticClass:"el-textarea__inner",style:e.textareaStyle,attrs:{tabindex:e.tabindex,disabled:e.inputDisabled,readonly:e.readonly,autocomplete:e.autoComplete||e.autocomplete,"aria-label":e.label},on:{compositionstart:e.handleCompositionStart,compositionupdate:e.handleCompositionUpdate,compositionend:e.handleCompositionEnd,input:e.handleInput,focus:e.handleFocus,blur:e.handleBlur,change:e.handleChange}},"textarea",e.$attrs,!1)),e.isWordLimitVisible&&"textarea"===e.type?n("span",{staticClass:"el-input__count"},[e._v(e._s(e.textLength)+"/"+e._s(e.upperLimit))]):e._e()],2)},o=[];r._withStripped=!0;var i=n(4),a=n.n(i),s=n(11),l=n.n(s),u=void 0,c="\n height:0 !important;\n visibility:hidden !important;\n overflow:hidden !important;\n position:absolute !important;\n z-index:-1000 !important;\n top:0 !important;\n right:0 !important\n",f=["letter-spacing","line-height","padding-top","padding-bottom","font-family","font-weight","font-size","text-rendering","text-transform","width","text-indent","padding-left","padding-right","border-width","box-sizing"];function d(e){var t=window.getComputedStyle(e),n=t.getPropertyValue("box-sizing"),r=parseFloat(t.getPropertyValue("padding-bottom"))+parseFloat(t.getPropertyValue("padding-top")),o=parseFloat(t.getPropertyValue("border-bottom-width"))+parseFloat(t.getPropertyValue("border-top-width")),i=f.map((function(e){return e+":"+t.getPropertyValue(e)})).join(";");return{contextStyle:i,paddingSize:r,borderSize:o,boxSizing:n}}function p(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;u||(u=document.createElement("textarea"),document.body.appendChild(u));var r=d(e),o=r.paddingSize,i=r.borderSize,a=r.boxSizing,s=r.contextStyle;u.setAttribute("style",s+";"+c),u.value=e.value||e.placeholder||"";var l=u.scrollHeight,f={};"border-box"===a?l+=i:"content-box"===a&&(l-=o),u.value="";var p=u.scrollHeight-o;if(null!==t){var h=p*t;"border-box"===a&&(h=h+o+i),l=Math.max(h,l),f.minHeight=h+"px"}if(null!==n){var v=p*n;"border-box"===a&&(v=v+o+i),l=Math.min(v,l)}return f.height=l+"px",u.parentNode&&u.parentNode.removeChild(u),u=null,f}var h=n(9),v=n.n(h),m=n(21),y={name:"ElInput",componentName:"ElInput",mixins:[a.a,l.a],inheritAttrs:!1,inject:{elForm:{default:""},elFormItem:{default:""}},data:function(){return{textareaCalcStyle:{},hovering:!1,focused:!1,isComposing:!1,passwordVisible:!1}},props:{value:[String,Number],size:String,resize:String,form:String,disabled:Boolean,readonly:Boolean,type:{type:String,default:"text"},autosize:{type:[Boolean,Object],default:!1},autocomplete:{type:String,default:"off"},autoComplete:{type:String,validator:function(e){return!0}},validateEvent:{type:Boolean,default:!0},suffixIcon:String,prefixIcon:String,label:String,clearable:{type:Boolean,default:!1},showPassword:{type:Boolean,default:!1},showWordLimit:{type:Boolean,default:!1},tabindex:String},computed:{_elFormItemSize:function(){return(this.elFormItem||{}).elFormItemSize},validateState:function(){return this.elFormItem?this.elFormItem.validateState:""},needStatusIcon:function(){return!!this.elForm&&this.elForm.statusIcon},validateIcon:function(){return{validating:"el-icon-loading",success:"el-icon-circle-check",error:"el-icon-circle-close"}[this.validateState]},textareaStyle:function(){return v()({},this.textareaCalcStyle,{resize:this.resize})},inputSize:function(){return this.size||this._elFormItemSize||(this.$ELEMENT||{}).size},inputDisabled:function(){return this.disabled||(this.elForm||{}).disabled},nativeInputValue:function(){return null===this.value||void 0===this.value?"":String(this.value)},showClear:function(){return this.clearable&&!this.inputDisabled&&!this.readonly&&this.nativeInputValue&&(this.focused||this.hovering)},showPwdVisible:function(){return this.showPassword&&!this.inputDisabled&&!this.readonly&&(!!this.nativeInputValue||this.focused)},isWordLimitVisible:function(){return this.showWordLimit&&this.$attrs.maxlength&&("text"===this.type||"textarea"===this.type)&&!this.inputDisabled&&!this.readonly&&!this.showPassword},upperLimit:function(){return this.$attrs.maxlength},textLength:function(){return"number"===typeof this.value?String(this.value).length:(this.value||"").length},inputExceed:function(){return this.isWordLimitVisible&&this.textLength>this.upperLimit}},watch:{value:function(e){this.$nextTick(this.resizeTextarea),this.validateEvent&&this.dispatch("ElFormItem","el.form.change",[e])},nativeInputValue:function(){this.setNativeInputValue()},type:function(){var e=this;this.$nextTick((function(){e.setNativeInputValue(),e.resizeTextarea(),e.updateIconOffset()}))}},methods:{focus:function(){this.getInput().focus()},blur:function(){this.getInput().blur()},getMigratingConfig:function(){return{props:{icon:"icon is removed, use suffix-icon / prefix-icon instead.","on-icon-click":"on-icon-click is removed."},events:{click:"click is removed."}}},handleBlur:function(e){this.focused=!1,this.$emit("blur",e),this.validateEvent&&this.dispatch("ElFormItem","el.form.blur",[this.value])},select:function(){this.getInput().select()},resizeTextarea:function(){if(!this.$isServer){var e=this.autosize,t=this.type;if("textarea"===t)if(e){var n=e.minRows,r=e.maxRows;this.textareaCalcStyle=p(this.$refs.textarea,n,r)}else this.textareaCalcStyle={minHeight:p(this.$refs.textarea).minHeight}}},setNativeInputValue:function(){var e=this.getInput();e&&e.value!==this.nativeInputValue&&(e.value=this.nativeInputValue)},handleFocus:function(e){this.focused=!0,this.$emit("focus",e)},handleCompositionStart:function(){this.isComposing=!0},handleCompositionUpdate:function(e){var t=e.target.value,n=t[t.length-1]||"";this.isComposing=!Object(m["isKorean"])(n)},handleCompositionEnd:function(e){this.isComposing&&(this.isComposing=!1,this.handleInput(e))},handleInput:function(e){this.isComposing||e.target.value!==this.nativeInputValue&&(this.$emit("input",e.target.value),this.$nextTick(this.setNativeInputValue))},handleChange:function(e){this.$emit("change",e.target.value)},calcIconOffset:function(e){var t=[].slice.call(this.$el.querySelectorAll(".el-input__"+e)||[]);if(t.length){for(var n=null,r=0;r0&&(this.timer=setTimeout((function(){e.closed||e.close()}),this.duration))},keydown:function(e){27===e.keyCode&&(this.closed||this.close())}},mounted:function(){this.startTimer(),document.addEventListener("keydown",this.keydown)},beforeDestroy:function(){document.removeEventListener("keydown",this.keydown)}},u=l,c=n(0),f=Object(c["a"])(u,i,a,!1,null,null,null);f.options.__file="packages/message/src/main.vue";var d=f.exports,p=n(15),h=n(23),v=o.a.extend(d),m=void 0,y=[],g=1,b=function e(t){if(!o.a.prototype.$isServer){t=t||{},"string"===typeof t&&(t={message:t});var n=t.onClose,r="message_"+g++;t.onClose=function(){e.close(r,n)},m=new v({data:t}),m.id=r,Object(h["isVNode"])(m.message)&&(m.$slots.default=[m.message],m.message=null),m.$mount(),document.body.appendChild(m.$el);var i=t.offset||20;return y.forEach((function(e){i+=e.$el.offsetHeight+16})),m.verticalOffset=i,m.visible=!0,m.$el.style.zIndex=p["PopupManager"].nextZIndex(),y.push(m),m}};["success","warning","info","error"].forEach((function(e){b[e]=function(t){return"string"===typeof t&&(t={message:t}),t.type=e,b(t)}})),b.close=function(e,t){for(var n=y.length,r=-1,o=void 0,i=0;iy.length-1))for(var a=r;a=0;e--)y[e].close()};var _=b;t["default"]=_}})},f5df:function(e,t,n){var r=n("00ee"),o=n("c6b6"),i=n("b622"),a=i("toStringTag"),s="Arguments"==o(function(){return arguments}()),l=function(e,t){try{return e[t]}catch(n){}};e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=l(t=Object(e),a))?n:s?o(t):"Object"==(r=o(t))&&"function"==typeof t.callee?"Arguments":r}},f772:function(e,t,n){var r=n("5692"),o=n("90e3"),i=r("keys");e.exports=function(e){return i[e]||(i[e]=o(e))}},fc6a:function(e,t,n){var r=n("44ad"),o=n("1d80");e.exports=function(e){return r(o(e))}},fdbf:function(e,t,n){var r=n("4930");e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},fea9:function(e,t,n){var r=n("da84");e.exports=r.Promise}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/new/js/detail.11777eca.js b/app/src/main/assets/web/new/js/detail.11777eca.js new file mode 100644 index 000000000..d5a8bce01 --- /dev/null +++ b/app/src/main/assets/web/new/js/detail.11777eca.js @@ -0,0 +1 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["detail"],{"057f":function(t,e,n){var i=n("fc6a"),o=n("241c").f,r={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(t){try{return o(t)}catch(e){return a.slice()}};t.exports.f=function(t){return a&&"[object Window]"==r.call(t)?s(t):o(i(t))}},"05b3":function(t,e,n){},"0827":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg=="},"0eec":function(t,e,n){},1247:function(t,e,n){"use strict";var i=n("0eec"),o=n.n(i);o.a},1276:function(t,e,n){"use strict";var i=n("d784"),o=n("44e7"),r=n("825a"),a=n("1d80"),s=n("4840"),c=n("8aa5"),l=n("50c4"),u=n("14c3"),A=n("9263"),f=n("d039"),d=[].push,g=Math.min,h=4294967295,p=!f((function(){return!RegExp(h,"y")}));i("split",2,(function(t,e,n){var i;return i="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var i=String(a(this)),r=void 0===n?h:n>>>0;if(0===r)return[];if(void 0===t)return[i];if(!o(t))return e.call(i,t,r);var s,c,l,u=[],f=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),g=0,p=new RegExp(t.source,f+"g");while(s=A.call(p,i)){if(c=p.lastIndex,c>g&&(u.push(i.slice(g,s.index)),s.length>1&&s.index=r))break;p.lastIndex===s.index&&p.lastIndex++}return g===i.length?!l&&p.test("")||u.push(""):u.push(i.slice(g)),u.length>r?u.slice(0,r):u}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:e.call(this,t,n)}:e,[function(e,n){var o=a(this),r=void 0==e?void 0:e[t];return void 0!==r?r.call(e,o,n):i.call(String(o),e,n)},function(t,o){var a=n(i,t,this,o,i!==e);if(a.done)return a.value;var A=r(t),f=String(this),d=s(A,RegExp),m=A.unicode,v=(A.ignoreCase?"i":"")+(A.multiline?"m":"")+(A.unicode?"u":"")+(p?"y":"g"),b=new d(p?A:"^(?:"+A.source+")",v),C=void 0===o?h:o>>>0;if(0===C)return[];if(0===f.length)return null===u(b,f)?[f]:[];var y=0,S=0,x=[];while(S=51||!i((function(){var e=[],n=e.constructor={};return n[a]=function(){return{foo:1}},1!==e[t](Boolean).foo}))}},"1e75":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXM2t7O3ODQ3uLR4OTDp25yAAACdUlEQVQozw3P70sTcQDH8c/3/M7NG+j35mnHwjwh4hRy/QFK3zvPNbeIG1koPZmxfj2IDAwihL53zj0JYisfmEHcZJZOiBUG60lZiI8T/ANusuftgQ+kCPIPeMP7hS5mUrV9c1g6MQCAEZ8tDLHwofImAGRlX+SZK3Vu9rRRPuO4PK6/9nA4GIATsxlODS+rdCMhkAZivpYV0LWoQHSLSA4NfUg+6mY+7BKL2++F9LvnrBDYm6JO9i/YO3i/HJTGQ4pdIV82TbEDFG6vGYCd4wZchgK5J2CrKTLE+Tx0v+YGlIbdWJFcQl4ptBN8fUJQN1MCJLcZLYwUVVo+famGGty8EXJF5ofOEDzcodT3/Fb0I5sHmc1ZG7CcSl8COgxlXx09jT05OafjCZLIHJhGIaU6wDZHsuMQ41wbdjmQXbhKnMq1zlXSYrjCnyZblqexA7fC8RxS74tq2P3OxSQwTuJSApH8OZLzBBp1pOe0i3rdyDUA47GySZ31YmC4EQYSXvFSvieORGBxXF9aeVtUWKGS9WMC4Z9Y2uXnJ2nCUXVMbPOYqNYNmGWWQ7Evr+BWC+a0JAMTImcq/S4Z5INdQMeuOqDIMa9beilxfA60iC6sP1INcPDpmHBW8drZHNmqwyddJtVje9q8WGUgWAOzmbU4FCQBFi8B2Wk6pickBnYhJMenmJGuRmtt2IoKq9NuFGbNFR99sHnvrnLsLysKANDIsxbp6RNMAsoDSKuRpMwZbAAzI68QatIjmZ0aImyM3O8/4e2MNlOHZomFsa/fLDsysliHS+nlYLQJMnynxrH8QO4PaAV2Li8B/+52UgeGIVNFYf8B1XG/kFSmLcUAAAAASUVORK5CYII="},2130:function(t,e,n){"use strict";var i=n("af67"),o=n.n(i);o.a},"356c":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXPz8/R0dHT09PU1NToNyAhAAACdElEQVQozw3NP0xTQQDH8d9d7sFrG+QeKVgQ4aoFCwFkYERyLY//0UB8GNGg1WAC0RBGJrzW4mCXQmpgvCYOwEAYiulSpYtza2KiW7s5FgNJFSV2/CzfL7RwpoJ20iadmgA8owOyaxmusKE44scBeb4vIv00dqYgmf6jzWcr7W6INbDQeZbQL9ytXeYgtFfzmW1Fek5msxJlwhyt6qDDxOLQzpVPompYrMPnEnhvLm7M5BxY5nowAj3zkydAkpC0FIG6g7AK+Ub25ybyNWVYwtpseP2rfrQwiGRpfqrnMuPeuvr2dA0p2YsHF2XghkrXKtZ8tLBjR7S2qIaYbKmyLd/QP+EogLjqqwNw5Lq1pDlMLkM5+gNoSvdq+Pxmz9/61EFq6GYM6GqaGvlN95zy3gsmEWI8K3k8OP9OmRLEPO6DP3Wv3g42COinJTZ33dcIvs4ESp6opMTjDs6mcYTEbFeUifuxh989yZrIx4lkpuixxz0nHLCekKbE17suKhYkMGhoYhTZtVBvg4bfq/1L1Im0AGMVpBFwumM0zwyuKiCMi5dqR4Flx47AGyF2xTbxqUdTwCH94BT3DozpLV5WuAL/N8rGtHKjotBOOuOtCJ9E21uqsyBoLOzaXbHPrK5PQBP+fBfeidvJAeMIAmzVt5IkJJ9DBWaZDAepYUhlQqHt0h72SJ3j8TZHom64f516xx9T5evgMPgwG82jZdJaJIDyWp6LAjOCclVyzNA3iTKzIULlBQEPaTXlPHok5gISclmyaWZlqY2aTHdRHpJOwTdDEQ3ZfKtbpclcNhyVClagmY+fIfyKukntPqBgnx5QvZHk/D/MK8JMClrSigAAAABJRU5ErkJggg=="},"3ca3":function(t,e,n){"use strict";var i=n("6547").charAt,o=n("69f3"),r=n("7dd0"),a="String Iterator",s=o.set,c=o.getterFor(a);r(String,"String",(function(t){s(this,{type:a,string:String(t),index:0})}),(function(){var t,e=c(this),n=e.string,o=e.index;return o>=n.length?{value:void 0,done:!0}:(t=i(n,o),e.index+=t.length,{value:t,done:!1})}))},4039:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX0/PTx+fH2/vbz+/P4//htSO9OAAAC5UlEQVQ4yyWT0QGjMAxDZTsDWKQDmJQBYrgBUsr+M517x0+LRWw9CyA+pC1YzndrMgHaNXVKQ+di13Of1qbur48nWhuRjj8i6ON8e7pNm7zyag/DBTfS9Z4Hup1fUuXMKY4HEE8QOHCByXkIkl7lDT239RtL9quO4JItmmhOAHXg45QuYKrQFLyGJcRvaTw6kQqZy6mkR6JAPFH/XqsQjEDRmUOA+MNLHGyMUT7AHApoAhjgjIJmCxy6XHdf648AWCdGe57IUDazCeTImQOY4/z+eVYVX2IjOw9RydeAeJwl79iGi4HpgQgHEchWraUZLtayu8scq0lHHHUKMY3Ml8hB7CS1jOckDLG9ccgNeX3124phOcjL9fPnWJhTXpLHeG9DRmHnTxHEaHakS2J51lwAJcUraNbuU7q4gMTDQj3Eripc/x+qFM5VEKAB1roQfAkX5/PxqnS2QpOrxfK1Zft0/omV5T+xCSBUAIbEIwUQgvAfxFE1O8dnk233+1UZiqJ1mAbsue6Yt8tF+yOrxC/YrUhzC4qPlE3EbR5hGKhhHdlrg7J9WunV7L7BcYQwAeE59u2tnN1c6gfVYrQiLSZ9OxZdWDXQq0+r0Pbarh3UqGCwauVvbiXuDsNxCtLDdW9rTF8oQYN4EoXXdfmwNguQP26n/tRjDeo+F2W7PjWtfSr6Bn/z+cXOLp4NnMV4RytvSW4B68m+XN9XfZTFGhO/S+cHTuTqZDC21ccA0N7QsePALaDQC3D1f94U9CWo+aq6BjB3v0rxIimBM12296M3aKPHjXLQE9KQKH4By8RHraJ3AgVto2r4xdFqlaPaiAHLl1ZF4P2pI6cYc+K8UZdcmxy7lqGc1IoPxLmIFuIeEZ6j2sQT88muEg1zwrEDTIX5U/ZmcsqfgVlBumiBLF4sAyhf9BFlXOPKLZ4H0iFb3VoHrGhtHTldKrOvP2/reu2zfV8CXMPqzRdlgd0a5eI7WwB/AYcgavcqxXWEAAAAAElFTkSuQmCC"},4400:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAElBMVEUQERMODxESFBYWGBkaHB0eICLm6ozJAAACkUlEQVQ4yyWTUdLbMAiEASfvoOkBkBy/O5keIE0v8E/uf5h+68qZWALELgu2MG9PP9qyvCzTVhrrsPGOCjvTfXQZvtp/W3Gy6LCITqs4q/DZ+KYl76zKzHVYpY2wNY27nqN1sbLGcrLH3/ENH4oWlGctsDu8AO+HzTLlsYdh8MzP1m6YDMz0ACfcimvakBj+mwO/+5Uta5teOD379sxK1fUxmUhv8MU3jUT5gs26PMephFznkLcpQZ6/dPL9C/GWHcCxDN6oZhD5xBm5qoYBPA+PFE/H1tXDWcWl8TW7rS+4dUzAVy0BIrvC4/HcqW2TkG1HO8q9dC23INAg7NA4AFRFkDTM2lfELPyFzi1VddcpX2z0KjHBUDmdLNJ6dDps4ytrX+FPsZwE31wSL+6OWfHOAJ3+Y0Rk/MiKfmWNPg7oVP/U3Ck9FoCkC2gBpALOiqbMNTkOe8P4FWkTD2Y9Q3+5VmV0uLUJBl68U5uAK2Kl6QDXvLxbwweOL2sixW78uU8p0ysfc7cWrF1j6B1sPJ4WgclYSnJN1bzozrhEcFHmRzBkbJWqqdG+EYJXRFmT5jnLXPUNF6WBdoFbTxYsmDXVLU/WA7MExNc93sJS5hIXDeLxzMScHzdhKvEkibr6cQXYPrmtmTA7JcInISrTzRDvShTdka0uVGrsJAAR6tSn1sKziZtfKVjAxPrJsYgZO0bye+vKTZ/DgoAoLGNO6jYHimZYTL/3pLJHawquJukjBpfz8WOGVSVIWx9ywUfS5iENutidRM4NzkAmxgUSQ68xgNOU+ZLalr4TS2V+D2xqukZig+Z9DilR7Nouzwp1cp/3E5q6Rdlf08obKvAM4qZ6pMr+w3PmQALSSBfjyZn5DwrNRVbywBQiAAAAAElFTkSuQmCC"},"44e7":function(t,e,n){var i=n("861d"),o=n("c6b6"),r=n("b622"),a=r("match");t.exports=function(t){var e;return i(t)&&(void 0!==(e=t[a])?!!e:"RegExp"==o(t))}},"477e":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAACVBMVEX28ef48+n69esoK7jYAAAB4UlEQVQozw2OsW4bQQxEhwLXkDrysGdEqRRgVShfQQq8wOr2jD0jSpXCLvwXbtKfADlFqgSwC/9ljqweZgYzQFnb/QGepYhA9jzmTc1WaSEtQpbFgjWATI00ZZtIckXx8q2Oe5yEByBy+RHOTcM+VVTadULsvxvRC/q8WTwgcWGD+Mnaqa0oy2gw2pKFzK+PzEsus5hP9AHojKslVynLlioVTBEN8cjDNnZoR1uMGTiZAAN47HxMtEkGUE9b8HWzkqNX5Lpk0yVziAJOs46rK1pG/xNuXLjz95fSDoJE5IqG23MAYPtWoeWPvfVtIV/Ng9oH3W0gGMPIOqd4MK4QZ55dV61gOb8Zxp7I9qayaGxp6Q91cmC0ZRdBwEQVHWzSAanlZwVWc9yljeTCeaHjBVvlPSLeyeBUT2rPdJegQI103jVS3uYkyIx1il6mslMDedZuOkwzolsagvPuQAfp7cYg7k9V1NOxfq64PNSvMdwONV4VYEmqlbpZy5OAakRKkjPnL4CBv5/OZRgoWHBmNbxB0LgB1I4vXFj93UoF2/0TPEsWwV9EhbIiTPqYoTHYoMn3enTDjmrFeDTIzaL1bUC/PBIMuF+vSSYSaxoVt90EO3Gu1zrMuMRGUk7Ffv3L+A931Gsb/yBoIgAAAABJRU5ErkJggg=="},"537b":function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"chapter-wrapper",class:{night:t.isNight,day:!t.isNight},style:t.bodyTheme},[n("div",{staticClass:"tool-bar",style:t.leftBarTheme},[n("div",{staticClass:"tools"},[n("el-popover",{attrs:{placement:"right",width:t.cataWidth,trigger:"click","visible-arrow":!1,"popper-class":"pop-cata"},model:{value:t.popCataVisible,callback:function(e){t.popCataVisible=e},expression:"popCataVisible"}},[n("PopCata",{ref:"popCata",staticClass:"popup",on:{getContent:t.getContent}}),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},attrs:{slot:"reference"},slot:"reference"},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("目录")])])],1),n("el-popover",{attrs:{placement:"bottom-right",width:"470",trigger:"click","visible-arrow":!1,"popper-class":"pop-setting"},model:{value:t.readSettingsVisible,callback:function(e){t.readSettingsVisible=e},expression:"readSettingsVisible"}},[n("ReadSettings",{staticClass:"popup"}),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},attrs:{slot:"reference"},slot:"reference"},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("设置")])])],1),n("div",{staticClass:"tool-icon",on:{click:t.toShelf}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("书架")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toTop}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("顶部")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toBottom}},[n("div",{staticClass:"iconfont"},[t._v("  ")]),n("div",{staticClass:"icon-text"},[t._v("底部")])])],1)]),n("div",{staticClass:"read-bar",style:t.rightBarTheme},[n("div",{staticClass:"tools"},[n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toLastChapter}},[n("div",{staticClass:"iconfont"},[t._v("  ")])]),n("div",{staticClass:"tool-icon",class:{"no-point":t.noPoint},on:{click:t.toNextChapter}},[n("div",{staticClass:"iconfont"},[t._v("  ")])])])]),n("div",{staticClass:"chapter-bar"}),n("div",{ref:"content",staticClass:"chapter",style:t.chapterTheme},[n("div",{staticClass:"content"},[n("div",{ref:"top",staticClass:"top-bar"}),t.show?n("div",{ref:"title",staticClass:"title"},[t._v(t._s(t.title))]):t._e(),n("Pcontent",{attrs:{carray:t.content}}),n("div",{ref:"bottom",staticClass:"bottom-bar"})],1)])])},o=[],r=(n("ac1f"),n("1276"),function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"cata-wrapper",style:t.popupTheme},[n("div",{staticClass:"title"},[t._v(" 目录 ")]),n("div",{ref:"cataData",staticClass:"data-wrapper",class:{night:t.isNight,day:!t.isNight}},[n("div",{staticClass:"cata"},t._l(t.catalog,(function(e,i){return n("div",{key:e.durChapterIndex,ref:"cata",refInFor:!0,staticClass:"log",class:{selected:t.isSelected(i)},on:{click:function(n){return t.gotoChapter(e)}}},[n("div",{staticClass:"log-text"},[t._v(" "+t._s(e.title)+" ")])])})),0)])])}),a=[];n("c975"),n("a4d3"),n("e01a"),n("d28b"),n("d3b7"),n("3ca3"),n("ddb0");function s(t){return s="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},s(t)}var c,l,u=function(t,e,n,i){return t/=i/2,t<1?n/2*t*t+e:(t--,-n/2*(t*(t-2)-1)+e)},A=function(){var t,e,n,i,o,r,a,c,l,A,f,d,g;function h(){var e=t.scrollTop||t.scrollY||t.pageYOffset;return e="undefined"===typeof e?0:e,e}function p(e){var i=e.getBoundingClientRect().top,o=t.getBoundingClientRect?t.getBoundingClientRect().top:0;return i-o+n}function m(e){t.scrollTo?t.scrollTo(0,e):t.scrollTop=e}function v(t){A||(A=t),f=t-A,d=r(f,n,c,l),m(d),f1&&void 0!==arguments[1]?arguments[1]:{};switch(l=f.duration||1e3,o=f.offset||0,g=f.callback,r=f.easing||u,a=f.a11y||!1,s(f.container)){case"object":t=f.container;break;case"string":t=document.querySelector(f.container);break;default:t=window}switch(n=h(),s(A)){case"number":e=void 0,a=!1,i=n+A;break;case"object":e=A,i=p(e);break;case"string":e=document.querySelector(A),i=p(e);break}switch(c=i-n+o,s(f.duration)){case"number":l=f.duration;break;case"function":l=f.duration(c);break}requestAnimationFrame(v)}return C},f=A(),d=f,g=n("7286"),h=n.n(g),p=n("477e"),m=n.n(p),v=n("e160"),b=n.n(v),C=n("df5e"),y=n.n(C),S=n("ec0f"),x=n.n(S),B=n("b671"),I=n.n(B),E=n("5629"),k=n.n(E),w=n("d0e3"),O=n.n(w),U=n("4039"),D=n.n(U),Q=n("1e75"),V=n.n(Q),R=n("1632"),P=n.n(R),M=n("7abd"),F=n.n(M),L=n("356c"),N=n.n(L),T=n("b165"),K=n.n(T),H=n("cf68"),J=n.n(H),z=n("4400"),W=n.n(z),G=n("802e"),q=n.n(G),Y=n("0827"),Z=n.n(Y),j={themes:[{body:"#ede7da url("+h.a+") repeat",content:"#ede7da url("+m.a+") repeat",popup:"#ede7da url("+b.a+") repeat"},{body:"#ede7da url("+y.a+") repeat",content:"#ede7da url("+x.a+") repeat",popup:"#ede7da url("+I.a+") repeat"},{body:"#ede7da url("+k.a+") repeat",content:"#ede7da url("+O.a+") repeat",popup:"#ede7da url("+D.a+") repeat"},{body:"#ede7da url("+V.a+") repeat",content:"#ede7da url("+P.a+") repeat",popup:"#ede7da url("+F.a+") repeat"},{body:"#ebcece repeat",content:"#f5e4e4 repeat",popup:"#faeceb repeat"},{body:"#ede7da url("+N.a+") repeat",content:"#ede7da url("+K.a+") repeat",popup:"#ede7da url("+J.a+") repeat"},{body:"#ede7da url("+W.a+") repeat",content:"#ede7da url("+q.a+") repeat",popup:"#ede7da url("+Z.a+") repeat"}],fonts:[{fontFamily:"Microsoft YaHei, PingFangSC-Regular, HelveticaNeue-Light, Helvetica Neue Light, sans-serif"},{fontFamily:"PingFangSC-Regular, -apple-system, Simsun"},{fontFamily:"Kaiti"}]},X=j,$=(n("05b3"),{name:"PopCata",data:function(){return{isNight:6==this.$store.state.config.theme,index:this.$store.state.readingBook.index}},computed:{catalog:function(){return this.$store.state.readingBook.catalog},popCataVisible:function(){return this.$store.state.popCataVisible},theme:function(){return this.$store.state.config.theme},popupTheme:function(){return{background:X.themes[this.theme].popup}}},mounted:function(){},watch:{theme:function(t){this.isNight=6==t},popCataVisible:function(){this.$nextTick((function(){var t=this.$store.state.readingBook.index,e=this.$refs.cataData;d(this.$refs.cata[t],{container:e,duration:0})}))}},methods:{isSelected:function(t){return t==this.$store.state.readingBook.index},gotoChapter:function(t){this.index=this.catalog.indexOf(t),this.$store.commit("setPopCataVisible",!1),this.$store.commit("setContentLoading",!0),this.$emit("getContent",this.index)}}}),_=$,tt=(n("9078"),n("2877")),et=Object(tt["a"])(_,r,a,!1,null,"22f8c37b",null),nt=et.exports,it=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"settings-wrapper",class:{night:t.isNight,day:!t.isNight},style:t.popupTheme},[n("div",{staticClass:"settings-title"},[t._v("设置")]),n("div",{staticClass:"setting-list"},[n("ul",[n("li",{staticClass:"theme-list"},[n("i",[t._v("阅读主题")]),t._l(t.themeColors,(function(e,i){return n("span",{key:i,ref:"themes",refInFor:!0,staticClass:"theme-item",class:{selected:t.selectedTheme==i},style:e,on:{click:function(e){return t.setTheme(i)}}},[i<6?n("em",{staticClass:"iconfont"},[t._v("")]):n("em",{staticClass:"moon-icon"},[t._v(t._s(t.moonIcon))])])}))],2),n("li",{staticClass:"font-list"},[n("i",[t._v("正文字体")]),t._l(t.fonts,(function(e,i){return n("span",{key:i,staticClass:"font-item",class:{selected:t.selectedFont==i},on:{click:function(e){return t.setFont(i)}}},[t._v(t._s(e))])}))],2),n("li",{staticClass:"font-size"},[n("i",[t._v("字体大小")]),n("div",{staticClass:"resize"},[n("span",{staticClass:"less",on:{click:t.lessFontSize}},[n("em",{staticClass:"iconfont"},[t._v("")])]),n("b"),t._v(" "),n("span",{staticClass:"lang"},[t._v(t._s(t.fontSize))]),n("b"),n("span",{staticClass:"more",on:{click:t.moreFontSize}},[n("em",{staticClass:"iconfont"},[t._v("")])])])]),n("li",{staticClass:"read-width"},[n("i",[t._v("页面宽度")]),n("div",{staticClass:"resize"},[n("span",{staticClass:"less",on:{click:t.lessReadWidth}},[n("em",{staticClass:"iconfont"},[t._v("")])]),n("b"),t._v(" "),n("span",{staticClass:"lang"},[t._v(t._s(t.readWidth))]),n("b"),n("span",{staticClass:"more",on:{click:t.moreReadWidth}},[n("em",{staticClass:"iconfont"},[t._v("")])])])])])])])},ot=[],rt=(n("82da"),{name:"ReadSettings",data:function(){return{theme:0,isNight:6==this.$store.state.config.theme,moonIcon:"",themeColors:[{background:"rgba(250, 245, 235, 0.8)"},{background:"rgba(245, 234, 204, 0.8)"},{background:"rgba(230, 242, 230, 0.8)"},{background:"rgba(228, 241, 245, 0.8)"},{background:"rgba(245, 228, 228, 0.8)"},{background:"rgba(224, 224, 224, 0.8)"},{background:"rgba(0, 0, 0, 0.5)"}],moonIconStyle:{display:"inline",color:"rgba(255,255,255,0.2)"},fonts:["雅黑","宋体","楷书"]}},mounted:function(){var t=this.$store.state.config;this.theme=t.theme,6==this.theme?this.moonIcon="":this.moonIcon=""},computed:{config:function(){return this.$store.state.config},popupTheme:function(){return{background:X.themes[this.config.theme].popup}},selectedTheme:function(){return this.$store.state.config.theme},selectedFont:function(){return this.$store.state.config.font},fontSize:function(){return this.$store.state.config.fontSize},readWidth:function(){return this.$store.state.config.readWidth}},methods:{setTheme:function(t){6==t?(this.isNight=!0,this.moonIcon="",this.moonIconStyle.color="#ed4259"):(this.isNight=!1,this.moonIcon="",this.moonIconStyle.color="rgba(255,255,255,0.2)");var e=this.config;e.theme=t,this.$store.commit("setConfig",e)},setFont:function(t){var e=this.config;e.font=t,this.$store.commit("setConfig",e)},moreFontSize:function(){var t=this.config;t.fontSize<48&&(t.fontSize+=2),this.$store.commit("setConfig",t)},lessFontSize:function(){var t=this.config;t.fontSize>12&&(t.fontSize-=2),this.$store.commit("setConfig",t)},moreReadWidth:function(){var t=this.config;t.readWidth<960&&(t.readWidth+=160),this.$store.commit("setConfig",t)},lessReadWidth:function(){var t=this.config;t.readWidth>640&&(t.readWidth-=160),this.$store.commit("setConfig",t)}}}),at=rt,st=(n("75ab"),Object(tt["a"])(at,it,ot,!1,null,"36dafd56",null)),ct=st.exports,lt=(n("d81d"),{name:"pcontent",data:function(){return{}},props:["carray"],render:function(){var t=arguments[0],e=this.fontFamily,n=this.fontSize,i=e;return i.fontSize=n,this.show?t("div",[this.carray.map((function(e){return t("p",{style:i},[e])}))]):t("div")},computed:{show:function(){return this.$store.state.showContent},fontFamily:function(){return X.fonts[this.$store.state.config.font]},fontSize:function(){return this.$store.state.config.fontSize+"px"}},watch:{fontSize:function(){var t=this;t.$store.commit("setShowContent",!1),this.$nextTick((function(){t.$store.commit("setShowContent",!0)}))}}}),ut=lt,At=(n("2130"),Object(tt["a"])(ut,c,l,!1,null,"72ace4c0",null)),ft=At.exports,dt=n("bc3a"),gt=n.n(dt),ht={components:{PopCata:nt,Pcontent:ft,ReadSettings:ct},created:function(){var t=JSON.parse(localStorage.getItem("config"));null!=t&&this.$store.commit("setConfig",t)},beforeCreate:function(){var t=JSON.parse(localStorage.getItem("config"));null!=t&&this.$store.commit("setConfig",t)},mounted:function(){var t=this;this.loading=this.$loading({target:this.$refs.content,lock:!0,text:"正在获取内容",spinner:"el-icon-loading",background:"rgba(0,0,0,0)"});var e=this,n=sessionStorage.getItem("bookUrl"),i=sessionStorage.getItem("bookName"),o=sessionStorage.getItem("chapterIndex")||0,r=JSON.parse(localStorage.getItem(n));o>0&&(r={bookName:i,bookUrl:n,index:o},localStorage.setItem(n,JSON.stringify(r))),this.getCatalog(n).then((function(n){var i=n.data.data;r.catalog=i,e.$store.commit("setReadingBook",r);var o=e.$store.state.readingBook.index||0;t.getContent(o),window.addEventListener("keyup",(function(t){switch(t.key){case"ArrowLeft":t.stopPropagation(),t.preventDefault(),e.toLastChapter();break;case"ArrowRight":t.stopPropagation(),t.preventDefault(),e.toNextChapter();break;case"ArrowUp":t.stopPropagation(),t.preventDefault(),0===document.documentElement.scrollTop?e.$message.warning("已到达页面顶部"):d(0-document.documentElement.clientHeight+100);break;case"ArrowDown":t.stopPropagation(),t.preventDefault(),document.documentElement.clientHeight+document.documentElement.scrollTop===document.documentElement.scrollHeight?e.$message.warning("已到达页面底部"):d(document.documentElement.clientHeight-100);break}}))}),(function(t){throw e.loading.close(),e.$message.error("获取书籍目录失败"),t}))},watch:{chapterName:function(t){this.title=t},content:function(){this.$store.commit("setContentLoading",!1)},theme:function(t){this.isNight=6==t},bodyColor:function(t){this.bodyTheme.background=t},chapterColor:function(t){this.chapterTheme.background=t},readWidth:function(t){this.chapterTheme.width=t;var e=-((parseInt(t)+130)/2+68)+"px",n=-((parseInt(t)+130)/2+52)+"px";this.leftBarTheme.marginLeft=e,this.rightBarTheme.marginRight=n},popupColor:function(t){this.leftBarTheme.background=t,this.rightBarTheme.background=t},readSettingsVisible:function(t){if(!t){var e=JSON.stringify(this.$store.state.config);localStorage.setItem("config",e)}}},data:function(){return{title:"",content:[],noPoint:!0,isNight:6==this.$store.state.config.theme,bodyTheme:{background:X.themes[this.$store.state.config.theme].body},chapterTheme:{background:X.themes[this.$store.state.config.theme].content,width:this.$store.state.config.readWidth-130+"px"},leftBarTheme:{background:X.themes[this.$store.state.config.theme].popup,marginLeft:-(this.$store.state.config.readWidth/2+68)+"px"},rightBarTheme:{background:X.themes[this.$store.state.config.theme].popup,marginRight:-(this.$store.state.config.readWidth/2+52)+"px"}}},computed:{catalog:function(){return this.$store.state.catalog},windowHeight:function(){return window.innerHeight},contentHeight:function(){return this.$refs.content.offsetHeight},popCataVisible:{get:function(){return this.$store.state.popCataVisible},set:function(t){this.$store.commit("setPopCataVisible",t)}},readSettingsVisible:{get:function(){return this.$store.state.readSettingsVisible},set:function(t){this.$store.commit("setReadSettingsVisible",t)}},config:function(){return this.$store.state.config},theme:function(){return this.config.theme},bodyColor:function(){return X.themes[this.config.theme].body},chapterColor:function(){return X.themes[this.config.theme].content},popupColor:function(){return X.themes[this.config.theme].popup},readWidth:function(){return this.$store.state.config.readWidth-130+"px"},cataWidth:function(){return this.$store.state.config.readWidth-33},show:function(){return this.$store.state.showContent}},methods:{getCatalog:function(t){return gt.a.get("/getChapterList?url="+encodeURIComponent(t))},getContent:function(t){var e=this;this.$store.commit("setShowContent",!1),this.loading.visible||(this.loading=this.$loading({target:this.$refs.content,lock:!0,text:"正在获取内容",spinner:"el-icon-loading",background:"rgba(0,0,0,0)"}));var n=sessionStorage.getItem("bookUrl"),i=JSON.parse(localStorage.getItem(n));i.index=t,localStorage.setItem(n,JSON.stringify(i)),this.$store.state.readingBook.index=t;var o=this.$store.state.readingBook.catalog[t].title,r=this.$store.state.readingBook.catalog[t].index;this.title=o,d(this.$refs.top,{duration:0});var a=this;gt.a.get("/getBookContent?url="+encodeURIComponent(n)+"&index="+r).then((function(t){var n=t.data.data,i=n.split("\n\n"),o="";o=i.length>1?i[1].split("\n"):i[0].split("\n"),a.content=o,e.$store.commit("setContentLoading",!0),a.loading.close(),a.noPoint=!1,a.$store.commit("setShowContent",!0)}),(function(t){throw a.$message.error("获取章节内容失败"),a.content="  获取章节内容失败!",t}))},toTop:function(){d(this.$refs.top)},toBottom:function(){d(this.$refs.bottom)},toNextChapter:function(){this.$store.commit("setContentLoading",!0);var t=this.$store.state.readingBook.index;t++,"undefined"!==typeof this.$store.state.readingBook.catalog[t]?(this.$message.info("下一章"),this.getContent(t)):this.$message.error("本章是最后一章")},toLastChapter:function(){this.$store.commit("setContentLoading",!0);var t=this.$store.state.readingBook.index;t--,"undefined"!==typeof this.$store.state.readingBook.catalog[t]?(this.$message.info("上一章"),this.getContent(t)):this.$message.error("本章是第一章")},toShelf:function(){this.$router.push("/")}}},pt=ht,mt=(n("1247"),Object(tt["a"])(pt,i,o,!1,null,"5cf5dc8c",null));e["default"]=mt.exports},5629:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXN383Q4tDP4c/R5NEInCCXAAACVElEQVQozw3Hv2sTYRwH4M/79pJ7bZL2bXqtERJ97zjUpbZDhg6pfC8qibi8hLR0EaJ0EFxaCSWDxjfpj1zrYBcRBKE6SAfBJWsx9i8IQfdQxDlKtA6t2OnhQfN3lbG7ytYRywF8rVoPCNO0X2sQOKDpAnSDK2VwkHgmh5yLGT8qASt+2KofnNt2Xg1gf1UF8AoM6052cRMNaloLZb7RKQGrKKji2OefsZF+VqIvos5ZLVIZCX61JcwUdk56wASVkgQvzPfvmT2twTSwyYaC/Pl/UhAHorFhBgZtL6XdAZRp1tkPwC1NLa9CWs5prLhI85NBQsLdXvjDymG3/EbYfQhVNYqc3TtktQhWLY3ko0QsdMbSEp+64v0NfxyqLbIGdh6M2xHHlLBGqKTyQo4E/nebBgBfe1GpdeywYXc8CT7D3cKXuMXkBy4xN6o5OuKamYp3DVI6uccO9lxgd2CAlJgI2BGgaAgIJV/TYwKqu3WFccjbMuA+bVkWgS2bfnlRbD1Eb1sDyWMmjKYIBgGAWbqKRicfvzBkBIz3V5AKnguWdglQEysQsSuVzOg6ALy1pitA5ykGCsc857BRYcgCSZyFOdvoOigSGoPc5Ta73mgxshIcQE5sHMHd9D7yqITw7JO+GHVMxjhzYLcKPSEgmz3fU+BRy3iYNtiXLaBssCW8KguReqkQOTb3MStV0Ugt4U1eIs1RZWRII6Ww8xeNNItyGGQI4ZMlpg/3lQtkl2JFnBp1imRyFe0kK2Id3PCslMgiQNMS77gvFeDhG3cSkYvheeg/e7ClIh5oh+IAAAAASUVORK5CYII="},"5ca7":function(t,e,n){"use strict";var i=n("a7bd"),o=n.n(i);o.a},"63f0":function(t,e,n){},6547:function(t,e,n){var i=n("a691"),o=n("1d80"),r=function(t){return function(e,n){var r,a,s=String(o(e)),c=i(n),l=s.length;return c<0||c>=l?t?"":void 0:(r=s.charCodeAt(c),r<55296||r>56319||c+1===l||(a=s.charCodeAt(c+1))<56320||a>57343?t?s.charAt(c):r:t?s.slice(c,c+2):a-56320+(r-55296<<10)+65536)}};t.exports={codeAt:r(!1),charAt:r(!0)}},"65f0":function(t,e,n){var i=n("861d"),o=n("e8b5"),r=n("b622"),a=r("species");t.exports=function(t,e){var n;return o(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!o(n.prototype)?i(n)&&(n=n[a],null===n&&(n=void 0)):n=void 0),new(void 0===n?Array:n)(0===e?0:e)}},7286:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXr5djn4dTp49bt59rT6LKxAAACnElEQVQozw3NUUwScRzA8d8R6MF8YMIx8uk47hDSJbj14IPzOGc7jPLvwTGg5uAYDbe2tt56cLtznvEnS6yDqCcEaWi91DvrbLJZz7b1aFtz1aO+2OZWvn+/+4CHeB6BMYaqBLfjPNRY6RFT2JJYby+uAk4WUTrtlmJ4hgPYb2q1XGDQjaK8pgJHvqNaAX+KyuIkDXpgQinb46nOulnn4b5laUHTxLfseeArAoNOeJlOIjdoal0n1FA7tKFv5roK+YaHOqP3P0XyKHPHY+MhTRe5uCZnKhtJKw2eSrSoBDPLtpZuNcFNJcFyiCMxOaaHIfXz1e8HQbWLySrBQ4x0x1qlhnHlnz2HQEC6TNb0gTHXa7IKhcaHqkE015hk9whA0YeWiLIXf7Fa2CZo3DjqjB4tTuF8jIcbfcEx5z/w4sXpQhXW+ju0cqh7icTFmRMaG+v6CIvTjcSpHcH8JEsF3EPh3fRthYdVLLgI2fWXm85/pGFE4l046s70L+yKCcirGFR+jbpy3kMmiCGHrSezVONsn1RBixncyk2PcVWk7DlgxHo8iZwDyq5uAUD854dZhdIFYzKoQig2haUKi1lVufz2RZUZPZ41n/hrOQB6h0Hhg8I367FNoEHgeM/KY7szSeQwD8q2WE3HM35ZLl0K1MJiOtHIkBclRQUwZnyOWcNsRQQgVLj1PSqkjF9DsoOSaSg3iinKzvfmgsNFFfpP/2T3GLGvL4fHEfwIX1sVvXcPqLztehWGcfn9nI2U9nTfCgJPe/jFPLZwgVEzimBgAm0VIyK2tt1cE/AzQdLK+SxLSQ4aDCZnnId94OG2S1XwvnTbNk/ZnhyRCQT+sZM6z9g6LXL1BOBe+zJySiFkHAINCtnQokbCJ/apCv0foqPiZVfhpywAAAAASUVORK5CYII="},"746f":function(t,e,n){var i=n("428f"),o=n("5135"),r=n("e538"),a=n("9bf2").f;t.exports=function(t){var e=i.Symbol||(i.Symbol={});o(e,t)||a(e,t,{value:r.f(t)})}},"75ab":function(t,e,n){"use strict";var i=n("63f0"),o=n.n(i);o.a},"7abd":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXm9PXq+Pno9vfs+vttWKBGAAACPElEQVQozw3RQWrbQACF4TfCMjPqZgIj4RRaxsZKE0PuMBZ2cLKaCI9RDAXFmJJknUWWI1O1UlamOMHJSjGkuFn3AD2Cr9CepDrAg+/xIxK4QwIqHHQkUhQ/WuphInVIFBojl8QXc012Tgq4RTtVHWVLZVFh1tEoI91uiN4joCqde8Ukn/zGM1B2W4ari2PtTwyw55Ld+Wways54qhGPyS6FzbIT3lIY8WwWdCq56Yolx6KmSKzoqrsCB5heAp4TGNQWJ1Pc6XlE5jQD5OlIX9I47A9uiUQcPQxcury/ToyxWJG/za6ki88crxKPocKS59Sl3EtBG7C89fCGflpfqoSzCeC4crioJA7F0V5+8MaSIk4qSCdwzpogmbqzEirVpGiS2dOVJvUuuqFEmhHao06KEpq+8lvHI14NJk3Qrmi9vBuRLwAz0qZB4hsDXQFXgtnlpDX3C6ug9BquSw/CYtwAzuTz5vuQNdr/YibhR68378ehZH30FSpjh71LpQkrsj+Q062h5WwZ5wlRoD6uQJy1DqvSYuCUapMBqT5YA4ZFw4KlWapxoUGlKWrx0eDQvmigu4WMYt97ruru98fYL8/0lG6CTOFcFWBhFK5gKw19h2JN808nh7xhkU6sWKLXdtkqBL6h+lULK5k19wFB/FldnGYf3LDeuf6IC2/MzJOSOP0qPxLqzaGIqtBcFIItrstkazONOkrc1D1czjuwEGESB4JJnjgSMN7PXAu7fZQpl1C236C+9mM4Af8P98Ch4R2TRl8AAAAASUVORK5CYII="},"802e":function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg=="},"82da":function(t,e,n){},"8aa5":function(t,e,n){"use strict";var i=n("6547").charAt;t.exports=function(t,e,n){return e+(n?i(t,e).length:1)}},9078:function(t,e,n){"use strict";var i=n("c0bc"),o=n.n(i);o.a},9263:function(t,e,n){"use strict";var i=n("ad6d"),o=n("9f7f"),r=RegExp.prototype.exec,a=String.prototype.replace,s=r,c=function(){var t=/a/,e=/b*/g;return r.call(t,"a"),r.call(e,"a"),0!==t.lastIndex||0!==e.lastIndex}(),l=o.UNSUPPORTED_Y||o.BROKEN_CARET,u=void 0!==/()??/.exec("")[1],A=c||u||l;A&&(s=function(t){var e,n,o,s,A=this,f=l&&A.sticky,d=i.call(A),g=A.source,h=0,p=t;return f&&(d=d.replace("y",""),-1===d.indexOf("g")&&(d+="g"),p=String(t).slice(A.lastIndex),A.lastIndex>0&&(!A.multiline||A.multiline&&"\n"!==t[A.lastIndex-1])&&(g="(?: "+g+")",p=" "+p,h++),n=new RegExp("^(?:"+g+")",d)),u&&(n=new RegExp("^"+g+"$(?!\\s)",d)),c&&(e=A.lastIndex),o=r.call(f?n:A,p),f?o?(o.input=o.input.slice(h),o[0]=o[0].slice(h),o.index=A.lastIndex,A.lastIndex+=o[0].length):A.lastIndex=0:c&&o&&(A.lastIndex=A.global?o.index+o[0].length:e),u&&o&&o.length>1&&a.call(o[0],n,(function(){for(s=1;sr)o.push(arguments[r++]);if(i=e,(d(e)||void 0!==t)&&!st(t))return f(e)||(e=function(t,e){if("function"==typeof i&&(e=i.call(this,t,e)),!st(e))return e}),o[1]=e,q.apply(null,o)}})}G[K][H]||k(G[K],H,G[K].valueOf),M(G,T),D[N]=!0},a640:function(t,e,n){"use strict";var i=n("d039");t.exports=function(t,e){var n=[][t];return!!n&&i((function(){n.call(null,e||function(){throw 1},1)}))}},a7bd:function(t,e,n){},ac1f:function(t,e,n){"use strict";var i=n("23e7"),o=n("9263");i({target:"RegExp",proto:!0,forced:/./.exec!==o},{exec:o})},ae40:function(t,e,n){var i=n("83ab"),o=n("d039"),r=n("5135"),a=Object.defineProperty,s={},c=function(t){throw t};t.exports=function(t,e){if(r(s,t))return s[t];e||(e={});var n=[][t],l=!!r(e,"ACCESSORS")&&e.ACCESSORS,u=r(e,0)?e[0]:c,A=r(e,1)?e[1]:void 0;return s[t]=!!n&&!o((function(){if(l&&!i)return!0;var t={length:-1};l?a(t,1,{enumerable:!0,get:c}):t[1]=1,n.call(t,u,A)}))}},af67:function(t,e,n){},b165:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXe3t7a2trc3Nzg4OCXP9lCAAACoklEQVQozwXBzU/TYBwA4N+QEr4CNbSFwcFuowSqMRvEAwShHWAYNsu7dS0dLnGUSWT4kZB4lGzE4VtcwgIDJqcOWLJxcv4BOoQZuCPxSNSD4WSWLJGL8XmAIiyo2RgJ4A1pxQQlOxRAszLTdnPu2oQGb05RC5slJld7ZAIfo4O44Bn1ud59F0BcjnYOa17Jhwc6EdiKettncsXjT1f8KUBZUW41pK0Jc1Az4dEV3rkkPBtDSZ83Blyt0kSf2PRjzIykoBwINisPbPPtljdVE9iAXRfUPkXLVIgYrCccp5g687NdZbcJ+xa5VE/HhTtT23IKsN5jj/pcUd0dTZNAqCVw72n4gOwnTOC0vvHfaauT8d9zAoRRfPpISZRVyUiw8ELzOG1b2DZpFzkSrHLhq52twDEdyZHwvp2j4uv/bjvOf23/AcEtTuJbY5Cp4YcAer1IGkUzOo2rn8LQOKjFJw3NTw24nprQXY5aF4wxcqcSdbFQ00H4xFl8Drx4X4CikvAM1tuR8bKIBCBoLnKN10KJG4zKAsc7c9WEB9gnCi6BhVjqoco6t20ILAJuVctvaEZK732cRHDRmGfuihOam0o2CHByUZ/epCcVlRs2wmCnMqsd6aSim3ibBJtm1LGyXW3Bb7tJCPlFtUG+SvPdeEUAB60lNdo+VQbLcwRNVtT68FsLcr1+NotgNihlpExS1V2SFgNbeC8bEhgm8sM17wSi6Us2gxVWJU/5GKBpandvfyYbU1yHCLpCgWGbbPXn40rehEsUXKIJr9DMKgICfjc4bl1YfvUhE/YIECGRqjCxSM9hrybAIkND5OeWfFZsXkxB+qDzb7pUQ3EfQ3Ml6EChEt3D+iS01VqC7EQ/Z/DuPQcz4yChoFQJce2Qr+NNAv0HxofmpXGqgHkAAAAASUVORK5CYII="},b671:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII="},b727:function(t,e,n){var i=n("0366"),o=n("44ad"),r=n("7b0b"),a=n("50c4"),s=n("65f0"),c=[].push,l=function(t){var e=1==t,n=2==t,l=3==t,u=4==t,A=6==t,f=5==t||A;return function(d,g,h,p){for(var m,v,b=r(d),C=o(b),y=i(g,h,3),S=a(C.length),x=0,B=p||s,I=e?B(d,S):n?B(d,0):void 0;S>x;x++)if((f||x in C)&&(m=C[x],v=y(m,x,b),t))if(e)I[x]=v;else if(v)switch(t){case 3:return!0;case 5:return m;case 6:return x;case 2:c.call(I,m)}else if(u)return!1;return A?-1:l||u?u:I}};t.exports={forEach:l(0),map:l(1),filter:l(2),some:l(3),every:l(4),find:l(5),findIndex:l(6)}},c0bc:function(t,e,n){},c84b:function(t,e,n){"use strict";n.r(e);var i=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"detail-wrapper"},[n("div",{staticClass:"detail"},[n("div",{staticClass:"bar"},[n("el-breadcrumb",{attrs:{separator:"/"}},[n("el-breadcrumb-item",{staticClass:"index",attrs:{to:{path:"/"}}},[t._v("书架")]),n("el-breadcrumb-item",{staticClass:"sub"},[t._v("目录")])],1)],1),n("el-divider"),n("div",{staticClass:"catalog"},t._l(this.$store.state.catalog,(function(e){return n("div",{key:e.index,staticClass:"note",on:{click:function(n){return t.toChapter(e.url,e.title,e.index)}}},[t._v(" "+t._s(e.title)+" ")])})),0)],1)])},o=[],r=n("bc3a"),a=n.n(r),s={data:function(){return{key:"value"}},mounted:function(){var t=this;a.a.get("/getChapterList?url="+encodeURIComponent(sessionStorage.getItem("bookUrl"))).then((function(e){t.$store.commit("setCatalog",e.data.data),sessionStorage.setItem("catalog",JSON.stringify(e.data.data))}),(function(t){throw t}))},methods:{toChapter:function(t,e,n){sessionStorage.setItem("chapterID",n),this.$router.push({path:"/chapter"})}}},c=s,l=(n("5ca7"),n("2877")),u=Object(l["a"])(c,i,o,!1,null,"57115a66",null);e["default"]=u.exports},c975:function(t,e,n){"use strict";var i=n("23e7"),o=n("4d64").indexOf,r=n("a640"),a=n("ae40"),s=[].indexOf,c=!!s&&1/[1].indexOf(1,-0)<0,l=r("indexOf"),u=a("indexOf",{ACCESSORS:!0,1:0});i({target:"Array",proto:!0,forced:c||!l||!u},{indexOf:function(t){return c?s.apply(this,arguments)||0:o(this,t,arguments.length>1?arguments[1]:void 0)}})},cf68:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEXm5ubo6Ojp6enr6+vt7e1FnZagAAACrklEQVQ4yx1SixUbMQgT3AKAFwDcAfzpBN1/qMrJS5w7bCQhC6IGSUGYQJd6Ox9ZPXi1AGJBavhUTT0JjYPGAab9WcDYIxsmlnxkayX8mhxCmKHA75az5cfRbWybEExiu08xDSgGym0mwuf3j4SvHeQxDJJzh2zp4iOlrD8iOb4SXyC1wiOLRTcnrje+nGamFeXVKWkmzbFIPChkmJ6Fg7mBpV8n+JGOVCd4jv1thThkjeQGNeafpeV3rsEWLfyWc8tC9jOv6FQ8rRzHOOVB+jCYEUAJpDvh8xHNFm/Tm5p5lw94Pp3NhtKEfQsGvnXhowdZE73hPwxKvjDd4i4PCdd0fe3W5fO8ktAsUAacLgstpUw60JCiPLg2XpkgiqPIYYXJd9ksGIT3q+LlevypzItvO+s0F1dBzVr2QDMUkYmuyGcrIS44mVJ7JVKwQXjYuBYp0Uetecbswzsikzu3gUR8bJC/C8Gd/NAzI/xdUGOYQQHDZ8X2d5XuzGRUiXAi9si5CRgoiToRZPtzLJkd0FUHRHZwJf0BHT1sE7gcnh0jmKKlSSF4/GBirGk5+K9NKlGDCfc9JtPhg78JdabH0YQRKNZnJ8tFnPfXHJb4xum1TTCeEmyEdbyEJLjznMLHuFD2Y9NEkSleIBs7SiCbblhgctVi9ch++kDYnn1C9DA5TvdPsToXM55wI6k+8eKT1blwPTqWb5CFJ+7dTBmab+KHy+xwNtItXhZNSpHD2fxnynrxG3ZBKRe8KBpXk11AnadlccEhr9w1nBBvBylNkv7A8eqpGBCDqhitmWQXBjjdS6idr/QjXWLDeMzMbVDoJuM8zN7WenMZWXgZ2vX3F01J3jHZbwk1LRP+DWEvDJtOUoh/AIaBUz5VpWyhuyx4QtgL/NmgC6kM/JvNe+R/C/5aL7BKIbYAAAAASUVORK5CYII="},d0e3:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXh7eHl8eXj7+Pn8+eTbH1KAAACPElEQVQozxWPQWrbQABF/0xn3JFKQRTZOIuUsbCCbOgdRoYEOauxkYPcTRyTlPQWIxEltrsRwQ6hK9nEQek6F+gNTE/Q3qLLusv34cN7SH3mFicdYW4gNIhJWXPBRVXzjcFD0IqeU4o4PRbAIVjyico0vJpIifqPfL80QN9DAQY5ucRHE/hpHxBldXe9GilaHKcKMlj6pho2zXgkNdBl0oJ8kiF1DSiJF1ZHBJkQr0Dbux/5I42Zp4cFahJDFGeW6/QjBwmFY/Q7vZ2SnoOdW2parv/Cnm81+m0xrEfiVXQ3W4nOXIqVYi3l6AAQBwMFkViVBANMto4enXHPNTkHBB0oVj4r5vHzCWayrgBvxtygDlDB2CNDjd80ZInY69aKVYZcfJ8DW+fWuc+syEODALx+ojqoafHsthTI+ZW27PGpIeo/cR6YKcbqIuIFhHmBrzAovzIOOJk1ucvcDzrMRYGVBH2yvcAOf0KiKwfRovBI3tm/kW1eemtfNWwIIXE2mJNhvoszfmMBfRCv0OPwd2321uDW3nx2q/BDxFVeoN1g7a6Im8yRnoawa8kbdXnU0cHeTMxKfZGlJgvLb3sKsxgglQnDdAfvj9LUnqWRDo0GiUmPwyU7TAsD7wHeIW3Nfy1qVGKoE9NgJCdYCAexNRob9yCn4DAQmXtQuUtera6bEmTTXhZy6h856xi4mnEl6BI9mfISkLbtJyZIMJIAUd5ZOBEu88KRAk71yxfItj/hpIB0Errv4gO1os4/UICf+o3kkqwAAAAASUVORK5CYII="},d28b:function(t,e,n){var i=n("746f");i("iterator")},d784:function(t,e,n){"use strict";n("ac1f");var i=n("6eeb"),o=n("d039"),r=n("b622"),a=n("9263"),s=n("9112"),c=r("species"),l=!o((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$
")})),u=function(){return"$0"==="a".replace(/./,"$0")}(),A=r("replace"),f=function(){return!!/./[A]&&""===/./[A]("a","$0")}(),d=!o((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]}));t.exports=function(t,e,n,A){var g=r(t),h=!o((function(){var e={};return e[g]=function(){return 7},7!=""[t](e)})),p=h&&!o((function(){var e=!1,n=/a/;return"split"===t&&(n={},n.constructor={},n.constructor[c]=function(){return n},n.flags="",n[g]=/./[g]),n.exec=function(){return e=!0,null},n[g](""),!e}));if(!h||!p||"replace"===t&&(!l||!u||f)||"split"===t&&!d){var m=/./[g],v=n(g,""[t],(function(t,e,n,i,o){return e.exec===a?h&&!o?{done:!0,value:m.call(e,n,i)}:{done:!0,value:t.call(n,e,i)}:{done:!1}}),{REPLACE_KEEPS_$0:u,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:f}),b=v[0],C=v[1];i(String.prototype,t,b),i(RegExp.prototype,g,2==e?function(t,e){return C.call(t,this,e)}:function(t){return C.call(t,this)})}A&&s(RegExp.prototype[g],"sham",!0)}},d81d:function(t,e,n){"use strict";var i=n("23e7"),o=n("b727").map,r=n("1dde"),a=n("ae40"),s=r("map"),c=a("map");i({target:"Array",proto:!0,forced:!s||!c},{map:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}})},ddb0:function(t,e,n){var i=n("da84"),o=n("fdbc"),r=n("e260"),a=n("9112"),s=n("b622"),c=s("iterator"),l=s("toStringTag"),u=r.values;for(var A in o){var f=i[A],d=f&&f.prototype;if(d){if(d[c]!==u)try{a(d,c,u)}catch(h){d[c]=u}if(d[l]||a(d,l,A),o[A])for(var g in r)if(d[g]!==r[g])try{a(d,g,r[g])}catch(h){d[g]=r[g]}}}},df5e:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAATlBMVEXdzaHh0KPgz6LdzKDezqLczJ7ezZ/fz6Dcy5zi0aXdzZ3fz6Tfz57h0KDg0aLcyZrg0KXi0qPfzZ3j06bh0qbdyJbfzJrhz5/cxpLZwo0vDconAAAFn0lEQVRIxxyPW5LjMAwDAT5FybLl2JnM3P+i6+wXWVC1GoQGaD0h4XM3Q5o4T0HgABHBi6pZ4CDXXcUOFd6VhqC3Kch4EI8w9oMXwvU6m5LOOvcxKMOhuu8i5+5cMjcgb0t4F2uvOoeI3/MlT4IqsbtM9UG2AGSXUOsxzPevnXzK1CSHytZLvx7VdQmUcJsJCxJh2nmHW12Qod1qPjt8pih47uQ9aGpoNWF+yElCt60oH7vdIU/MnlRPSBLC/VwqxcKR8PFqnADN9ih5ufqnTlG9KwCofvs7kKYqOPHTNMQ93j9qNImFw9vjHPZ0F1m8hUUVB/Q/TrRYDMXr9++APMFARAt6sPh6wVAXzxUGhZsFUwCNfPZ8/72TAHebAhvuOuT3gO1Vn5d9Jd5sBRkg0p2seL9B7ulkjFJFIt9HPpLzdSzzMP3UcodAfMqC6pBuET2heHK1itZf1GZ1bi0BwOSxiCS8f/JBHMPMM4XCu3Mt1uz9lJbDJRqsKDZuikzkvskQEz6hanfDfO494azY5JpqPqOF1RhxD9XYEdaNxiqWqakKgmPfmrsta8KAiwF4HBxGVUJAgeSqQaiRRZJ7D2jedhw5t1CIAKxag0CBA60BpoBE6DcUi8O5AuM4pLfN0kHLmeu2B4e6HofqbgxsTWUw3PAODqa1oDtyzgXBlusi1KFdclMPE8O3jvLJ8RNi5/RxDQVzVmXA233XQ4KummunfxvLOZo+iH37964YjP06995CTdu9hsvErqJNzmf4wTrZ5DL7+qW9EoLnadrx67b8dUtrJnBXaT1N1uvPaYRKpWkq52xNsMN7vv4Sdryt/f4MhQoMCKnvVxikai1CQ6ZsnwJDc8+3Y/z8HcfvYQNq66pnAu1Hwa+3KNSwbNu8h3nDPqTl9fl7tx8fBhFfdS0o0F3JUKEZtZG9b/LZEM95lzaR30OnWPzroMxyZYdBIMoMnpN0J+m7/40+/P4soFSUjgzE7yY5zrMJuoZv0CmpVguYx1pprfb5HOviRVhHUVi/352shxCYrYBZxGtVaxiAz/MsaGSIsB7R1t4zJXH//n7RTTQQwxqcGEqEvklFHUgiO2GvJV+jAIPR+N29usWDoiSOVrN3XuqT1egQJAAU9EwslVJC8u0rGcy+WPqktJhjfMpatIG6CDAb0v5H34MGKqiVRue7GGLZ9Otxtt4JIrAhxBDwDuqI9JavcO0A7GlqFt219tH/bln9jBXzaKWAEqJV0CBxs5TwM8EvUPHaa8S86vN303MVWOsl3goDBHPWSoQ9c0kQmCKljfsKNH1+ofEOHW8a9a7glZGS8fPieL/SRSs0LAhI4FDTnXs1QYtubv2+IXPZpHB4bhivRexBkYKsSrYXNjvMUbVXpVJ+N6haV72c1k2zrnv5IYBMJBYTSZx0KTkoM3vY93rU/qs7zHplc/3d2ACadhFWByrn9LUk2IWb5JywvawTQc3F0iz+lgsBmInAIemBJtft2plKIlAFOgcroigrG2XlDsAzywQECNyaI8yr2ogoh7D4qJOYmZBzQgoZAM1PAcB8sDrr1uE5CDMR+nWSSVUGUCHAs8Vd21HOE0FzNj37pX0sLp9p3K8k++xxpkmzDxK64rmTSJnDUuIgTeslui6lg92jonZXI4jqNiUuzN4IagcKMjCniMGCODoo8T4tGDprn2hRww+NrnYiCwokd9iiWrkmbRfXYGLAoZrjO1lVQKExjUy5fIkgJURmz2uGFdASwwlWx5gDVTMK7hP6ISRVsFbYNmqtZL9MQtio285PaekyzDhZmtdexCYB0SZcTmBdhvdbmAEonk8hwcHQuZN1kVqrhyKoHHsnQhQAjF7SG533Da2S4LGjx1LoZqp7XeKQLDUBmYmydG0NQHpMeR5lRIRQc1PQ2ASMQflF4YBDMt0/GFlEHeRwCcEAAAAASUVORK5CYII="},e01a:function(t,e,n){"use strict";var i=n("23e7"),o=n("83ab"),r=n("da84"),a=n("5135"),s=n("861d"),c=n("9bf2").f,l=n("e893"),u=r.Symbol;if(o&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var A={},f=function(){var t=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),e=this instanceof f?new u(t):void 0===t?u():u(t);return""===t&&(A[e]=!0),e};l(f,u);var d=f.prototype=u.prototype;d.constructor=f;var g=d.toString,h="Symbol(test)"==String(u("test")),p=/^Symbol\((.*)\)[^)]+$/;c(d,"description",{configurable:!0,get:function(){var t=s(this)?this.valueOf():this,e=g.call(t);if(a(A,t))return"";var n=h?e.slice(7,-1):e.replace(p,"$1");return""===n?void 0:n}}),i({global:!0,forced:!0},{Symbol:f})}},e160:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEX6+fP8+/X+/ff///kbczPAAAACeElEQVQozxXHQUgUUQAG4P8936yzs6VvZNZmN9QxVxiF9OLBoOjtOC6rQq6ygXjI2fCQBdXBg4egtzFGdqkoI+zgBFbqkm3hQSxhFYLotOcubeKhOnVYoqQy+m4f5g5TvpX0xHLbLY9j8SMhJp+Jk4LfAUS2kVRIjILmnwGBTX42PhCVlDJQkIiy2nWAvaJ1h+oFIpJ0hMSYVbyyrgDWshcMpMyL1brPDQKWmduO+KTJ6XeXAMK9Yc3FpD7atyNwg6kt5XgFpLPhjUTFSYVn2abDiugGShwD8JTVRJVo/2ecuKtRb/qc4BK+9TboFfokog4T2Fn6Oqdnsjk90NMS76Rji6E0NmwkPBAZ4Xbkw8KoDAkAbEhkc78e9omxxgxg6qa5HvMv+UZbCV0qmHnSHKl5TxeA2XTCGWekR581mwC5crBH81PznASqB9va3TbkYAjJPLfg5uBfXaJgIgIBv9eessRIhxe7PA7kj6uUMeMaQ/OEQOYRaaHlqH2Gxwsl6E/pwVY5FH7uCypBZPKvDQyVziYBrAkMURe2MOOOxG/eQpp5PF+bFzUV5HtPj9GeiVSNZDELleifYTp9NAjsoiXg4cW+4ZORkdSMB/B74aAdjhsVakhgkugsbmqcDSLEoWp8zRjrux3tli6Q5uM3E+maT99Wy0RiP7tboiuRZle2c6CYeL2kcUc1KvPtQKucogMadKVTQOJYCeyCYlhQQ/Q7Etfd/vBygy9iqy+LyHeF46saCYvW6ingsbA9RBWtdi8GgUXW+oQx9/wP6bAAX1TWeV+CbShZDlQ9xT6SoSxZmKRAkmXb60kzEzkRF+Ccb94BGspGJoN/UzmyR4wjXHAAAAAASUVORK5CYII="},e538:function(t,e,n){var i=n("b622");e.f=i},e8b5:function(t,e,n){var i=n("c6b6");t.exports=Array.isArray||function(t){return"Array"==i(t)}},ec0f:function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAALVBMVEXx58b168ny6Mjz6sn06sf27Mvw5sTz6cbw5cLy58T37svv47/168v37s7t4Ltrv0//AAAEjUlEQVQ4yw2Ty2sTURxGf3dmOqmPxb0zmaStCnfmZpL6gpmbxIpUSMZGrSJkxsZiVZimNVaqMklrUnWTRq2KIDFWWx+IFrIRFxXEB4KIgqu6EBdu7M6FIPg32PW3+DhwDmBaYrK56KP4HGIsvg/uvOV0wK+qgBMlO9BujuH4DSJlOseqV5a/BEF97gt0ChyIPqBhXI9BtqtIB8vJB/LdCQ3OVjaLNX0g7+OmoI4e7nkemAqX6o8vg0yyQAyQS7IfgvFbI+6QyI3R4KELxw7kwM2ooQfyQigYnwY5MZbMlHI1DvnQVCoVcrt+R+bO7vPDif3ybNajwqAAe443dpfDsPt379VMWZzGRuqM79mQF+DUz9nt74bQ8J/O80MtVR51U02JKKmTCvTzLVf+vuxP/aHnPo9+2bW+zVsJ0Y630/CrfzX+b+UL+7O68Rczv+7lrMh5etfKXvhc2rk6KforxuoO2xB2tcxKfeXHt18rHOiHI/0RRjW/YGRDkHiwo3nzqL60o58C/bgRuaj7vk+QOwOhpnFNdjuWpKMCGP8Yapu9Ty5FTHKQLGSEFikjd9ADwP9ciaNNjc5qMH6w50AF/LKOsOYqsOG9GjKgc7ZXolqntm6fysJ6Ma6ll2CiqmOgE6O7x1wXExklbeqMYcwsmJmOoigt8SBg2WfilDSsAZJcBxDcrqtBXzFQJqZNHfscyIhoZlygAtyYAceah+elrFbI+46gEHDGiW878Kj7JpWyfhg6iyRMymV1MKBSeVpfgLHIohyTojI6sRyK1VpcqzVZeEBLOnA9unhGKUXPJDYtV9Dxuz4iA5xSkSWhCJdAiJR9PHlvfvbntbrR14FDqUNRAYDJmSnv3oKxuz5+7fiblgVJyYLTbgUM05P7LESkoXvyWNfb0aUU6FZizgQIa25VqKQZqFrk6v6BsqqIHlQmkQ9KrBhkC20/DrFsAFEEYLjM+lj2wYHXCwnNvZQR42XJ2iVK+UBXnI+OBE6oXpUUHiQ1yg0MhA03iwGbnOdQYc1CMiPIPQrCQJFH4L4BMFktAtKd9PN5gnU2Gra4KuK+V+mjtBRpAGIqDVe4wnSnajiFGO5d7smvhVQEMEYwqshrENIEaY7YeblJYtsb3QhAHWZCEKK67swwPMKw0If1Ta+6DgHmlgPzcUTSbi3rrv1Y64/BYEMPQ5SDHUOR022B4QRF6xLUPAaPX/V4IDI5N2BMwx4LqO1uO4j6uW7NvM7lATqGAxY/ZHVgoGZbu7SvkNR75x6qGSB23FdouENVwN7sCbewTdsXGrrnQ5ZZKOCOFtMTIzxlPu6eYmtL+nMFmoK7OeXajn86r9sqWbfmvHC4IagE5qfCPGZvLSq5F55hHIxJFa4/vRxHBlz0og4TojU1l/MOHJX17lybdF0mQhFO44JYUNt3UA473IXw/iPfDWtKG5oFSXIF5iU/VnyDSjxxeDk3jAXRyVyGTNB9FxH9qcFDNJpVbt2y9LytUXkK7Py6+z1RezHQqnoY8XcLimmd8dCnBhQCuaGpJCq3SoIlmYvLz8UkWhJw7T8k+Db/DYEKwgAAAABJRU5ErkJggg=="},fdbc:function(t,e){t.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}}}]); \ No newline at end of file diff --git a/app/src/main/assets/web/manifest.json b/app/src/main/assets/web/new/manifest.json old mode 100644 new mode 100755 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/assets/web/new/precache-manifest.78eb8adcb8f052b2a72d462abe0dc498.js b/app/src/main/assets/web/new/precache-manifest.78eb8adcb8f052b2a72d462abe0dc498.js new file mode 100644 index 000000000..058fc361f --- /dev/null +++ b/app/src/main/assets/web/new/precache-manifest.78eb8adcb8f052b2a72d462abe0dc498.js @@ -0,0 +1,74 @@ +self.__precacheManifest = (self.__precacheManifest || []).concat([ + { + "revision": "a77097c019b699bc81ee", + "url": "css/about.dbe575e1.css" + }, + { + "revision": "4d729c4b428d537ebd8d", + "url": "css/app.e4c919b7.css" + }, + { + "revision": "3e91096748e0f4d6bb89", + "url": "css/chunk-vendors.ad4ff18f.css" + }, + { + "revision": "8f2124417070a994ebbd", + "url": "css/detail.9ba76c69.css" + }, + { + "revision": "535877f50039c0cb49a6196a5b7517cd", + "url": "fonts/element-icons.535877f5.woff" + }, + { + "revision": "732389ded34cb9c52dd88271f1345af9", + "url": "fonts/element-icons.732389de.ttf" + }, + { + "revision": "f9a3fb0e145017e166dd4d91d9280cc4", + "url": "fonts/iconfont.f9a3fb0e.woff" + }, + { + "revision": "f39ecc1a1d2a1eff3aca8aadd818bb61", + "url": "fonts/popfont.f39ecc1a.ttf" + }, + { + "revision": "6c094b6d4ae9404dbed273c41b06fae8", + "url": "fonts/shelffont.6c094b6d.ttf" + }, + { + "revision": "b5c48bc1e1fe73212a31be704875b71f", + "url": "img/noCover.b5c48bc1.jpeg" + }, + { + "revision": "ad9f43586bb9220e0df71ce8fad92d8b", + "url": "index.html" + }, + { + "revision": "a77097c019b699bc81ee", + "url": "js/about.59a63964.js" + }, + { + "revision": "2c81bd893f3a92f018d8", + "url": "js/about~detail.1caf6ef5.js" + }, + { + "revision": "4d729c4b428d537ebd8d", + "url": "js/app.d7843716.js" + }, + { + "revision": "3e91096748e0f4d6bb89", + "url": "js/chunk-vendors.8dd9045a.js" + }, + { + "revision": "8f2124417070a994ebbd", + "url": "js/detail.11777eca.js" + }, + { + "revision": "b46d04eb43bc31ca0f9f95121646440d", + "url": "manifest.json" + }, + { + "revision": "b6216d61c03e6ce0c9aea6ca7808f7ca", + "url": "robots.txt" + } +]); \ No newline at end of file diff --git a/app/src/main/assets/web/new/robots.txt b/app/src/main/assets/web/new/robots.txt new file mode 100755 index 000000000..eb0536286 --- /dev/null +++ b/app/src/main/assets/web/new/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/app/src/main/assets/web/new/service-worker.js b/app/src/main/assets/web/new/service-worker.js new file mode 100755 index 000000000..0e5255383 --- /dev/null +++ b/app/src/main/assets/web/new/service-worker.js @@ -0,0 +1,34 @@ +/** + * Welcome to your Workbox-powered service worker! + * + * You'll need to register this file in your web app and you should + * disable HTTP caching for this file too. + * See https://goo.gl/nhQhGp + * + * The rest of the code is auto-generated. Please don't update this file + * directly; instead, make changes to your Workbox build configuration + * and re-run your build process. + * See https://goo.gl/2aRDsh + */ + +importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); + +importScripts( + "precache-manifest.78eb8adcb8f052b2a72d462abe0dc498.js" +); + +workbox.core.setCacheNameDetails({prefix: "yd-web-tool"}); + +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + +/** + * The workboxSW.precacheAndRoute() method efficiently caches and responds to + * requests for URLs in the manifest. + * See https://goo.gl/S9QRab + */ +self.__precacheManifest = [].concat(self.__precacheManifest || []); +workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 7cb9eff1f..37aea552d 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -1,28 +1,31 @@ package io.legado.app -import android.app.Application 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 android.provider.Settings import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDexApplication 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.ThemeStore -import io.legado.app.utils.getCompatColor -import io.legado.app.utils.getPrefInt +import io.legado.app.utils.* @Suppress("DEPRECATION") -class App : Application() { +class App : MultiDexApplication() { companion object { @JvmStatic @@ -32,15 +35,18 @@ class App : Application() { @JvmStatic lateinit var db: AppDatabase private set - } - var versionCode = 0 - var versionName = "" + lateinit var androidId: String + var versionCode = 0 + var versionName = "" + } override fun onCreate() { super.onCreate() INSTANCE = this - CrashHandler().init(this) + androidId = Settings.System.getString(contentResolver, Settings.Secure.ANDROID_ID) + CrashHandler(this) + LanguageUtils.setConfigurationOld(this) db = AppDatabase.createDatabase(INSTANCE) packageManager.getPackageInfo(packageName, 0)?.let { versionCode = it.versionCode @@ -60,7 +66,8 @@ class App : Application() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) { - Configuration.UI_MODE_NIGHT_YES, Configuration.UI_MODE_NIGHT_NO -> applyDayNight() + Configuration.UI_MODE_NIGHT_YES, + Configuration.UI_MODE_NIGHT_NO -> applyDayNight() } } @@ -68,28 +75,58 @@ 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_brown_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) + } + val bBackground = + getPrefInt(PreferKey.cNBBackground, getCompatColor(R.color.md_grey_850)) + 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_brown_500)) + 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) + } + val bBackground = + getPrefInt(PreferKey.cBBackground, getCompatColor(R.color.md_grey_200)) + 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 +134,7 @@ class App : Application() { ReadBookConfig.upBg() applyTheme() initNightMode() + postEvent(EventBus.RECREATE, "") } private fun initNightMode() { @@ -118,7 +156,7 @@ class App : Application() { //用唯一的ID创建渠道对象 val downloadChannel = NotificationChannel( channelIdDownload, - getString(R.string.download_offline), + getString(R.string.action_download), NotificationManager.IMPORTANCE_LOW ) //初始化channel diff --git a/app/src/main/java/io/legado/app/api/ReaderProvider.kt b/app/src/main/java/io/legado/app/api/ReaderProvider.kt new file mode 100644 index 000000000..fcf50aa9c --- /dev/null +++ b/app/src/main/java/io/legado/app/api/ReaderProvider.kt @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2020 w568w + */ +package io.legado.app.api + +import android.content.ContentProvider +import android.content.ContentResolver +import android.content.ContentValues +import android.content.UriMatcher +import android.database.CharArrayBuffer +import android.database.ContentObserver +import android.database.Cursor +import android.database.DataSetObserver +import android.net.Uri +import android.os.Bundle +import com.google.gson.Gson +import io.legado.app.web.controller.BookshelfController +import io.legado.app.web.controller.SourceController +import io.legado.app.web.utils.ReturnData +import java.util.* + +/** + * Export book data to other app. + */ +class ReaderProvider : ContentProvider() { + private enum class RequestCode { + SaveSource, SaveSources, SaveBook, DeleteSources, GetSource, GetSources, GetBookshelf, GetChapterList, GetBookContent + } + + private val postBodyKey = "json" + private val sMatcher by lazy { + UriMatcher(UriMatcher.NO_MATCH).apply { + "${context?.applicationInfo?.packageName}.readerProvider".also { authority -> + addURI(authority, "source/insert", RequestCode.SaveSource.ordinal) + addURI(authority, "sources/insert", RequestCode.SaveSources.ordinal) + addURI(authority, "book/insert", RequestCode.SaveBook.ordinal) + addURI(authority, "sources/delete", RequestCode.DeleteSources.ordinal) + addURI(authority, "source/query", RequestCode.GetSource.ordinal) + addURI(authority, "sources/query", RequestCode.GetSources.ordinal) + addURI(authority, "books/query", RequestCode.GetBookshelf.ordinal) + addURI(authority, "book/chapter/query", RequestCode.GetChapterList.ordinal) + addURI(authority, "book/content/query", RequestCode.GetBookContent.ordinal) + } + } + } + + override fun onCreate() = false + + override fun delete( + uri: Uri, + selection: String?, + selectionArgs: Array? + ): Int { + if (sMatcher.match(uri) < 0) return -1 + when (RequestCode.values()[sMatcher.match(uri)]) { + RequestCode.DeleteSources -> SourceController.deleteSources(selection) + else -> throw IllegalStateException( + "Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name + ) + } + return 0 + } + + override fun getType(uri: Uri) = throw UnsupportedOperationException("Not yet implemented") + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + if (sMatcher.match(uri) < 0) return null + when (RequestCode.values()[sMatcher.match(uri)]) { + RequestCode.SaveSource -> values?.let { + SourceController.saveSource(values.getAsString(postBodyKey)) + } + RequestCode.SaveBook -> values?.let { + BookshelfController.saveBook(values.getAsString(postBodyKey)) + } + RequestCode.SaveSources -> values?.let { + SourceController.saveSources(values.getAsString(postBodyKey)) + } + else -> throw IllegalStateException( + "Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name + ) + } + return null + } + + override fun query( + uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String? + ): Cursor? { + val map: MutableMap> = HashMap() + uri.getQueryParameter("url")?.let { + map["url"] = arrayListOf(it) + } + uri.getQueryParameter("index")?.let { + map["index"] = arrayListOf(it) + } + return if (sMatcher.match(uri) < 0) null else when (RequestCode.values()[sMatcher.match(uri)]) { + RequestCode.GetSource -> SimpleCursor(SourceController.getSource(map)) + RequestCode.GetSources -> SimpleCursor(SourceController.sources) + RequestCode.GetBookshelf -> SimpleCursor(BookshelfController.bookshelf) + RequestCode.GetBookContent -> SimpleCursor(BookshelfController.getBookContent(map)) + RequestCode.GetChapterList -> SimpleCursor(BookshelfController.getChapterList(map)) + else -> throw IllegalStateException( + "Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name + ) + } + } + + override fun update( + uri: Uri, values: ContentValues?, selection: String?, + selectionArgs: Array? + ) = throw UnsupportedOperationException("Not yet implemented") + + + /** + * Simple inner class to deliver json callback data. + * + * Only getString() makes sense. + */ + private class SimpleCursor(data: ReturnData?) : Cursor { + + private val mData: String = Gson().toJson(data) + + override fun getCount() = 1 + + override fun getPosition() = 0 + + override fun move(i: Int) = true + + override fun moveToPosition(i: Int) = true + + override fun moveToFirst() = true + + override fun moveToLast() = true + + override fun moveToNext() = true + + override fun moveToPrevious() = true + + override fun isFirst() = true + + override fun isLast() = true + + override fun isBeforeFirst() = true + + override fun isAfterLast() = true + + override fun getColumnIndex(s: String) = 0 + + @Throws(IllegalArgumentException::class) + override fun getColumnIndexOrThrow(s: String): Int { + throw UnsupportedOperationException("Not yet implemented") + } + + override fun getColumnName(i: Int) = null as String? + + override fun getColumnNames() = arrayOf() + + override fun getColumnCount() = 0 + + override fun getBlob(i: Int) = ByteArray(0) + + override fun getString(i: Int) = mData + + override fun copyStringToBuffer( + i: Int, + charArrayBuffer: CharArrayBuffer + ) { + } + + override fun getShort(i: Int) = 0.toShort() + + + override fun getInt(i: Int) = 0 + + override fun getLong(i: Int) = 0L + + override fun getFloat(i: Int) = 0F + + override fun getDouble(i: Int) = 0.toDouble() + + override fun getType(i: Int) = 0 + + override fun isNull(i: Int) = false + + override fun deactivate() {} + override fun requery() = false + + override fun close() {} + override fun isClosed() = false + + override fun registerContentObserver(contentObserver: ContentObserver) {} + override fun unregisterContentObserver(contentObserver: ContentObserver) {} + override fun registerDataSetObserver(dataSetObserver: DataSetObserver) {} + override fun unregisterDataSetObserver(dataSetObserver: DataSetObserver) {} + override fun setNotificationUri(contentResolver: ContentResolver, uri: Uri) {} + + override fun getNotificationUri() = null as Uri? + + override fun getWantsAllOnMoveCalls() = false + + override fun setExtras(bundle: Bundle) {} + override fun getExtras() = null as Bundle? + + override fun respond(bundle: Bundle) = null as Bundle? + + } +} \ No newline at end of file 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..b1d3c76d9 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -1,20 +1,23 @@ package io.legado.app.base +import android.content.Context +import android.content.res.Configuration import android.os.Bundle +import android.util.AttributeSet import android.view.Menu import android.view.MenuItem import android.view.View import android.view.WindowManager +import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import io.legado.app.R +import io.legado.app.constant.AppConst import io.legado.app.constant.Theme import io.legado.app.lib.theme.ATH -import io.legado.app.lib.theme.ColorUtils +import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.primaryColor -import io.legado.app.utils.applyOpenTint -import io.legado.app.utils.applyTint -import io.legado.app.utils.disableAutoFill -import io.legado.app.utils.hideSoftInput +import io.legado.app.ui.widget.TitleBar +import io.legado.app.utils.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel @@ -22,21 +25,66 @@ import kotlinx.coroutines.cancel abstract class BaseActivity( private val layoutID: Int, - private val fullScreen: Boolean = true, - private val theme: Theme = Theme.Auto + val fullScreen: Boolean = true, + private val theme: Theme = Theme.Auto, + private val toolBarTheme: Theme = Theme.Auto, + private val transparent: Boolean = false ) : AppCompatActivity(), CoroutineScope by MainScope() { + val isInMultiWindow: Boolean + get() { + return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + isInMultiWindowMode + } else { + false + } + } + + override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LanguageUtils.setConfiguration(newBase)) + } + + override fun onCreateView( + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? { + if (AppConst.menuViewNames.contains(name) && parent?.parent is FrameLayout) { + (parent.parent as View).setBackgroundColor(backgroundColor) + } + return super.onCreateView(parent, name, context, attrs) + } + override fun onCreate(savedInstanceState: Bundle?) { window.decorView.disableAutoFill() initTheme() setupSystemBar() super.onCreate(savedInstanceState) setContentView(layoutID) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + findViewById(R.id.title_bar) + ?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen) + } onActivityCreated(savedInstanceState) observeLiveBus() } + override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration?) { + super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) + findViewById(R.id.title_bar) + ?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen) + setupSystemBar() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + findViewById(R.id.title_bar) + ?.onMultiWindowModeChanged(isInMultiWindow, fullScreen) + setupSystemBar() + } + override fun onDestroy() { super.onDestroy() cancel() @@ -47,7 +95,7 @@ abstract class BaseActivity( final override fun onCreateOptionsMenu(menu: Menu?): Boolean { return menu?.let { val bool = onCompatCreateOptionsMenu(it) - it.applyTint(this, theme) + it.applyTint(this, toolBarTheme) bool } ?: super.onCreateOptionsMenu(menu) } @@ -60,9 +108,7 @@ abstract class BaseActivity( return true } - open fun onCompatCreateOptionsMenu(menu: Menu): Boolean { - return super.onCreateOptionsMenu(menu) - } + open fun onCompatCreateOptionsMenu(menu: Menu) = super.onCreateOptionsMenu(menu) final override fun onOptionsItemSelected(item: MenuItem?): Boolean { item?.let { @@ -74,25 +120,32 @@ abstract class BaseActivity( return item != null && onCompatOptionsItemSelected(item) } - open fun onCompatOptionsItemSelected(item: MenuItem): Boolean { - return super.onOptionsItemSelected(item) - } + open fun onCompatOptionsItemSelected(item: MenuItem) = super.onOptionsItemSelected(item) private fun initTheme() { - ATH.applyBackgroundTint(window.decorView) when (theme) { - Theme.Dark -> setTheme(R.style.AppTheme_Dark) - Theme.Light -> setTheme(R.style.AppTheme_Light) - else -> if (ColorUtils.isColorLight(primaryColor)) { - setTheme(R.style.AppTheme_Light) - } else { + Theme.Transparent -> setTheme(R.style.AppTheme_Transparent) + Theme.Dark -> { setTheme(R.style.AppTheme_Dark) + ATH.applyBackgroundTint(window.decorView) + } + Theme.Light -> { + setTheme(R.style.AppTheme_Light) + ATH.applyBackgroundTint(window.decorView) + } + else -> { + if (ColorUtils.isColorLight(primaryColor)) { + setTheme(R.style.AppTheme_Light) + } else { + setTheme(R.style.AppTheme_Dark) + } + ATH.applyBackgroundTint(window.decorView) } } } private fun setupSystemBar() { - if (fullScreen) { + if (fullScreen && !isInMultiWindow) { window.clearFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION @@ -102,11 +155,16 @@ abstract class BaseActivity( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE } ATH.setStatusBarColorAuto(this, fullScreen) - if (theme == Theme.Dark) { - ATH.setLightStatusBar(this, false) - } else if (theme == Theme.Light) { - ATH.setLightStatusBar(this, true) + if (toolBarTheme == Theme.Dark) { + ATH.setLightStatusBar(window, false) + } else if (toolBarTheme == Theme.Light) { + ATH.setLightStatusBar(window, true) } + upNavigationBarColor() + } + + open fun upNavigationBarColor() { + ATH.setNavigationBarColorAuto(this) } open fun observeLiveBus() { diff --git a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt index 84e9d4b0b..6b2ca45ba 100644 --- a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt @@ -3,12 +3,15 @@ package io.legado.app.base import android.os.Bundle import android.view.View import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager import io.legado.app.help.coroutine.Coroutine +import io.legado.app.lib.theme.ThemeStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext + abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main @@ -21,12 +24,24 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + view.setBackgroundColor(ThemeStore.backgroundColor()) onFragmentCreated(view, savedInstanceState) observeLiveBus() } abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?) + override fun show(manager: FragmentManager, tag: String?) { + try { + //在每个add事务前增加一个remove事务,防止连续的add + manager.beginTransaction().remove(this).commit() + super.show(manager, tag) + } catch (e: Exception) { + //同一实例使用不同的tag会异常,这里捕获一下 + e.printStackTrace() + } + } + override fun onDestroy() { super.onDestroy() job.cancel() @@ -36,9 +51,7 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { scope: CoroutineScope = this, context: CoroutineContext = Dispatchers.IO, block: suspend CoroutineScope.() -> T - ): Coroutine { - return Coroutine.async(scope, context) { block() } - } + ) = Coroutine.async(scope, context) { block() } open fun observeLiveBus() { } diff --git a/app/src/main/java/io/legado/app/base/BaseFragment.kt b/app/src/main/java/io/legado/app/base/BaseFragment.kt index c19033aa8..155bb6076 100644 --- a/app/src/main/java/io/legado/app/base/BaseFragment.kt +++ b/app/src/main/java/io/legado/app/base/BaseFragment.kt @@ -1,11 +1,14 @@ package io.legado.app.base import android.annotation.SuppressLint +import android.content.res.Configuration import android.os.Bundle import android.view.* import androidx.appcompat.view.SupportMenuInflater import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment +import io.legado.app.R +import io.legado.app.ui.widget.TitleBar import io.legado.app.utils.applyTint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -36,12 +39,30 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + onMultiWindowModeChanged() onFragmentCreated(view, savedInstanceState) observeLiveBus() } abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?) + override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) { + super.onMultiWindowModeChanged(isInMultiWindowMode) + onMultiWindowModeChanged() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + onMultiWindowModeChanged() + } + + private fun onMultiWindowModeChanged() { + (activity as? BaseActivity)?.let { + view?.findViewById(R.id.title_bar) + ?.onMultiWindowModeChanged(it.isInMultiWindow, it.fullScreen) + } + } + override fun onDestroy() { super.onDestroy() job.cancel() diff --git a/app/src/main/java/io/legado/app/base/BasePreferenceFragment.kt b/app/src/main/java/io/legado/app/base/BasePreferenceFragment.kt new file mode 100644 index 000000000..b2e467b7c --- /dev/null +++ b/app/src/main/java/io/legado/app/base/BasePreferenceFragment.kt @@ -0,0 +1,61 @@ +package io.legado.app.base + +import android.annotation.SuppressLint +import androidx.fragment.app.DialogFragment +import androidx.preference.* +import io.legado.app.ui.widget.prefs.EditTextPreferenceDialog +import io.legado.app.ui.widget.prefs.ListPreferenceDialog +import io.legado.app.ui.widget.prefs.MultiSelectListPreferenceDialog + +abstract class BasePreferenceFragment : PreferenceFragmentCompat() { + + private val dialogFragmentTag = "androidx.preference.PreferenceFragment.DIALOG" + + @SuppressLint("RestrictedApi") + override fun onDisplayPreferenceDialog(preference: Preference) { + + var handled = false + if (callbackFragment is OnPreferenceDisplayDialogCallback) { + handled = + (callbackFragment as OnPreferenceDisplayDialogCallback) + .onPreferenceDisplayDialog(this, preference) + } + if (!handled && activity is OnPreferenceDisplayDialogCallback) { + handled = (activity as OnPreferenceDisplayDialogCallback) + .onPreferenceDisplayDialog(this, preference) + } + + if (handled) { + return + } + + // check if dialog is already showing + if (parentFragmentManager.findFragmentByTag(dialogFragmentTag) != null) { + return + } + + val f: DialogFragment = when (preference) { + is EditTextPreference -> { + EditTextPreferenceDialog.newInstance(preference.getKey()) + } + is ListPreference -> { + ListPreferenceDialog.newInstance(preference.getKey()) + } + is MultiSelectListPreference -> { + MultiSelectListPreferenceDialog.newInstance(preference.getKey()) + } + else -> { + throw IllegalArgumentException( + "Cannot display dialog for an unknown Preference type: " + + preference.javaClass.simpleName + + ". Make sure to implement onPreferenceDisplayDialog() to handle " + + "displaying a custom dialog for this Preference." + ) + } + } + f.setTargetFragment(this, 0) + f.show(parentFragmentManager, dialogFragmentTag) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/BaseService.kt b/app/src/main/java/io/legado/app/base/BaseService.kt index 163ac09ec..f00540526 100644 --- a/app/src/main/java/io/legado/app/base/BaseService.kt +++ b/app/src/main/java/io/legado/app/base/BaseService.kt @@ -16,13 +16,9 @@ abstract class BaseService : Service(), CoroutineScope by MainScope() { scope: CoroutineScope = this, context: CoroutineContext = Dispatchers.IO, block: suspend CoroutineScope.() -> T - ): Coroutine { - return Coroutine.async(scope, context) { block() } - } + ) = Coroutine.async(scope, context) { block() } - override fun onBind(intent: Intent?): IBinder? { - return null - } + override fun onBind(intent: Intent?) = null override fun onDestroy() { super.onDestroy() diff --git a/app/src/main/java/io/legado/app/base/VMBaseActivity.kt b/app/src/main/java/io/legado/app/base/VMBaseActivity.kt index 3643588af..f8de615de 100644 --- a/app/src/main/java/io/legado/app/base/VMBaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/VMBaseActivity.kt @@ -6,8 +6,9 @@ import io.legado.app.constant.Theme abstract class VMBaseActivity( layoutID: Int, fullScreen: Boolean = true, - theme: Theme = Theme.Auto -) : BaseActivity(layoutID, fullScreen, theme) { + theme: Theme = Theme.Auto, + toolBarTheme: Theme = Theme.Auto +) : BaseActivity(layoutID, fullScreen, theme, toolBarTheme) { protected abstract val viewModel: VM diff --git a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt index 6a72c911f..48e72de45 100644 --- a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt +++ b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt @@ -10,73 +10,72 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import java.util.* - /** * Created by Invincible on 2017/11/24. * * 通用的adapter 可添加header,footer,以及不同类型item */ -abstract class CommonRecyclerAdapter(protected val context: Context) : +abstract class CommonRecyclerAdapter(protected val context: Context): RecyclerView.Adapter() { - - constructor(context: Context, vararg delegates: ItemViewDelegate) : this(context) { + + constructor(context: Context, vararg delegates: ItemViewDelegate): this(context) { addItemViewDelegates(*delegates) } - + constructor( context: Context, vararg delegates: Pair> - ) : this(context) { + ): this(context) { addItemViewDelegates(*delegates) } - + private val inflater: LayoutInflater = LayoutInflater.from(context) - + private var headerItems: SparseArray? = null private var footerItems: SparseArray? = null - + private val itemDelegates: HashMap> = hashMapOf() private val items: MutableList = mutableListOf() - + private val lock = Object() - + private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null - - private var itemAnimation: ItemAnimation? = null - + + // 这个用Kotlin的setter就行了, 不需要手动开一个函数进行设置 + var itemAnimation: ItemAnimation? = null + fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) { itemClickListener = listener } - + fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) { itemLongClickListener = listener } - + fun bindToRecyclerView(recyclerView: RecyclerView) { recyclerView.adapter = this } - - fun > addItemViewDelegate(viewType: Int, delegate: DELEGATE) { + + fun > addItemViewDelegate(viewType: Int, delegate: DELEGATE) { itemDelegates[viewType] = delegate } - - fun > addItemViewDelegate(delegate: DELEGATE) { + + fun > addItemViewDelegate(delegate: DELEGATE) { itemDelegates[itemDelegates.size] = delegate } - - fun > addItemViewDelegates(vararg delegates: DELEGATE) { + + fun > addItemViewDelegates(vararg delegates: DELEGATE) { delegates.forEach { addItemViewDelegate(it) } } - - fun addItemViewDelegates(vararg delegates: Pair>) { + + fun addItemViewDelegates(vararg delegates: Pair>) = delegates.forEach { addItemViewDelegate(it.first, it.second) } - } - + fun addHeaderView(header: View) { synchronized(lock) { if (headerItems == null) { @@ -89,8 +88,8 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - - fun addFooterView(footer: View) { + + fun addFooterView(footer: View) = synchronized(lock) { if (footerItems == null) { footerItems = SparseArray() @@ -101,9 +100,9 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : notifyItemInserted(index) } } - } - - fun removeHeaderView(header: View) { + + + fun removeHeaderView(header: View) = synchronized(lock) { headerItems?.let { val index = it.indexOfValue(header) @@ -113,9 +112,8 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - } - - fun removeFooterView(footer: View) { + + fun removeFooterView(footer: View) = synchronized(lock) { footerItems?.let { val index = it.indexOfValue(footer) @@ -125,8 +123,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - } - + fun setItems(items: List?) { synchronized(lock) { if (this.items.isNotEmpty()) { @@ -138,7 +135,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : notifyDataSetChanged() } } - + fun setItems(items: List?, diffResult: DiffUtil.DiffResult) { synchronized(lock) { if (this.items.isNotEmpty()) { @@ -150,7 +147,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : diffResult.dispatchUpdatesTo(this) } } - + fun setItem(position: Int, item: ITEM) { synchronized(lock) { val oldSize = getActualItemCount() @@ -160,7 +157,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun addItem(item: ITEM) { synchronized(lock) { val oldSize = getActualItemCount() @@ -169,7 +166,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun addItems(position: Int, newItems: List) { synchronized(lock) { if (this.items.addAll(position, newItems)) { @@ -177,7 +174,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun addItems(newItems: List) { synchronized(lock) { val oldSize = getActualItemCount() @@ -190,7 +187,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun removeItem(position: Int) { synchronized(lock) { if (this.items.removeAt(position) != null) { @@ -198,7 +195,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun removeItem(item: ITEM) { synchronized(lock) { if (this.items.remove(item)) { @@ -206,7 +203,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun removeItems(items: List) { synchronized(lock) { if (this.items.removeAll(items)) { @@ -214,7 +211,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + fun swapItem(oldPosition: Int, newPosition: Int) { synchronized(lock) { val size = getActualItemCount() @@ -227,8 +224,8 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - - fun updateItem(item: ITEM) { + + fun updateItem(item: ITEM) = synchronized(lock) { val index = this.items.indexOf(item) if (index >= 0) { @@ -236,18 +233,16 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : notifyItemChanged(index) } } - } - - fun updateItem(position: Int, payload: Any) { + + fun updateItem(position: Int, payload: Any) = synchronized(lock) { val size = getActualItemCount() if (position in 0 until size) { notifyItemChanged(position + getHeaderCount(), payload) } } - } - - fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) { + + fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) = synchronized(lock) { val size = getActualItemCount() if (fromPosition in 0 until size && toPosition in 0 until size) { @@ -258,119 +253,94 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : ) } } - } - - fun clearItems() { + + fun clearItems() = synchronized(lock) { this.items.clear() notifyDataSetChanged() } - } - - fun isEmpty(): Boolean { - return items.isEmpty() - } - - fun isNotEmpty(): Boolean { - return items.isNotEmpty() - } - + + fun isEmpty() = items.isEmpty() + + fun isNotEmpty() = items.isNotEmpty() + /** * 除去header和footer */ - fun getActualItemCount(): Int { - return items.size - } - - fun getHeaderCount(): Int { - return headerItems?.size() ?: 0 - } - - fun getFooterCount(): Int { - return footerItems?.size() ?: 0 - } - + fun getActualItemCount() = items.size + + + fun getHeaderCount() = headerItems?.size() ?: 0 + + + fun getFooterCount() = footerItems?.size() ?: 0 + fun getItem(position: Int): ITEM? = items.getOrNull(position) - - fun getItemByLayoutPosition(position: Int): ITEM? { - val pos = position - getHeaderCount() - return items.getOrNull(pos) - } - + + fun getItemByLayoutPosition(position: Int) = items.getOrNull(position - getHeaderCount()) + fun getItems(): List = items - - protected open fun getItemViewType(item: ITEM, position: Int): Int { - return 0 - } - + + protected open fun getItemViewType(item: ITEM, position: Int) = 0 + /** * grid 模式下使用 */ - protected open fun getSpanSize(item: ITEM, viewType: Int, position: Int): Int { - return 1 - } - - final override fun getItemCount(): Int { - return getActualItemCount() + getHeaderCount() + getFooterCount() - } - - final override fun getItemViewType(position: Int): Int { - return when { - isHeader(position) -> TYPE_HEADER_VIEW + position - isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount() - else -> getItem(getActualPosition(position))?.let { - getItemViewType(it, getActualPosition(position)) - } ?: 0 + protected open fun getSpanSize(item: ITEM, viewType: Int, position: Int) = 1 + + final override fun getItemCount() = getActualItemCount() + getHeaderCount() + getFooterCount() + + final override fun getItemViewType(position: Int) = when { + isHeader(position) -> TYPE_HEADER_VIEW + position + isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount() + else -> getItem(getActualPosition(position))?.let { + getItemViewType(it, getActualPosition(position)) + } ?: 0 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when { + viewType < TYPE_HEADER_VIEW + getHeaderCount() -> { + ItemViewHolder(headerItems!!.get(viewType)) } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { - return when { - viewType < TYPE_HEADER_VIEW + getHeaderCount() -> { - ItemViewHolder(headerItems!!.get(viewType)) - } - - viewType >= TYPE_FOOTER_VIEW -> { - ItemViewHolder(footerItems!!.get(viewType)) - } - - else -> { - val holder = ItemViewHolder( - inflater.inflate( - itemDelegates.getValue(viewType).layoutId, - parent, - false - ) + + viewType >= TYPE_FOOTER_VIEW -> { + ItemViewHolder(footerItems!!.get(viewType)) + } + + else -> { + val holder = ItemViewHolder( + inflater.inflate( + itemDelegates.getValue(viewType).layoutId, + parent, + false ) - - itemDelegates.getValue(viewType) - .registerListener(holder) - - if (itemClickListener != null) { - holder.itemView.setOnClickListener { - getItem(holder.layoutPosition)?.let { - itemClickListener?.invoke(holder, it) - } + ) + + itemDelegates.getValue(viewType) + .registerListener(holder) + + if (itemClickListener != null) { + holder.itemView.setOnClickListener { + getItem(holder.layoutPosition)?.let { + itemClickListener?.invoke(holder, it) } } - - if (itemLongClickListener != null) { - holder.itemView.setOnLongClickListener { - getItem(holder.layoutPosition)?.let { - itemLongClickListener?.invoke(holder, it) ?: true - } ?: true - } + } + + if (itemLongClickListener != null) { + holder.itemView.setOnLongClickListener { + getItem(holder.layoutPosition)?.let { + itemLongClickListener?.invoke(holder, it) ?: true + } ?: true } - - holder } + + holder } } - - - final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { - } - + + final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {} + final override fun onBindViewHolder( holder: ItemViewHolder, position: Int, @@ -383,19 +353,19 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + override fun onViewAttachedToWindow(holder: ItemViewHolder) { super.onViewAttachedToWindow(holder) if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) { addAnimation(holder) } } - + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) val manager = recyclerView.layoutManager if (manager is GridLayoutManager) { - manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + manager.spanSizeLookup = object: GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { return getItem(position)?.let { if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize( @@ -406,23 +376,13 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - - fun setItemAnimation(item: ItemAnimation) { - itemAnimation = item - } - - private fun isHeader(position: Int): Boolean { - return position < getHeaderCount() - } - - private fun isFooter(position: Int): Boolean { - return position >= getActualItemCount() + getHeaderCount() - } - - private fun getActualPosition(position: Int): Int { - return position - getHeaderCount() - } - + + private fun isHeader(position: Int) = position < getHeaderCount() + + private fun isFooter(position: Int) = position >= getActualItemCount() + getHeaderCount() + + private fun getActualPosition(position: Int) = position - getHeaderCount() + private fun addAnimation(holder: ItemViewHolder) { itemAnimation?.let { if (it.itemAnimEnabled) { @@ -433,8 +393,7 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - - + protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) { item.itemAnimation?.let { for (anim in it.getAnimators(holder.itemView)) { @@ -443,12 +402,12 @@ abstract class CommonRecyclerAdapter(protected val context: Context) : } } } - + companion object { private const val TYPE_HEADER_VIEW = Int.MIN_VALUE private const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999 } - + } diff --git a/app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt b/app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt index a7cc3cdb3..e2c37d708 100644 --- a/app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt +++ b/app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt @@ -16,22 +16,19 @@ class ItemAnimation private constructor() { var itemAnimDuration: Long = 300L var itemAnimStartPosition: Int = -1 - fun interpolator(interpolator: Interpolator): ItemAnimation { + fun interpolator(interpolator: Interpolator) = apply { itemAnimInterpolator = interpolator - return this } - fun duration(duration: Long): ItemAnimation { + fun duration(duration: Long) = apply { itemAnimDuration = duration - return this } - fun startPostion(startPos: Int): ItemAnimation { + fun startPosition(startPos: Int) = apply { itemAnimStartPosition = startPos - return this } - fun animation(animationType: Int = NONE, animation: BaseAnimation? = null): ItemAnimation { + fun animation(animationType: Int = NONE, animation: BaseAnimation? = null) = apply { if (animation != null) { itemAnimation = animation } else { @@ -43,17 +40,14 @@ class ItemAnimation private constructor() { RIGHT_SLIDE_IN -> itemAnimation = SlideInRightAnimation() } } - return this } - fun enabled(enabled: Boolean): ItemAnimation { + fun enabled(enabled: Boolean) = apply { itemAnimEnabled = enabled - return this } - fun firstOnly(firstOnly: Boolean): ItemAnimation { + fun firstOnly(firstOnly: Boolean) = apply { itemAnimFirstOnly = firstOnly - return this } companion object { @@ -79,8 +73,7 @@ class ItemAnimation private constructor() { */ const val RIGHT_SLIDE_IN: Int = 0x00000005 - fun create(): ItemAnimation { - return ItemAnimation() - } + fun create() = ItemAnimation() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 810e6deb2..d6bb81302 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,17 +41,35 @@ 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" ) } val bookGroupAll = BookGroup(-1, App.INSTANCE.getString(R.string.all)) val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio)) + val bookGroupNone = BookGroup(-4, App.INSTANCE.getString(R.string.no_group)) const val notificationIdRead = 1144771 const val notificationIdAudio = 1144772 const val notificationIdWeb = 1144773 const val notificationIdDownload = 1144774 + + val urlOption: String by lazy { + """ + ,{ + "charset": "", + "method": "POST", + "body": "", + "headers": {"User-Agent": ""} + } + """.trimIndent() + } + + val menuViewNames = arrayOf( + "com.android.internal.view.menu.ListMenuItemView", + "androidx.appcompat.view.menu.ListMenuItemView" + ) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/constant/AppPattern.kt b/app/src/main/java/io/legado/app/constant/AppPattern.kt index 396edb73d..19fa46a41 100644 --- a/app/src/main/java/io/legado/app/constant/AppPattern.kt +++ b/app/src/main/java/io/legado/app/constant/AppPattern.kt @@ -3,8 +3,14 @@ package io.legado.app.constant import java.util.regex.Pattern object AppPattern { - val JS_PATTERN: Pattern = Pattern.compile("([\\w\\W]*?|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) + val JS_PATTERN: Pattern = + Pattern.compile("([\\w\\W]*?|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}") + val imgPattern: Pattern = + Pattern.compile("", Pattern.CASE_INSENSITIVE) - val authorRegex = "作\\s*者\\s*[::]".toRegex() + val nameRegex = Regex("\\s+作\\s*者.*") + val authorRegex = Regex(".*?作\\s*?者[::]") + val fileNameRegex = Regex("[\\\\/:*?\"<>|.]") + val splitGroupRegex = Regex("[,;,;]") } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index e24ba8000..6fe8b5cea 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -3,7 +3,7 @@ package io.legado.app.constant object EventBus { const val MEDIA_BUTTON = "mediaButton" const val RECREATE = "RECREATE" - const val UP_BOOK = "sourceDebugLog" + const val UP_BOOK = "upBookToc" const val ALOUD_STATE = "aloud_state" const val TTS_PROGRESS = "ttsStart" const val TTS_DS = "ttsDs" diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index e14ec42f6..c6e222849 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -2,16 +2,16 @@ package io.legado.app.constant object PreferKey { const val versionCode = "versionCode" + const val language = "language" const val themeMode = "themeMode" const val hideStatusBar = "hideStatusBar" const val clickTurnPage = "clickTurnPage" const val clickAllNext = "clickAllNext" const val hideNavigationBar = "hideNavigationBar" const val precisionSearch = "precisionSearch" - const val readAloudOnLine = "readAloudOnLine" + const val speakEngine = "speakEngine" const val readAloudByPage = "readAloudByPage" const val ttsSpeechRate = "ttsSpeechRate" - const val ttsSpeechPer = "ttsSpeechPer" const val prevKey = "prevKeyCode" const val nextKey = "nextKeyCode" const val showRss = "showRss" @@ -22,9 +22,9 @@ object PreferKey { const val cleanCache = "cleanCache" const val saveTabPosition = "saveTabPosition" const val pageAnim = "pageAnim" - const val readBookFont = "readBookFont" const val fontFolder = "fontFolder" const val backupPath = "backupUri" + const val restoreIgnore = "restoreIgnore" const val threadCount = "threadCount" const val webPort = "webPort" const val keepLight = "keep_light" @@ -42,4 +42,24 @@ object PreferKey { const val shareLayout = "shareLayout" const val readStyleSelect = "readStyleSelect" const val systemTypefaces = "system_typefaces" + const val readBodyToLh = "readBodyToLh" + const val textFullJustify = "textFullJustify" + const val textBottomJustify = "textBottomJustify" + const val autoReadSpeed = "autoReadSpeed" + const val barElevation = "barElevation" + const val transparentStatusBar = "transparentStatusBar" + const val defaultCover = "defaultCover" + const val replaceEnableDefault = "replaceEnableDefault" + const val showBrightnessView = "showBrightnessView" + + 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/constant/Theme.kt b/app/src/main/java/io/legado/app/constant/Theme.kt index b8587997b..896e73ea0 100644 --- a/app/src/main/java/io/legado/app/constant/Theme.kt +++ b/app/src/main/java/io/legado/app/constant/Theme.kt @@ -1,15 +1,19 @@ package io.legado.app.constant import io.legado.app.help.AppConfig +import io.legado.app.utils.ColorUtils enum class Theme { - Dark, Light, Auto; + Dark, Light, Auto, Transparent; companion object { - fun getTheme(): Theme { - return if (AppConfig.isNightTheme) { - Dark - } else Light - } + fun getTheme() = + if (AppConfig.isNightTheme) Dark + else Light + + fun getTheme(backgroundColor: Int) = + if (ColorUtils.isColorLight(backgroundColor)) Light + else Dark + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index f32d6a199..a865f8d86 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -4,41 +4,121 @@ 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.App import io.legado.app.data.dao.* import io.legado.app.data.entities.* -import io.legado.app.help.storage.Backup -import io.legado.app.help.storage.Restore -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch @Database( entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, - RssStar::class, TxtTocRule::class], - version = 8, + RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class], + version = 20, exportSchema = true ) -abstract class AppDatabase : RoomDatabase() { - +abstract class AppDatabase: RoomDatabase() { + companion object { - + private const val DATABASE_NAME = "legado.db" - - fun createDatabase(context: Context): AppDatabase { - return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) + + fun createDatabase(context: Context) = + Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) .fallbackToDestructiveMigration() - .addCallback(object : Callback() { - override fun onDestructiveMigration(db: SupportSQLiteDatabase) { - GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) } - } - }) + .addMigrations( + migration_10_11, + migration_11_12, + migration_12_13, + migration_13_14, + migration_14_15, + migration_15_17, + migration_17_18, + migration_18_19, + migration_19_20 + ) + .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 ") + } + } + + private val migration_13_14 = object: Migration(13, 14) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, + `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, + `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, + `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, + `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, + `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`)) + """ + ) + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books_new` (`name`, `author`) ") + database.execSQL("INSERT INTO books_new select * from books ") + database.execSQL("DROP TABLE books") + database.execSQL("ALTER TABLE books_new RENAME TO books") + } + } + + private val migration_14_15 = object: Migration(14, 15) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE bookmarks ADD bookAuthor TEXT NOT NULL DEFAULT ''") + } + } + + private val migration_15_17 = object: Migration(15, 17) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))") + } + } + + private val migration_17_18 = object: Migration(17, 18) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))") + } + } + + private val migration_18_19 = object: Migration(18, 19) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))") + database.execSQL("INSERT INTO readRecordNew(androidId, bookName, readTime) select '${App.androidId}' as androidId, bookName, readTime from readRecord") + database.execSQL("DROP TABLE readRecord") + database.execSQL("ALTER TABLE readRecordNew RENAME TO readRecord") + } + } + private val migration_19_20 = object: Migration(19, 20) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE book_sources ADD bookSourceComment TEXT") + } } } - + abstract fun bookDao(): BookDao abstract fun bookGroupDao(): BookGroupDao abstract fun bookSourceDao(): BookSourceDao @@ -52,4 +132,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun rssStarDao(): RssStarDao abstract fun cookieDao(): CookieDao abstract fun txtTocRule(): TxtTocRuleDao + abstract fun readRecordDao(): ReadRecordDao + abstract fun httpTTSDao(): HttpTTSDao } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt index 38cc452f2..0041591e3 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt @@ -10,21 +10,24 @@ import io.legado.app.data.entities.BookChapter @Dao interface BookChapterDao { - @Query("select * from chapters where bookUrl = :bookUrl") + @Query("select * from chapters where bookUrl = :bookUrl order by `index`") fun observeByBook(bookUrl: String): LiveData> - @Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%'") + @Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%' order by `index`") fun liveDataSearch(bookUrl: String, key: String): LiveData> - @Query("select * from chapters where bookUrl = :bookUrl") + @Query("select * from chapters where bookUrl = :bookUrl order by `index`") fun getChapterList(bookUrl: String): List - @Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end") + @Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end order by `index`") fun getChapterList(bookUrl: String, start: Int, end: Int): List @Query("select * from chapters where bookUrl = :bookUrl and `index` = :index") fun getChapter(bookUrl: String, index: Int): BookChapter? + @Query("select * from chapters where bookUrl = :bookUrl and `title` = :title") + fun getChapter(bookUrl: String, title: String): BookChapter? + @Query("select count(url) from chapters where bookUrl = :bookUrl") fun getChapterCount(bookUrl: String): Int diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index d8d960df3..9aaf5d3fe 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,15 +21,15 @@ 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> @Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") fun observeNoGroup(): LiveData> + @Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") + fun observeNoGroupSize(): LiveData + @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") fun liveDataSearch(key: String): LiveData> @@ -42,6 +42,12 @@ interface BookDao { @Query("SELECT * FROM books WHERE bookUrl = :bookUrl") fun getBook(bookUrl: String): Book? + @Query("SELECT * FROM books WHERE name = :name and author = :author") + fun getBook(name: String, author: String): Book? + + @get:Query("select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") + val noGroupSize: Int + @get:Query("SELECT * FROM books where origin <> '${BookType.local}' and type = 0") val webBooks: List @@ -78,7 +84,7 @@ interface BookDao { @Query("update books set `group` = :newGroupId where `group` = :oldGroupId") fun upGroup(oldGroupId: Int, newGroupId: Int) - @get:Query("select bookUrl, durChapterIndex, durChapterPos, durChapterTime, durChapterTitle from books") + @get:Query("select bookUrl, tocUrl, origin, originName, durChapterIndex, durChapterPos, durChapterTime, durChapterTitle from books") val allBookProgress: List @Query( 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..e7ffc7fe0 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 @@ -11,9 +11,15 @@ interface BookSourceDao { @Query("select * from book_sources order by customOrder asc") fun liveDataAll(): LiveData> - @Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey order by customOrder asc") + @Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey or bookSourceComment 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/BookmarkDao.kt b/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt index 568714e2f..16fdff455 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt @@ -1,21 +1,22 @@ package io.legado.app.data.dao import androidx.paging.DataSource -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room.* import io.legado.app.data.entities.Bookmark @Dao interface BookmarkDao { - @Query("select * from bookmarks") - fun all(): List + @get:Query("select * from bookmarks") + val all: List - @Query("select * from bookmarks where bookUrl = :bookUrl") - fun observeByBook(bookUrl: String): DataSource.Factory + @Query("select * from bookmarks where bookUrl = :bookUrl or (bookName = :bookName and bookAuthor = :bookAuthor)") + fun observeByBook( + bookUrl: String, + bookName: String, + bookAuthor: String + ): DataSource.Factory @Query("SELECT * FROM bookmarks where bookUrl = :bookUrl and chapterName like '%'||:key||'%' or content like '%'||:key||'%'") fun liveDataSearch(bookUrl: String, key: String): DataSource.Factory @@ -23,6 +24,12 @@ interface BookmarkDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg bookmark: Bookmark) + @Update + fun update(bookmark: Bookmark) + + @Delete + fun delete(vararg bookmark: Bookmark) + @Query("delete from bookmarks where bookUrl = :bookUrl and chapterName like '%'||:chapterName||'%'") fun delByBookmark(bookUrl: String, chapterName: String) diff --git a/app/src/main/java/io/legado/app/data/dao/CookieDao.kt b/app/src/main/java/io/legado/app/data/dao/CookieDao.kt index 1ec8c99e3..f667b7d2d 100644 --- a/app/src/main/java/io/legado/app/data/dao/CookieDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/CookieDao.kt @@ -9,6 +9,9 @@ interface CookieDao { @Query("SELECT * FROM cookies Where url = :url") fun get(url: String): Cookie? + @Query("select * from cookies where url like '%|%'") + fun getOkHttpCookies(): List + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg cookie: Cookie) @@ -17,4 +20,7 @@ interface CookieDao { @Query("delete from cookies where url = :url") fun delete(url: String) + + @Query("delete from cookies where url like '%|%'") + fun deleteOkHttp() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt b/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt new file mode 100644 index 000000000..f71169bb1 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt @@ -0,0 +1,31 @@ +package io.legado.app.data.dao + +import androidx.lifecycle.LiveData +import androidx.room.* +import io.legado.app.data.entities.HttpTTS + +@Dao +interface HttpTTSDao { + + @get:Query("select * from httpTTS order by name") + val all: List + + @Query("select * from httpTTS order by name") + fun observeAll(): LiveData> + + @get:Query("select count(*) from httpTTS") + val count: Int + + @Query("select * from httpTTS where id = :id") + fun get(id: Long): HttpTTS? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg httpTTS: HttpTTS) + + @Delete + fun delete(vararg httpTTS: HttpTTS) + + @Update + fun update(vararg httpTTS: HttpTTS) + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt b/app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt new file mode 100644 index 000000000..3e964ea73 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt @@ -0,0 +1,39 @@ +package io.legado.app.data.dao + +import androidx.room.* +import io.legado.app.data.entities.ReadRecord +import io.legado.app.data.entities.ReadRecordShow + +@Dao +interface ReadRecordDao { + + @get:Query("select * from readRecord") + val all: List + + @get:Query("select bookName, sum(readTime) as readTime from readRecord group by bookName order by bookName") + val allShow: List + + @get:Query("select sum(readTime) from readRecord") + val allTime: Long + + @Query("select sum(readTime) from readRecord where bookName = :bookName") + fun getReadTime(bookName: String): Long? + + @Query("select readTime from readRecord where androidId = :androidId and bookName = :bookName") + fun getReadTime(androidId: String, bookName: String): Long? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg readRecord: ReadRecord) + + @Update + fun update(vararg record: ReadRecord) + + @Delete + fun delete(vararg record: ReadRecord) + + @Query("delete from readRecord") + fun clear() + + @Query("delete from readRecord where bookName = :bookName") + fun deleteByName(bookName: String) +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt index fbf3c3611..10bf63933 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt @@ -12,17 +12,18 @@ interface RssArticleDao { fun get(origin: String, link: String): RssArticle? @Query( - """select t1.link, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read + """select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read from rssArticles as t1 left join rssReadRecords as t2 - on t1.link = t2.record where origin = :origin order by `order` desc""" + on t1.link = t2.record where origin = :origin and sort = :sort + order by `order` desc""" ) - fun liveByOrigin(origin: String): LiveData> + fun liveByOriginSort(origin: String, sort: String): LiveData> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg rssArticle: RssArticle) - @Query("delete from rssArticles where origin = :origin and `order` < :order") - fun clearOld(origin: String, order: Long) + @Query("delete from rssArticles where origin = :origin and sort = :sort and `order` < :order") + fun clearOld(origin: String, sort: String, order: Long) @Update fun update(vararg rssArticle: RssArticle) diff --git a/app/src/main/java/io/legado/app/data/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..e52f9e185 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 @@ -3,8 +3,10 @@ package io.legado.app.data.entities import io.legado.app.utils.splitNotBlank interface BaseBook { + var name: String + var author: String 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 0dd5cab9e..62926b3b4 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 @@ -5,9 +5,13 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index import androidx.room.PrimaryKey +import io.legado.app.App import io.legado.app.constant.AppPattern import io.legado.app.constant.BookType +import io.legado.app.help.AppConfig +import io.legado.app.service.help.ReadBook import io.legado.app.utils.GSON +import io.legado.app.utils.MD5Utils import io.legado.app.utils.fromJsonObject import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.Parcelize @@ -15,15 +19,18 @@ import java.nio.charset.Charset import kotlin.math.max @Parcelize -@Entity(tableName = "books", indices = [(Index(value = ["bookUrl"], unique = true))]) +@Entity( + tableName = "books", + indices = [Index(value = ["name", "author"], unique = true)] +) data class Book( @PrimaryKey - override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) + override var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) var tocUrl: String = "", // 目录页Url (toc=table of Contents) var origin: String = BookType.local, // 书源URL(默认BookType.local) var originName: String = "", //书源名称 or 本地书籍文件名 - var name: String = "", // 书籍名称(书源获取) - var author: String = "", // 作者名称(书源获取) + override var name: String = "", // 书籍名称(书源获取) + override var author: String = "", // 作者名称(书源获取) override var kind: String? = null, // 分类信息(书源获取) var customTag: String? = null, // 分类信息(用户修改) var coverUrl: String? = null, // 封面Url(书源获取) @@ -46,83 +53,124 @@ data class Book( var canUpdate: Boolean = true, // 刷新书架时更新书籍信息 var order: Int = 0, // 手动排序 var originOrder: Int = 0, //书源排序 - var useReplaceRule: Boolean = true, // 正文使用净化替换规则 + var useReplaceRule: Boolean = AppConfig.replaceEnableDefault, // 正文使用净化替换规则 var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息) -) : Parcelable, BaseBook { - +): Parcelable, BaseBook { + fun isLocalBook(): Boolean { return origin == BookType.local } - - fun isTxt(): Boolean { + + fun isLocalTxt(): Boolean { return isLocalBook() && originName.endsWith(".txt", true) } - + + fun isEpub(): Boolean { + return originName.endsWith(".epub", true) + } + + fun isOnLineTxt(): Boolean { + return !isLocalBook() && type == 0 + } + override fun equals(other: Any?): Boolean { if (other is Book) { return other.bookUrl == bookUrl } return false } - + override fun hashCode(): Int { 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 override var infoHtml: String? = null - + @Ignore @IgnoredOnParcel override var tocHtml: String? = null - + fun getRealAuthor() = author.replace(AppPattern.authorRegex, "") - + fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) - + fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl - + 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") } + + fun getFolderName(): String { + return name.replace(AppPattern.fileNameRegex, "") + MD5Utils.md5Encode16(bookUrl) + } + + fun toSearchBook() = SearchBook( + name = name, + author = author, + kind = kind, + bookUrl = bookUrl, + origin = origin, + originName = originName, + type = type, + wordCount = wordCount, + latestChapterTitle = latestChapterTitle, + coverUrl = coverUrl, + intro = intro, + tocUrl = tocUrl, + originOrder = originOrder, + variable = variable + ).apply { + this.infoHtml = this@Book.infoHtml + this.tocHtml = this@Book.tocHtml + } + + fun changeTo(newBook: Book) { + newBook.group = group + newBook.order = order + newBook.customCoverUrl = customCoverUrl + newBook.customIntro = customIntro + newBook.customTag = customTag + newBook.canUpdate = canUpdate + newBook.useReplaceRule = useReplaceRule + delete() + App.db.bookDao().insert(newBook) + } - fun toSearchBook(): SearchBook { - return SearchBook( - name = name, - author = author, - kind = kind, - bookUrl = bookUrl, - origin = origin, - originName = originName, - type = type, - wordCount = wordCount, - latestChapterTitle = latestChapterTitle, - coverUrl = coverUrl, - intro = intro, - tocUrl = tocUrl, - originOrder = originOrder, - variable = variable - ).apply { - this.infoHtml = this@Book.infoHtml - this.tocHtml = this@Book.tocHtml + fun delete() { + if (ReadBook.book?.bookUrl == bookUrl) { + ReadBook.book = null + } + App.db.bookDao().delete(this) + } + + fun upInfoFromOld(oldBook: Book?) { + oldBook?.let { + group = oldBook.group + durChapterIndex = oldBook.durChapterIndex + durChapterPos = oldBook.durChapterPos + durChapterTitle = oldBook.durChapterTitle + customCoverUrl = oldBook.customCoverUrl + customIntro = oldBook.customIntro + order = oldBook.order + if (coverUrl.isNullOrEmpty()) { + coverUrl = oldBook.getDisplayCover() + } } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt index f7a008870..607704e6a 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt @@ -52,16 +52,9 @@ data class BookChapter( variable = GSON.toJson(variableMap) } - override fun hashCode(): Int { - return url.hashCode() - } + override fun hashCode() = url.hashCode() - override fun equals(other: Any?): Boolean { - if (other is BookChapter) { - return other.url == url - } - return false - } + override fun equals(other: Any?) = if (other is BookChapter) other.url == url else false } diff --git a/app/src/main/java/io/legado/app/data/entities/BookProgress.kt b/app/src/main/java/io/legado/app/data/entities/BookProgress.kt index c0d693284..bb9d4e0c5 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookProgress.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookProgress.kt @@ -2,6 +2,9 @@ package io.legado.app.data.entities data class BookProgress( val bookUrl: String, + val tocUrl: String = "", + var origin: String = "", + var originName: String = "", val durChapterIndex: Int, val durChapterPos: Int, val durChapterTime: Long, diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index 78852a4ec..29ffdcfd1 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,20 @@ 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))] @@ -37,119 +31,70 @@ data class BookSource( var enabledExplore: Boolean = true, // 启用发现 var header: String? = null, // 请求头 var loginUrl: String? = null, // 登录地址 + var bookSourceComment: String? = null, // 注释 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 // 正文页规则 -) : Parcelable, JsExtensions { - + var ruleSearch: SearchRule? = null, // 搜索规则 + var ruleBookInfo: BookInfoRule? = null, // 书籍信息页规则 + var ruleToc: TocRule? = null, // 目录页规则 + var ruleContent: ContentRule? = null // 正文页规则 +): Parcelable, JsExtensions { + override fun hashCode(): Int { return bookSourceUrl.hashCode() } - - override fun equals(other: Any?): Boolean { - if (other is BookSource) { - return other.bookSourceUrl == bookSourceUrl - } - 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 - + + override fun equals(other: Any?) = if (other is BookSource) other.bookSourceUrl == bookSourceUrl else false + @Throws(Exception::class) - fun getHeaderMap(): Map { - val headerMap = HashMap() - headerMap[AppConst.UA_NAME] = App.INSTANCE.getPrefString("user_agent") ?: userAgent + fun getHeaderMap() = (HashMap().apply { + this[AppConst.UA_NAME] = App.INSTANCE.getPrefString("user_agent") ?: userAgent header?.let { - val header1 = when { - it.startsWith("@js:", true) -> - evalJS(it.substring(4)).toString() - it.startsWith("", true) -> - evalJS(it.substring(4, it.lastIndexOf("<"))).toString() - else -> it - } - GSON.fromJsonObject>(header1)?.let { map -> - headerMap.putAll(map) + GSON.fromJsonObject>( + when { + it.startsWith("@js:", true) -> + evalJS(it.substring(4)).toString() + it.startsWith("", true) -> + evalJS(it.substring(4, it.lastIndexOf("<"))).toString() + else -> it + } + )?.let { map -> + putAll(map) } } - return headerMap - } - - fun getSearchRule(): SearchRule { - searchRuleV ?: let { - searchRuleV = GSON.fromJsonObject(ruleSearch) - searchRuleV ?: let { searchRuleV = SearchRule() } - } - return searchRuleV!! - } - - fun getExploreRule(): ExploreRule { - exploreRuleV ?: let { - exploreRuleV = GSON.fromJsonObject(ruleExplore) - exploreRuleV ?: let { exploreRuleV = ExploreRule() } - } - return exploreRuleV!! - } - - fun getBookInfoRule(): BookInfoRule { - bookInfoRuleV ?: let { - bookInfoRuleV = GSON.fromJsonObject(ruleBookInfo) - bookInfoRuleV ?: let { bookInfoRuleV = BookInfoRule() } - } - return bookInfoRuleV!! - } - - fun getTocRule(): TocRule { - tocRuleV ?: let { - tocRuleV = GSON.fromJsonObject(ruleToc) - tocRuleV ?: let { tocRuleV = TocRule() } - } - return tocRuleV!! - } - - fun getContentRule(): ContentRule { - contentRuleV ?: let { - contentRuleV = GSON.fromJsonObject(ruleContent) - contentRuleV ?: let { contentRuleV = ContentRule() } - } - return contentRuleV!! - } - + }) as Map + + fun getSearchRule() = ruleSearch ?: SearchRule() + + fun getExploreRule() = ruleExplore ?: ExploreRule() + + fun getBookInfoRule() = ruleBookInfo ?: BookInfoRule() + + fun getTocRule() = ruleToc ?: TocRule() + + fun getContentRule() = ruleContent ?: ContentRule() + fun addGroup(group: String) { bookSourceGroup?.let { if (!it.contains(group)) { - bookSourceGroup = "$it;$group" + bookSourceGroup = "$it,$group" } } ?: let { bookSourceGroup = group } } - - fun getExploreKinds(): ArrayList? { - val exploreKinds = arrayListOf() + + fun removeGroup(group: String) { + bookSourceGroup?.splitNotBlank("[,;,;]".toRegex())?.toHashSet()?.let { + it.remove(group) + bookSourceGroup = TextUtils.join(",", it) + } + } + + fun getExploreKinds() = arrayListOf().apply { exploreUrl?.let { var a = it if (a.isNotBlank()) { @@ -169,19 +114,18 @@ data class BookSource( } } val b = a.split("(&&|\n)+".toRegex()) - b.map { c -> + b.forEach { c -> val d = c.split("::") if (d.size > 1) - exploreKinds.add(ExploreKind(d[0], d[1])) + add(ExploreKind(d[0], d[1])) } } catch (e: Exception) { - exploreKinds.add(ExploreKind(e.localizedMessage ?: "")) + add(ExploreKind(e.localizedMessage ?: "")) } } } - return exploreKinds } - + /** * 执行JS */ @@ -192,31 +136,62 @@ data class BookSource( return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings) } - fun equal(source: BookSource): Boolean { - return equal(bookSourceName, source.bookSourceName) - && equal(bookSourceUrl, source.bookSourceUrl) - && equal(bookSourceGroup, source.bookSourceGroup) - && bookSourceType == source.bookSourceType - && equal(bookUrlPattern, source.bookUrlPattern) - && enabled == source.enabled - && enabledExplore == source.enabledExplore - && equal(header, source.header) - && equal(loginUrl, source.loginUrl) - && equal(exploreUrl, source.exploreUrl) - && equal(searchUrl, source.searchUrl) - && getSearchRule() == source.getSearchRule() - && getExploreRule() == source.getExploreRule() - && getBookInfoRule() == source.getBookInfoRule() - && getTocRule() == source.getTocRule() - && getContentRule() == source.getContentRule() - } - - private fun equal(a: String?, b: String?): Boolean { - return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) - } + fun equal(source: BookSource) = + equal(bookSourceName, source.bookSourceName) + && equal(bookSourceUrl, source.bookSourceUrl) + && equal(bookSourceGroup, source.bookSourceGroup) + && bookSourceType == source.bookSourceType + && equal(bookUrlPattern, source.bookUrlPattern) + && equal(bookSourceComment, source.bookSourceComment) + && enabled == source.enabled + && enabledExplore == source.enabledExplore + && equal(header, source.header) + && equal(loginUrl, source.loginUrl) + && equal(exploreUrl, source.exploreUrl) + && equal(searchUrl, source.searchUrl) + && getSearchRule() == source.getSearchRule() + && getExploreRule() == source.getExploreRule() + && getBookInfoRule() == source.getBookInfoRule() + && getTocRule() == source.getTocRule() + && getContentRule() == source.getContentRule() + + private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) data class ExploreKind( var title: String, var url: String? = null ) + + class Converters { + @TypeConverter + fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule) + + @TypeConverter + fun stringToExploreRule(json: String?) = GSON.fromJsonObject(json) + + @TypeConverter + fun searchRuleToString(searchRule: SearchRule?): String = GSON.toJson(searchRule) + + @TypeConverter + fun stringToSearchRule(json: String?) = GSON.fromJsonObject(json) + + @TypeConverter + fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String = GSON.toJson(bookInfoRule) + + @TypeConverter + fun stringToBookInfoRule(json: String?) = GSON.fromJsonObject(json) + + @TypeConverter + fun tocRuleToString(tocRule: TocRule?): String = GSON.toJson(tocRule) + + @TypeConverter + fun stringToTocRule(json: String?) = GSON.fromJsonObject(json) + + @TypeConverter + fun contentRuleToString(contentRule: ContentRule?): String = GSON.toJson(contentRule) + + @TypeConverter + fun stringToContentRule(json: String?) = GSON.fromJsonObject(json) + + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/Bookmark.kt b/app/src/main/java/io/legado/app/data/entities/Bookmark.kt index acd6005a3..e7222bf33 100644 --- a/app/src/main/java/io/legado/app/data/entities/Bookmark.kt +++ b/app/src/main/java/io/legado/app/data/entities/Bookmark.kt @@ -13,6 +13,7 @@ data class Bookmark( var time: Long = System.currentTimeMillis(), var bookUrl: String = "", var bookName: String = "", + val bookAuthor: String = "", var chapterIndex: Int = 0, var pageIndex: Int = 0, var chapterName: String = "", diff --git a/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt b/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt new file mode 100644 index 000000000..2e5ad7df0 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt @@ -0,0 +1,12 @@ +package io.legado.app.data.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "httpTTS") +data class HttpTTS( + @PrimaryKey + val id: Long = System.currentTimeMillis(), + var name: String = "", + var url: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/ReadRecord.kt b/app/src/main/java/io/legado/app/data/entities/ReadRecord.kt new file mode 100644 index 000000000..60f577477 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/ReadRecord.kt @@ -0,0 +1,10 @@ +package io.legado.app.data.entities + +import androidx.room.Entity + +@Entity(tableName = "readRecord", primaryKeys = ["androidId", "bookName"]) +data class ReadRecord( + var androidId: String = "", + var bookName: String = "", + var readTime: Long = 0L +) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/ReadRecordShow.kt b/app/src/main/java/io/legado/app/data/entities/ReadRecordShow.kt new file mode 100644 index 000000000..79795b0d9 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/ReadRecordShow.kt @@ -0,0 +1,6 @@ +package io.legado.app.data.entities + +data class ReadRecordShow( + var bookName: String = "", + var readTime: Long = 0L +) \ 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 c74597e74..fda4769d2 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssArticle.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssArticle.kt @@ -9,6 +9,7 @@ import androidx.room.Entity ) data class RssArticle( var origin: String = "", + var sort: String = "", var title: String = "", var order: Long = 0, var link: String = "", @@ -18,31 +19,23 @@ data class RssArticle( var image: String? = null, var read: Boolean = false ) { - - override fun hashCode(): Int { - return super.hashCode() - } - + + override fun hashCode() = link.hashCode() + override fun equals(other: Any?): Boolean { - if (other == null) { - return false - } - if (other is RssArticle) { - return origin == other.origin && link == other.link - } - return false - } - - fun toStar(): RssStar { - return RssStar( - origin = origin, - title = title, - starTime = System.currentTimeMillis(), - link = link, - pubDate = pubDate, - description = description, - content = content, - image = image - ) + other ?: return false + return if (other is RssArticle) origin == other.origin && link == other.link else false } + + fun toStar() = RssStar( + origin = origin, + sort = sort, + title = title, + starTime = System.currentTimeMillis(), + link = link, + pubDate = pubDate, + description = description, + content = content, + image = image + ) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/RssSource.kt b/app/src/main/java/io/legado/app/data/entities/RssSource.kt index 999b12523..300049f82 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssSource.kt @@ -23,6 +23,8 @@ data class RssSource( var sourceIcon: String = "", var sourceGroup: String? = null, var enabled: Boolean = true, + var sortUrl: String? = null, + var articleStyle: Int = 0, //列表规则 var ruleArticles: String? = null, var ruleNextPage: String? = null, @@ -33,70 +35,71 @@ 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 { - - override fun equals(other: Any?): Boolean { - if (other is RssSource) { - return other.sourceUrl == sourceUrl - } - return false - } - - override fun hashCode(): Int { - return sourceUrl.hashCode() - } - +): Parcelable, JsExtensions { + + override fun equals(other: Any?) = if (other is RssSource) other.sourceUrl == sourceUrl else false + + override fun hashCode() = sourceUrl.hashCode() + @Throws(Exception::class) - fun getHeaderMap(): Map { - val headerMap = HashMap() - headerMap[AppConst.UA_NAME] = App.INSTANCE.getPrefString("user_agent") ?: AppConst.userAgent + fun getHeaderMap() = HashMap().apply { + this[AppConst.UA_NAME] = App.INSTANCE.getPrefString("user_agent") ?: AppConst.userAgent header?.let { - val header1 = when { - it.startsWith("@js:", true) -> - evalJS(it.substring(4)).toString() - it.startsWith("", true) -> - evalJS(it.substring(4, it.lastIndexOf("<"))).toString() - else -> it - } - GSON.fromJsonObject>(header1)?.let { map -> - headerMap.putAll(map) + GSON.fromJsonObject>( + when { + it.startsWith("@js:", true) -> + evalJS(it.substring(4)).toString() + it.startsWith("", true) -> + evalJS(it.substring(4, it.lastIndexOf("<"))).toString() + else -> it + } + )?.let { map -> + putAll(map) } } - return headerMap } - + /** * 执行JS */ @Throws(Exception::class) - private fun evalJS(jsStr: String): Any { - val bindings = SimpleBindings() - bindings["java"] = this - return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings) - } - + private fun evalJS(jsStr: String): Any = AppConst.SCRIPT_ENGINE.eval(jsStr, SimpleBindings().apply { this["java"] = this@RssSource }) + fun equal(source: RssSource): Boolean { return equal(sourceUrl, source.sourceUrl) - && equal(sourceIcon, source.sourceIcon) - && enabled == source.enabled - && equal(sourceGroup, source.sourceGroup) - && equal(ruleArticles, source.ruleArticles) - && equal(ruleNextPage, source.ruleNextPage) - && equal(ruleTitle, source.ruleTitle) - && equal(rulePubDate, source.rulePubDate) - && equal(ruleDescription, source.ruleDescription) - && equal(ruleLink, source.ruleLink) - && equal(ruleContent, source.ruleContent) - && enableJs == source.enableJs - && loadWithBaseUrl == source.loadWithBaseUrl + && equal(sourceIcon, source.sourceIcon) + && enabled == source.enabled + && equal(sourceGroup, source.sourceGroup) + && equal(ruleArticles, source.ruleArticles) + && equal(ruleNextPage, source.ruleNextPage) + && equal(ruleTitle, source.ruleTitle) + && equal(rulePubDate, source.rulePubDate) + && equal(ruleDescription, source.ruleDescription) + && equal(ruleLink, source.ruleLink) + && equal(ruleContent, source.ruleContent) + && enableJs == source.enableJs + && loadWithBaseUrl == source.loadWithBaseUrl } - + private fun equal(a: String?, b: String?): Boolean { return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) } - + + fun sortUrls(): LinkedHashMap = + linkedMapOf().apply { + sortUrl?.split("(&&|\n)+".toRegex())?.forEach { c -> + val d = c.split("::") + if (d.size > 1) + this[d[0]] = d[1] + } + if (isEmpty()) { + this[""] = sourceUrl + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/RssStar.kt b/app/src/main/java/io/legado/app/data/entities/RssStar.kt index 1d0c266f8..7e78550bb 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssStar.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssStar.kt @@ -9,6 +9,7 @@ import androidx.room.Entity ) data class RssStar( var origin: String = "", + var sort: String = "", var title: String = "", var starTime: Long = 0, var link: String = "", @@ -17,9 +18,9 @@ data class RssStar( var content: String? = null, var image: String? = null ) { - fun toRssArticle(): RssArticle { - return RssArticle( + fun toRssArticle() = RssArticle( origin = origin, + sort = sort, title = title, link = link, pubDate = pubDate, @@ -27,5 +28,4 @@ data class RssStar( content = content, image = image ) - } } \ No newline at end of file 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..62635219c 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 @@ -25,8 +25,8 @@ data class SearchBook( var origin: String = "", // 书源规则 var originName: String = "", var type: Int = 0, // @BookType - var name: String = "", - var author: String = "", + override var name: String = "", + override var author: String = "", override var kind: String? = null, var coverUrl: String? = null, var intro: String? = null, @@ -36,56 +36,45 @@ data class SearchBook( var time: Long = System.currentTimeMillis(), var variable: String? = null, var originOrder: Int = 0 -) : Parcelable, BaseBook, Comparable { - +): Parcelable, BaseBook, Comparable { + @Ignore @IgnoredOnParcel override var infoHtml: String? = null - + @Ignore @IgnoredOnParcel override var tocHtml: String? = null - - override fun equals(other: Any?): Boolean { - if (other is SearchBook) { - if (other.bookUrl == bookUrl) { - return true - } - } - return false - } - - override fun hashCode(): Int { - return bookUrl.hashCode() - } - + + override fun equals(other: Any?) = other is SearchBook && other.bookUrl == bookUrl + + override fun hashCode() = bookUrl.hashCode() + override fun compareTo(other: SearchBook): Int { 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) } - + fun addOrigin(origin: String) { origins.add(origin) } - + fun getDisplayLastChapterTitle(): String { latestChapterTitle?.let { if (it.isNotEmpty()) { @@ -94,26 +83,24 @@ data class SearchBook( } return "无最新章节" } - - fun toBook(): Book { - return Book( - name = name, - author = author, - kind = kind, - bookUrl = bookUrl, - origin = origin, - originName = originName, - type = type, - wordCount = wordCount, - latestChapterTitle = latestChapterTitle, - coverUrl = coverUrl, - intro = intro, - tocUrl = tocUrl, - originOrder = originOrder, - variable = variable - ).apply { - this.infoHtml = this@SearchBook.infoHtml - this.tocUrl = this@SearchBook.tocUrl - } + + fun toBook() = Book( + name = name, + author = author, + kind = kind, + bookUrl = bookUrl, + origin = origin, + originName = originName, + type = type, + wordCount = wordCount, + latestChapterTitle = latestChapterTitle, + coverUrl = coverUrl, + intro = intro, + tocUrl = tocUrl, + originOrder = originOrder, + variable = variable + ).apply { + this.infoHtml = this@SearchBook.infoHtml + this.tocUrl = this@SearchBook.tocUrl } } \ No newline at end of file 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..882ffbad7 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,9 @@ package io.legado.app.data.entities.rule +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize data class BookInfoRule( var init: String? = null, var name: String? = null, @@ -11,4 +15,4 @@ data class BookInfoRule( var coverUrl: String? = null, var tocUrl: String? = null, var wordCount: String? = null -) \ No newline at end of file +) : Parcelable \ 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..a3c565652 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,14 @@ package io.legado.app.data.entities.rule +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize 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 + var sourceRegex: String? = null, + var replaceRegex: String? = null, + var imageStyle: String? = null //默认大小居中,1最大宽度 +) : Parcelable \ 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..41031e61e 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,9 @@ package io.legado.app.data.entities.rule +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize data class ExploreRule( override var bookList: String? = null, override var name: String? = null, @@ -11,4 +15,4 @@ 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 \ No newline at end of file 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..f5683f05b 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,14 +1,18 @@ package io.legado.app.data.entities.rule +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize data class SearchRule( - override var bookList: String? = null, - override var name: String? = null, - override var author: String? = null, - override var intro: String? = null, - override var kind: String? = null, - override var lastChapter: String? = null, - override var updateTime: String? = null, - override var bookUrl: String? = null, - override var coverUrl: String? = null, - override var wordCount: String? = null -) : BookListRule \ No newline at end of file + override var bookList: String? = null, + override var name: String? = null, + override var author: String? = null, + override var intro: String? = null, + override var kind: String? = null, + override var lastChapter: String? = null, + override var updateTime: String? = null, + override var bookUrl: String? = null, + override var coverUrl: String? = null, + override var wordCount: String? = null +) : BookListRule, Parcelable \ No newline at end of file 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..d040f3905 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,9 @@ package io.legado.app.data.entities.rule +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize data class TocRule( var chapterList: String? = null, var chapterName: String? = null, @@ -7,4 +11,4 @@ data class TocRule( var isVip: String? = null, var updateTime: String? = null, var nextTocUrl: String? = null -) \ No newline at end of file +) : Parcelable \ No newline at end of file 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 2e336fdad..e2538e902 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() } } @@ -20,17 +21,22 @@ object AppConfig { var isNightTheme: Boolean get() = isNightTheme(App.INSTANCE) set(value) { - if (value) { - App.INSTANCE.putPrefString(PreferKey.themeMode, "2") - } else { - App.INSTANCE.putPrefString(PreferKey.themeMode, "1") + if (isNightTheme != value) { + if (value) { + App.INSTANCE.putPrefString(PreferKey.themeMode, "2") + } else { + App.INSTANCE.putPrefString(PreferKey.themeMode, "1") + } } } + 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? @@ -77,12 +83,6 @@ object AppConfig { App.INSTANCE.putPrefInt(PreferKey.ttsSpeechRate, value) } - 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 @@ -115,23 +115,31 @@ object AppConfig { App.INSTANCE.putPrefBoolean("bookGroupAudio", value) } + var bookGroupNoneShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupNone", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupNone", value) + } + var elevation: Int - get() = App.INSTANCE.getPrefInt("elevation", -1) + @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) } -} + var replaceEnableDefault: Boolean = + App.INSTANCE.getPrefBoolean(PreferKey.replaceEnableDefault, true) -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 "" - } + 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 isCoolApk: Boolean get() = App.INSTANCE.channel == "coolApk" +} 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 b6e3ec000..f52252f0b 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -1,35 +1,31 @@ package io.legado.app.help -import android.net.Uri -import androidx.documentfile.provider.DocumentFile -import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap +import com.hankcs.hanlp.HanLP import io.legado.app.App -import io.legado.app.R +import io.legado.app.constant.AppPattern import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.ReplaceRule -import io.legado.app.model.localBook.AnalyzeTxtFile +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.model.localBook.LocalBook import io.legado.app.utils.* +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import org.apache.commons.text.similarity.JaccardSimilarity import org.jetbrains.anko.toast import java.io.File +import java.util.concurrent.CopyOnWriteArraySet import kotlin.math.min object BookHelp { private const val cacheFolderName = "book_cache" - val downloadPath: String - get() = App.INSTANCE.getPrefString(R.string.pk_download_path) - ?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath - ?: App.INSTANCE.cacheDir.absolutePath - - private val downloadUri get() = Uri.parse(downloadPath) - - private fun bookFolderName(book: Book): String { - return formatFolderName(book.name) + MD5Utils.md5Encode16(book.bookUrl) - } + private const val cacheImageFolderName = "images" + private val downloadDir: File = App.INSTANCE.externalFilesDir + private val downloadImages = CopyOnWriteArraySet() fun formatChapterName(bookChapter: BookChapter): String { return String.format( @@ -40,150 +36,169 @@ object BookHelp { } fun clearCache() { - if (downloadPath.isContentPath()) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri) - ?.findFile(cacheFolderName) - ?.delete() - } else { - FileUtils.deleteFile( - FileUtils.getPath( - File(downloadPath), - subDirs = *arrayOf(cacheFolderName) - ) - ) + FileUtils.deleteFile( + FileUtils.getPath(downloadDir, cacheFolderName) + ) + } + + fun clearCache(book: Book) { + val filePath = FileUtils.getPath(downloadDir, cacheFolderName, book.getFolderName()) + FileUtils.deleteFile(filePath) + } + + /** + * 清楚已删除书的缓存 + */ + fun clearRemovedCache() { + Coroutine.async { + val bookFolderNames = arrayListOf() + App.db.bookDao().all.forEach { + bookFolderNames.add(it.getFolderName()) + } + val file = FileUtils.getFile(downloadDir, cacheFolderName) + file.listFiles()?.forEach { bookFile -> + if (!bookFolderNames.contains(bookFile.name)) { + FileUtils.deleteFile(bookFile.absolutePath) + } + } } } - @Synchronized - fun saveContent(book: Book, bookChapter: BookChapter, content: String) { + suspend fun saveContent(book: Book, bookChapter: BookChapter, content: String) { if (content.isEmpty()) return - if (downloadPath.isContentPath()) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - DocumentUtils.createFileIfNotExist( - root, - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.uri?.writeText(App.INSTANCE, content) + //保存文本 + FileUtils.createFileIfNotExist( + downloadDir, + cacheFolderName, + book.getFolderName(), + formatChapterName(bookChapter), + ).writeText(content) + //保存图片 + content.split("\n").forEach { + val matcher = AppPattern.imgPattern.matcher(it) + if (matcher.find()) { + var src = matcher.group(1) + src = NetworkUtils.getAbsoluteURL(bookChapter.url, src) + src?.let { + saveImage(book, src) + } } - } else { - FileUtils.createFileIfNotExist( - File(downloadPath), - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ).writeText(content) } postEvent(EventBus.SAVE_CONTENT, bookChapter) } + suspend fun saveImage(book: Book, src: String) { + while (downloadImages.contains(src)) { + delay(100) + } + if (getImage(book, src).exists()) { + return + } + downloadImages.add(src) + val analyzeUrl = AnalyzeUrl(src) + try { + analyzeUrl.getImageBytes(book.origin)?.let { + FileUtils.createFileIfNotExist( + downloadDir, + cacheFolderName, + book.getFolderName(), + cacheImageFolderName, + "${MD5Utils.md5Encode16(src)}${getImageSuffix(src)}" + ).writeBytes(it) + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + downloadImages.remove(src) + } + } + + fun getImage(book: Book, src: String): File { + return FileUtils.getFile( + downloadDir, + cacheFolderName, + book.getFolderName(), + cacheImageFolderName, + "${MD5Utils.md5Encode16(src)}${getImageSuffix(src)}" + ) + } + + private fun getImageSuffix(src: String): String { + var suffix = src.substringAfterLast(".").substringBefore(",") + if (suffix.length > 5) { + suffix = ".jpg" + } + return suffix + } + fun getChapterFiles(book: Book): List { val fileNameList = arrayListOf() - if (downloadPath.isContentPath()) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - DocumentUtils.createFolderIfNotExist( - root, - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.let { bookDoc -> - DocumentUtils.listFiles(App.INSTANCE, bookDoc.uri).forEach { - fileNameList.add(it.name) - } - } - } - } else { - FileUtils.createFolderIfNotExist( - File(downloadPath), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ).list()?.let { - fileNameList.addAll(it) - } + if (book.isLocalBook()) { + return fileNameList + } + FileUtils.createFolderIfNotExist( + downloadDir, + subDirs = arrayOf(cacheFolderName, book.getFolderName()) + ).list()?.let { + fileNameList.addAll(it) } return fileNameList } + // 检测该章节是否下载 fun hasContent(book: Book, bookChapter: BookChapter): Boolean { - when { - book.isLocalBook() -> { - return true - } - downloadPath.isContentPath() -> { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - return DocumentUtils.exists( - root, - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ) - } - } - else -> { - return FileUtils.exists( - File(downloadPath), - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ) - } + return if (book.isLocalBook()) { + true + } else { + FileUtils.exists( + downloadDir, + cacheFolderName, + book.getFolderName(), + formatChapterName(bookChapter) + ) } - return false } fun getContent(book: Book, bookChapter: BookChapter): String? { - when { - book.isLocalBook() -> { - return AnalyzeTxtFile.getContent(book, bookChapter) - } - downloadPath.isContentPath() -> { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - return DocumentUtils.getDirDocument( - root, - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.findFile(formatChapterName(bookChapter)) - ?.uri?.readText(App.INSTANCE) - } - } - else -> { - val file = FileUtils.getFile( - File(downloadPath), - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ) - if (file.exists()) { - return file.readText() - } + if (book.isLocalBook()) { + return LocalBook.getContext(book, bookChapter) + } else { + val file = FileUtils.getFile( + downloadDir, + cacheFolderName, + book.getFolderName(), + formatChapterName(bookChapter) + ) + if (file.exists()) { + return file.readText() } } return null } fun delContent(book: Book, bookChapter: BookChapter) { - when { - book.isLocalBook() -> return - downloadPath.isContentPath() -> { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - DocumentUtils.getDirDocument( - root, - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.findFile(formatChapterName(bookChapter)) - ?.delete() - } - } - else -> { - FileUtils.createFileIfNotExist( - File(downloadPath), - formatChapterName(bookChapter), - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ).delete() - } + if (book.isLocalBook()) { + return + } else { + FileUtils.createFileIfNotExist( + downloadDir, + cacheFolderName, + book.getFolderName(), + formatChapterName(bookChapter) + ).delete() } } - private fun formatFolderName(folderName: String): String { - return folderName.replace("[\\\\/:*?\"<>|.]".toRegex(), "") + fun formatBookName(name: String): String { + return name + .replace(AppPattern.nameRegex, "") + .trim { it <= ' ' } } - fun formatAuthor(author: String?): String { + fun formatBookAuthor(author: String): String { return author - ?.replace("作\\s*者\\s*[::]\n*".toRegex(), "") - ?.replace("\\s+".toRegex(), " ") - ?.trim { it <= ' ' } - ?: "" + .replace(AppPattern.authorRegex, "") + .trim { it <= ' ' } } /** @@ -192,7 +207,7 @@ object BookHelp { fun getDurChapterIndexByChapterTitle( title: String?, index: Int, - chapters: List + chapters: List, ): Int { if (title.isNullOrEmpty()) { return min(index, chapters.lastIndex) @@ -242,24 +257,16 @@ object BookHelp { private var replaceRules: List = arrayListOf() @Synchronized - fun upReplaceRules(name: String? = null, origin: String? = null) { - if (name != null) { - if (bookName != name || bookOrigin != origin) { - replaceRules = if (origin.isNullOrEmpty()) { - App.db.replaceRuleDao().findEnabledByScope(name) - } else { - App.db.replaceRuleDao().findEnabledByScope(name, origin) - } - bookName = name - bookOrigin = origin - } - } else { - val o = bookOrigin - bookName?.let { - replaceRules = if (o.isNullOrEmpty()) { - App.db.replaceRuleDao().findEnabledByScope(it) - } else { - App.db.replaceRuleDao().findEnabledByScope(it, o) + suspend fun upReplaceRules() { + withContext(IO) { + synchronized(this) { + val o = bookOrigin + bookName?.let { + replaceRules = if (o.isNullOrEmpty()) { + App.db.replaceRuleDao().findEnabledByScope(it) + } else { + App.db.replaceRuleDao().findEnabledByScope(it, o) + } } } } @@ -270,11 +277,21 @@ object BookHelp { name: String, origin: String?, content: String, - enableReplace: Boolean - ): String { + enableReplace: Boolean, + ): List { var c = content if (enableReplace) { - upReplaceRules(name, origin) + synchronized(this) { + if (bookName != name || bookOrigin != origin) { + bookName = name + bookOrigin = origin + replaceRules = if (origin.isNullOrEmpty()) { + App.db.replaceRuleDao().findEnabledByScope(name) + } else { + App.db.replaceRuleDao().findEnabledByScope(name, origin) + } + } + } replaceRules.forEach { item -> item.pattern.let { if (it.isNotEmpty()) { @@ -293,15 +310,29 @@ object BookHelp { } } } - if (!c.substringBefore("\n").contains(title)) { - c = "$title\n$c" + try { + when (AppConfig.chineseConverterType) { + 1 -> c = HanLP.convertToSimplifiedChinese(c) + 2 -> c = HanLP.convertToTraditionalChinese(c) + } + } catch (e: Exception) { + withContext(Main) { + App.INSTANCE.toast("简繁转换出错") + } } - when (AppConfig.chineseConverterType) { - 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) - 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) + 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 c - .replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") - .replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行 + return contents } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/CrashHandler.kt b/app/src/main/java/io/legado/app/help/CrashHandler.kt index b59609e18..e842707d3 100644 --- a/app/src/main/java/io/legado/app/help/CrashHandler.kt +++ b/app/src/main/java/io/legado/app/help/CrashHandler.kt @@ -20,18 +20,14 @@ import java.util.concurrent.TimeUnit * 异常管理类 */ @Suppress("DEPRECATION") -class CrashHandler : Thread.UncaughtExceptionHandler { +class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler { private val tag = this.javaClass.simpleName + /** * 系统默认UncaughtExceptionHandler */ private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null - /** - * context - */ - private var mContext: Context? = null - /** * 存储异常和参数信息 */ @@ -43,8 +39,7 @@ class CrashHandler : Thread.UncaughtExceptionHandler { @SuppressLint("SimpleDateFormat") private val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss") - fun init(context: Context) { - mContext = context + init { mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler() //设置该CrashHandler为系统默认的 Thread.setDefaultUncaughtExceptionHandler(this) @@ -65,14 +60,14 @@ class CrashHandler : Thread.UncaughtExceptionHandler { private fun handleException(ex: Throwable?) { if (ex == null) return //收集设备参数信息 - collectDeviceInfo(mContext!!) + collectDeviceInfo(context) //添加自定义信息 addCustomInfo() kotlin.runCatching { //使用Toast来显示异常信息 Handler(Looper.getMainLooper()).post { Toast.makeText( - mContext, + context, ex.message, Toast.LENGTH_LONG ).show() @@ -141,13 +136,13 @@ class CrashHandler : Thread.UncaughtExceptionHandler { val timestamp = System.currentTimeMillis() val time = format.format(Date()) val fileName = "crash-$time-$timestamp.log" - mContext?.externalCacheDir?.let { rootFile -> - FileUtils.getDirFile(rootFile, "crash").listFiles()?.forEach { + context.externalCacheDir?.let { rootFile -> + FileUtils.getFile(rootFile, "crash").listFiles()?.forEach { if (it.lastModified() < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)) { it.delete() } } - FileUtils.createFileIfNotExist(rootFile, fileName, "crash") + FileUtils.createFileIfNotExist(rootFile, "crash", fileName) .writeText(sb.toString()) } } diff --git a/app/src/main/java/io/legado/app/help/DefaultValueHelp.kt b/app/src/main/java/io/legado/app/help/DefaultValueHelp.kt new file mode 100644 index 000000000..49338e960 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/DefaultValueHelp.kt @@ -0,0 +1,19 @@ +package io.legado.app.help + +import io.legado.app.App +import io.legado.app.data.entities.HttpTTS +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray + +object DefaultValueHelp { + + + fun initHttpTTS() { + val json = String(App.INSTANCE.assets.open("httpTTS.json").readBytes()) + GSON.fromJsonArray(json)?.let { + App.db.httpTTSDao().insert(*it.toTypedArray()) + } + } + + +} \ 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..9a58f45bf 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,21 @@ 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 io.legado.app.model.analyzeRule.AnalyzeUrl 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(AnalyzeUrl(path).getGlideUrl()) + 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/IntentHelp.kt b/app/src/main/java/io/legado/app/help/IntentHelp.kt index d02e13392..d7ed03af1 100644 --- a/app/src/main/java/io/legado/app/help/IntentHelp.kt +++ b/app/src/main/java/io/legado/app/help/IntentHelp.kt @@ -3,6 +3,7 @@ package io.legado.app.help import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Bundle import io.legado.app.R import org.jetbrains.anko.toast @@ -32,21 +33,29 @@ object IntentHelp { } } - inline fun servicePendingIntent(context: Context, action: String): PendingIntent? { - return PendingIntent.getService( - context, - 0, - Intent(context, T::class.java).apply { this.action = action }, - PendingIntent.FLAG_UPDATE_CURRENT - ) + inline fun servicePendingIntent( + context: Context, + action: String, + bundle: Bundle? = null + ): PendingIntent? { + val intent = Intent(context, T::class.java) + intent.action = action + bundle?.let { + intent.putExtras(bundle) + } + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } - inline fun activityPendingIntent(context: Context, action: String): PendingIntent? { - return PendingIntent.getActivity( - context, - 0, - Intent(context, T::class.java).apply { this.action = action }, - PendingIntent.FLAG_UPDATE_CURRENT - ) + inline fun activityPendingIntent( + context: Context, + action: String, + bundle: Bundle? = null + ): PendingIntent? { + val intent = Intent(context, T::class.java) + intent.action = action + bundle?.let { + intent.putExtras(bundle) + } + return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 493ede9c3..201f2e62a 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -6,6 +6,9 @@ import io.legado.app.constant.AppConst.dateFormat import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.EncoderUtils import io.legado.app.utils.MD5Utils +import io.legado.app.utils.htmlFormat +import io.legado.app.utils.msg +import java.net.URLEncoder import java.util.* @Keep @@ -22,7 +25,18 @@ interface JsExtensions { val response = call.execute() response.body() } catch (e: Exception) { - e.localizedMessage + e.msg + } + } + + fun connect(urlStr: String): Any { + return try { + val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null) + val call = analyzeUrl.getResponse(urlStr) + val response = call.execute() + response + } catch (e: Exception) { + e.msg } } @@ -41,15 +55,34 @@ interface JsExtensions { return EncoderUtils.base64Encode(str, flags) } - fun md5Encode(str: String): String? { + fun md5Encode(str: String): String { return MD5Utils.md5Encode(str) } - fun md5Encode16(str: String): String? { + fun md5Encode16(str: String): String { return MD5Utils.md5Encode16(str) } fun timeFormat(time: Long): String { return dateFormat.format(Date(time)) } + + //utf8编码转gbk编码 + fun utf8ToGbk(str: String): String { + val utf8 = String(str.toByteArray(charset("UTF-8"))) + val unicode = String(utf8.toByteArray(), charset("UTF-8")) + return String(unicode.toByteArray(charset("GBK"))) + } + + fun encodeURI(str: String): String { + return try { + URLEncoder.encode(str, "UTF-8") + } catch (e: Exception) { + "" + } + } + + fun htmlFormat(str: String): String { + return str.htmlFormat() + } } diff --git a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt index b859336e4..04723c21a 100644 --- a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt +++ b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt @@ -26,7 +26,7 @@ object LauncherIconHelp { fun changeIcon(icon: String?) { if (icon.isNullOrEmpty()) return if (Build.VERSION.SDK_INT < 26) { - App.INSTANCE.toast(R.string.chage_icon_error) + App.INSTANCE.toast(R.string.change_icon_error) return } var hasEnabled = false diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index d0e67e901..4353e2fe1 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -4,13 +4,15 @@ import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import android.os.Parcelable import androidx.annotation.Keep import io.legado.app.App import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.help.coroutine.Coroutine -import io.legado.app.ui.book.read.page.ChapterProvider +import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.* +import kotlinx.android.parcel.Parcelize import java.io.File /** @@ -18,21 +20,33 @@ import java.io.File */ @Keep object ReadBookConfig { - const val readConfigFileName = "readConfig.json" - private val configFilePath = - App.INSTANCE.filesDir.absolutePath + File.separator + readConfigFileName + const val configFileName = "readConfig.json" + const val shareConfigFileName = "shareReadConfig.json" + val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, configFileName) + val shareConfigFilePath = FileUtils.getPath(App.INSTANCE.filesDir, shareConfigFileName) val configList: ArrayList = arrayListOf() + lateinit var shareConfig: Config private val defaultConfigs by lazy { - val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes()) + val json = String(App.INSTANCE.assets.open(configFileName).readBytes()) GSON.fromJsonArray(json)!! } - val durConfig get() = getConfig(styleSelect) - private val shareConfig get() = getConfig(5) + var durConfig + get() = getConfig(styleSelect) + set(value) { + configList[styleSelect] = value + if (shareLayout) { + shareConfig = value + } + upBg() + } + var bg: Drawable? = null var bgMeanColor: Int = 0 + val textColor: Int get() = durConfig.textColor() init { - upConfig() + initConfigs() + initShareConfig() } @Synchronized @@ -40,29 +54,38 @@ object ReadBookConfig { if (configList.size < 5) { resetAll() } - if (configList.size < 6) { - configList.add(Config()) - } - return configList[index] + return configList.getOrNull(index) ?: configList[0] } - fun upConfig() { - (getConfigs() ?: defaultConfigs).let { + fun initConfigs() { + val configFile = File(configFilePath) + var configs: List? = null + if (configFile.exists()) { + try { + val json = configFile.readText() + configs = GSON.fromJsonArray(json) + } catch (e: Exception) { + e.printStackTrace() + } + } + (configs ?: defaultConfigs).let { configList.clear() configList.addAll(it) } } - private fun getConfigs(): List? { + fun initShareConfig() { val configFile = File(configFilePath) + var c: Config? = null if (configFile.exists()) { try { val json = configFile.readText() - return GSON.fromJsonArray(json) + c = GSON.fromJsonObject(json) } catch (e: Exception) { + e.printStackTrace() } } - return null + shareConfig = c ?: configList.getOrNull(5) ?: Config() } fun upBg() { @@ -77,22 +100,34 @@ object ReadBookConfig { bgMeanColor = color } } + isScroll = pageAnim == 3 } fun save() { Coroutine.async { - val json = GSON.toJson(configList) - FileUtils.createFileIfNotExist(configFilePath).writeText(json) + synchronized(this) { + GSON.toJson(configList).let { + FileUtils.deleteFile(configFilePath) + FileUtils.createFileIfNotExist(configFilePath).writeText(it) + } + GSON.toJson(shareConfig).let { + FileUtils.deleteFile(shareConfigFilePath) + FileUtils.createFileIfNotExist(shareConfigFilePath).writeText(it) + } + } } } - fun resetDur() { - defaultConfigs[styleSelect].let { - durConfig.setBg(it.bgType(), it.bgStr()) - durConfig.setTextColor(it.textColor()) + fun deleteDur(): Boolean { + if (configList.size > 5) { + configList.removeAt(styleSelect) + if (styleSelect > 0) { + styleSelect -= 1 + } upBg() - save() + return true } + return false } private fun resetAll() { @@ -104,6 +139,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 @@ -118,16 +158,16 @@ 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) + isScroll = pageAnim == 3 } var isScroll = pageAnim == 3 val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true) + val textFullJustify get() = App.INSTANCE.getPrefBoolean(PreferKey.textFullJustify, true) + val textBottomJustify get() = App.INSTANCE.getPrefBoolean(PreferKey.textBottomJustify, true) var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) set(value) { field = value @@ -139,145 +179,212 @@ object ReadBookConfig { var bodyIndent = " ".repeat(bodyIndentCount) var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) - var textBold: Boolean - get() = if (shareLayout) shareConfig.textBold else durConfig.textBold - set(value) = if (shareLayout) shareConfig.textBold = value else durConfig.textBold = value + + val config get() = if (shareLayout) shareConfig else durConfig + + var textFont: String + get() = config.textFont + set(value) { + config.textFont = value + } + + var textBold: Int + get() = config.textBold + set(value) { + config.textBold = value + } var textSize: Int - get() = if (shareLayout) shareConfig.textSize else durConfig.textSize - set(value) = if (shareLayout) shareConfig.textSize = value else durConfig.textSize = value + get() = config.textSize + set(value) { + config.textSize = value + } var letterSpacing: Float - get() = if (shareLayout) shareConfig.letterSpacing else durConfig.letterSpacing - set(value) = - if (shareLayout) shareConfig.letterSpacing = value else durConfig.letterSpacing = value + get() = config.letterSpacing + set(value) { + config.letterSpacing = value + } var lineSpacingExtra: Int - get() = if (shareLayout) shareConfig.lineSpacingExtra else durConfig.lineSpacingExtra - set(value) = - if (shareLayout) shareConfig.lineSpacingExtra = value - else durConfig.lineSpacingExtra = value + get() = config.lineSpacingExtra + set(value) { + config.lineSpacingExtra = value + } var paragraphSpacing: Int - get() = if (shareLayout) shareConfig.paragraphSpacing else durConfig.paragraphSpacing - set(value) = - if (shareLayout) shareConfig.paragraphSpacing = value - else durConfig.paragraphSpacing = value + get() = config.paragraphSpacing + set(value) { + config.paragraphSpacing = value + } var titleMode: Int - get() = if (shareLayout) shareConfig.titleMode else durConfig.titleMode - set(value) = - if (shareLayout) shareConfig.titleMode = value else durConfig.titleMode = value + get() = config.titleMode + set(value) { + config.titleMode = value + } var titleSize: Int - get() = if (shareLayout) shareConfig.titleSize else durConfig.titleSize - set(value) = - if (shareLayout) shareConfig.titleSize = value else durConfig.titleSize = value + get() = config.titleSize + set(value) { + config.titleSize = value + } + var titleTopSpacing: Int - get() = if (shareLayout) shareConfig.titleTopSpacing else durConfig.titleTopSpacing - set(value) = - if (shareLayout) shareConfig.titleTopSpacing = value - else durConfig.titleTopSpacing = value + get() = config.titleTopSpacing + set(value) { + config.titleTopSpacing = value + } + var titleBottomSpacing: Int - get() = if (shareLayout) shareConfig.titleBottomSpacing else durConfig.titleBottomSpacing - set(value) = - if (shareLayout) shareConfig.titleBottomSpacing = value - else durConfig.titleBottomSpacing = value + get() = config.titleBottomSpacing + set(value) { + config.titleBottomSpacing = value + } var paddingBottom: Int - get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom - set(value) = - if (shareLayout) shareConfig.paddingBottom = value else durConfig.paddingBottom = value + get() = config.paddingBottom + set(value) { + config.paddingBottom = value + } var paddingLeft: Int - get() = if (shareLayout) shareConfig.paddingLeft else durConfig.paddingLeft - set(value) = - if (shareLayout) shareConfig.paddingLeft = value else durConfig.paddingLeft = value + get() = config.paddingLeft + set(value) { + config.paddingLeft = value + } var paddingRight: Int - get() = if (shareLayout) shareConfig.paddingRight else durConfig.paddingRight - set(value) = - if (shareLayout) shareConfig.paddingRight = value else durConfig.paddingRight = value + get() = config.paddingRight + set(value) { + config.paddingRight = value + } var paddingTop: Int - get() = if (shareLayout) shareConfig.paddingTop else durConfig.paddingTop - set(value) = - if (shareLayout) shareConfig.paddingTop = value else durConfig.paddingTop = value + get() = config.paddingTop + set(value) { + config.paddingTop = value + } var headerPaddingBottom: Int - get() = if (shareLayout) shareConfig.headerPaddingBottom else durConfig.headerPaddingBottom - set(value) = - if (shareLayout) shareConfig.headerPaddingBottom = value - else durConfig.headerPaddingBottom = value + get() = config.headerPaddingBottom + set(value) { + config.headerPaddingBottom = value + } var headerPaddingLeft: Int - get() = if (shareLayout) shareConfig.headerPaddingLeft else durConfig.headerPaddingLeft - set(value) = - if (shareLayout) shareConfig.headerPaddingLeft = value - else durConfig.headerPaddingLeft = value + get() = config.headerPaddingLeft + set(value) { + config.headerPaddingLeft = value + } var headerPaddingRight: Int - get() = if (shareLayout) shareConfig.headerPaddingRight else durConfig.headerPaddingRight - set(value) = - if (shareLayout) shareConfig.headerPaddingRight = value - else durConfig.headerPaddingRight = value + get() = config.headerPaddingRight + set(value) { + config.headerPaddingRight = value + } var headerPaddingTop: Int - get() = if (shareLayout) shareConfig.headerPaddingTop else durConfig.headerPaddingTop - set(value) = - if (shareLayout) shareConfig.headerPaddingTop = value - else durConfig.headerPaddingTop = value + get() = config.headerPaddingTop + set(value) { + config.headerPaddingTop = value + } var footerPaddingBottom: Int - get() = if (shareLayout) shareConfig.footerPaddingBottom else durConfig.footerPaddingBottom - set(value) = - if (shareLayout) shareConfig.footerPaddingBottom = value - else durConfig.footerPaddingBottom = value + get() = config.footerPaddingBottom + set(value) { + config.footerPaddingBottom = value + } var footerPaddingLeft: Int - get() = if (shareLayout) shareConfig.footerPaddingLeft else durConfig.footerPaddingLeft - set(value) = - if (shareLayout) shareConfig.footerPaddingLeft = value - else durConfig.footerPaddingLeft = value + get() = config.footerPaddingLeft + set(value) { + config.footerPaddingLeft = value + } var footerPaddingRight: Int - get() = if (shareLayout) shareConfig.footerPaddingRight else durConfig.footerPaddingRight - set(value) = - if (shareLayout) shareConfig.footerPaddingRight = value - else durConfig.footerPaddingRight = value + get() = config.footerPaddingRight + set(value) { + config.footerPaddingRight = value + } var footerPaddingTop: Int - get() = if (shareLayout) shareConfig.footerPaddingTop else durConfig.footerPaddingTop - set(value) = - if (shareLayout) shareConfig.footerPaddingTop = value - else durConfig.footerPaddingTop = value + get() = config.footerPaddingTop + set(value) { + config.footerPaddingTop = value + } var showHeaderLine: Boolean - get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine - set(value) = - if (shareLayout) shareConfig.showHeaderLine = value - else durConfig.showHeaderLine = value + get() = config.showHeaderLine + set(value) { + config.showHeaderLine = value + } var showFooterLine: Boolean - get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine - set(value) = - if (shareLayout) shareConfig.showFooterLine = value - else durConfig.showFooterLine = value + get() = config.showFooterLine + set(value) { + config.showFooterLine = value + } + + fun getExportConfig(): Config { + val exportConfig = GSON.fromJsonObject(GSON.toJson(durConfig))!! + if (shareLayout) { + exportConfig.textFont = shareConfig.textFont + exportConfig.textBold = shareConfig.textBold + exportConfig.textSize = shareConfig.textSize + exportConfig.letterSpacing = shareConfig.letterSpacing + exportConfig.lineSpacingExtra = shareConfig.lineSpacingExtra + exportConfig.paragraphSpacing = shareConfig.paragraphSpacing + exportConfig.titleMode = shareConfig.titleMode + exportConfig.titleSize = shareConfig.titleSize + exportConfig.titleTopSpacing = shareConfig.titleTopSpacing + exportConfig.titleBottomSpacing = shareConfig.titleBottomSpacing + exportConfig.paddingBottom = shareConfig.paddingBottom + exportConfig.paddingLeft = shareConfig.paddingLeft + exportConfig.paddingRight = shareConfig.paddingRight + exportConfig.paddingTop = shareConfig.paddingTop + exportConfig.headerPaddingBottom = shareConfig.headerPaddingBottom + exportConfig.headerPaddingLeft = shareConfig.headerPaddingLeft + exportConfig.headerPaddingRight = shareConfig.headerPaddingRight + exportConfig.headerPaddingTop = shareConfig.headerPaddingTop + exportConfig.footerPaddingBottom = shareConfig.footerPaddingBottom + exportConfig.footerPaddingLeft = shareConfig.footerPaddingLeft + exportConfig.footerPaddingRight = shareConfig.footerPaddingRight + exportConfig.footerPaddingTop = shareConfig.footerPaddingTop + exportConfig.showHeaderLine = shareConfig.showHeaderLine + exportConfig.showFooterLine = shareConfig.showFooterLine + exportConfig.tipHeaderLeft = shareConfig.tipHeaderLeft + exportConfig.tipHeaderMiddle = shareConfig.tipHeaderMiddle + exportConfig.tipHeaderRight = shareConfig.tipHeaderRight + exportConfig.tipFooterLeft = shareConfig.tipFooterLeft + exportConfig.tipFooterMiddle = shareConfig.tipFooterMiddle + exportConfig.tipFooterRight = shareConfig.tipFooterRight + exportConfig.hideHeader = shareConfig.hideHeader + exportConfig.hideFooter = shareConfig.hideFooter + } + return exportConfig + } @Keep + @Parcelize class Config( private var bgStr: String = "#EEEEEE",//白天背景 private var bgStrNight: String = "#000000",//夜间背景 + private var bgStrEInk: String = "#FFFFFF", private var bgType: Int = 0,//白天背景类型 0:颜色, 1:assets图片, 2其它图片 private var bgTypeNight: Int = 0,//夜间背景类型 + private var bgTypeEInk: Int = 0, private var darkStatusIcon: Boolean = true,//白天是否暗色状态栏 private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏 + private var darkStatusIconEInk: Boolean = true, private var textColor: String = "#3E3D3B",//白天文字颜色 private var textColorNight: String = "#ADADAD",//夜间文字颜色 - var textBold: Boolean = false,//是否粗体字 + private var textColorEInk: String = "#000000", + var textFont: String = "",//字体 + 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 = 2,//段距 var titleMode: Int = 0,//标题居中 var titleSize: Int = 0, var titleTopSpacing: Int = 0, @@ -295,56 +402,80 @@ object ReadBookConfig { var footerPaddingRight: Int = 16, var footerPaddingTop: Int = 6, var showHeaderLine: Boolean = false, - var showFooterLine: Boolean = true - ) { + var showFooterLine: Boolean = true, + var tipHeaderLeft: Int = ReadTipConfig.time, + var tipHeaderMiddle: Int = ReadTipConfig.none, + var tipHeaderRight: Int = ReadTipConfig.battery, + var tipFooterLeft: Int = ReadTipConfig.chapterTitle, + var tipFooterMiddle: Int = ReadTipConfig.none, + var tipFooterRight: Int = ReadTipConfig.pageAndTotal, + var hideHeader: Boolean = true, + var hideFooter: Boolean = false + ) : Parcelable { fun setBg(bgType: Int, bg: String) { - if (AppConfig.isNightTheme) { - bgTypeNight = bgType - bgStrNight = bg - } else { - this.bgType = bgType - bgStr = bg + when { + AppConfig.isEInkMode -> { + bgTypeEInk = bgType + bgStrEInk = bg + } + AppConfig.isNightTheme -> { + bgTypeNight = bgType + bgStrNight = bg + } + else -> { + this.bgType = bgType + bgStr = bg + } } } fun setTextColor(color: Int) { - if (AppConfig.isNightTheme) { - textColorNight = "#${color.hexString}" - } else { - textColor = "#${color.hexString}" + when { + AppConfig.isEInkMode -> textColorEInk = "#${color.hexString}" + AppConfig.isNightTheme -> textColorNight = "#${color.hexString}" + else -> textColor = "#${color.hexString}" } ChapterProvider.upStyle() } fun setStatusIconDark(isDark: Boolean) { - if (AppConfig.isNightTheme) { - darkStatusIconNight = isDark - } else { - darkStatusIcon = isDark + when { + AppConfig.isEInkMode -> darkStatusIconEInk = isDark + AppConfig.isNightTheme -> darkStatusIconNight = isDark + else -> darkStatusIcon = isDark } } fun statusIconDark(): Boolean { - return if (AppConfig.isNightTheme) { - darkStatusIconNight - } else { - darkStatusIcon + return when { + AppConfig.isEInkMode -> darkStatusIconEInk + AppConfig.isNightTheme -> darkStatusIconNight + else -> darkStatusIcon } } fun textColor(): Int { - return if (AppConfig.isNightTheme) Color.parseColor(textColorNight) - else Color.parseColor(textColor) + return when { + AppConfig.isEInkMode -> Color.parseColor(textColorEInk) + AppConfig.isNightTheme -> Color.parseColor(textColorNight) + else -> Color.parseColor(textColor) + } } fun bgStr(): String { - return if (AppConfig.isNightTheme) bgStrNight - else bgStr + return when { + AppConfig.isEInkMode -> bgStrEInk + AppConfig.isNightTheme -> bgStrNight + else -> bgStr + } } fun bgType(): Int { - return if (AppConfig.isNightTheme) bgTypeNight - else bgType + return when { + AppConfig.isEInkMode -> bgTypeEInk + AppConfig.isNightTheme -> bgTypeNight + else -> bgType + } } fun bgDrawable(width: Int, height: Int): Drawable { diff --git a/app/src/main/java/io/legado/app/help/ReadTipConfig.kt b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt new file mode 100644 index 000000000..eb37a10e1 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt @@ -0,0 +1,70 @@ +package io.legado.app.help + +import io.legado.app.App +import io.legado.app.R + +object ReadTipConfig { + val tipArray: Array = App.INSTANCE.resources.getStringArray(R.array.read_tip) + const val none = 0 + const val chapterTitle = 1 + const val time = 2 + const val battery = 3 + const val page = 4 + const val totalProgress = 5 + const val pageAndTotal = 6 + + val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] } + val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] } + val tipHeaderRightStr: String get() = tipArray.getOrElse(tipHeaderRight) { tipArray[none] } + val tipFooterLeftStr: String get() = tipArray.getOrElse(tipFooterLeft) { tipArray[none] } + val tipFooterMiddleStr: String get() = tipArray.getOrElse(tipFooterMiddle) { tipArray[none] } + val tipFooterRightStr: String get() = tipArray.getOrElse(tipFooterRight) { tipArray[none] } + + var tipHeaderLeft: Int + get() = ReadBookConfig.config.tipHeaderLeft + set(value) { + ReadBookConfig.config.tipHeaderLeft = value + } + + var tipHeaderMiddle: Int + get() = ReadBookConfig.config.tipHeaderMiddle + set(value) { + ReadBookConfig.config.tipHeaderMiddle = value + } + + var tipHeaderRight: Int + get() = ReadBookConfig.config.tipHeaderRight + set(value) { + ReadBookConfig.config.tipHeaderRight = value + } + + var tipFooterLeft: Int + get() = ReadBookConfig.config.tipFooterLeft + set(value) { + ReadBookConfig.config.tipFooterLeft = value + } + + var tipFooterMiddle: Int + get() = ReadBookConfig.config.tipFooterMiddle + set(value) { + ReadBookConfig.config.tipFooterMiddle = value + } + + var tipFooterRight: Int + get() = ReadBookConfig.config.tipFooterRight + set(value) { + ReadBookConfig.config.tipFooterRight = value + } + + var hideHeader: Boolean + get() = ReadBookConfig.config.hideHeader + set(value) { + ReadBookConfig.config.hideHeader = value + } + + var hideFooter: Boolean + get() = ReadBookConfig.config.hideFooter + set(value) { + ReadBookConfig.config.hideFooter = value + } +} \ No newline at end of file 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..ebeb30d02 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/SourceHelp.kt @@ -0,0 +1,67 @@ +package io.legado.app.help + +import android.os.Handler +import android.os.Looper +import io.legado.app.App +import io.legado.app.data.entities.BookSource +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) + } + } + } + + fun insertBookSource(vararg bookSources: BookSource) { + bookSources.forEach { bookSource -> + if (is18Plus(bookSource.bookSourceUrl)) { + handler.post { + App.INSTANCE.toast("${bookSource.bookSourceName}是18+网址,禁止导入.") + } + } else { + App.db.bookSourceDao().insert(bookSource) + } + } + } + + private fun is18Plus(url: String?): Boolean { + url ?: return false + val baseUrl = NetworkUtils.getBaseUrl(url) + baseUrl ?: return false + if (AppConfig.isGooglePlay) 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/ThemeConfig.kt b/app/src/main/java/io/legado/app/help/ThemeConfig.kt new file mode 100644 index 000000000..5061d129f --- /dev/null +++ b/app/src/main/java/io/legado/app/help/ThemeConfig.kt @@ -0,0 +1,153 @@ +package io.legado.app.help + +import android.content.Context +import android.graphics.Color +import androidx.annotation.Keep +import io.legado.app.App +import io.legado.app.R +import io.legado.app.constant.EventBus +import io.legado.app.constant.PreferKey +import io.legado.app.utils.* +import java.io.File + +object ThemeConfig { + const val configFileName = "themeConfig.json" + val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, configFileName) + private val defaultConfigs by lazy { + val json = String(App.INSTANCE.assets.open(configFileName).readBytes()) + GSON.fromJsonArray(json)!! + } + val configList = arrayListOf() + + init { + upConfig() + } + + fun upConfig() { + (getConfigs() ?: defaultConfigs).let { + configList.clear() + configList.addAll(it) + } + } + + fun save() { + val json = GSON.toJson(configList) + FileUtils.deleteFile(configFilePath) + FileUtils.createFileIfNotExist(configFilePath).writeText(json) + } + + fun delConfig(index: Int) { + configList.removeAt(index) + save() + } + + fun addConfig(json: String): Boolean { + GSON.fromJsonObject(json.trim { it < ' ' })?.let { + addConfig(it) + return true + } + return false + } + + private fun addConfig(newConfig: Config) { + configList.forEachIndexed { index, config -> + if (newConfig.themeName == config.themeName) { + configList[index] = newConfig + return + } + } + configList.add(newConfig) + save() + } + + private fun getConfigs(): List? { + val configFile = File(configFilePath) + if (configFile.exists()) { + try { + val json = configFile.readText() + return GSON.fromJsonArray(json) + } catch (e: Exception) { + e.printStackTrace() + } + } + return null + } + + fun applyConfig(context: Context, config: Config) { + val primary = Color.parseColor(config.primaryColor) + val accent = Color.parseColor(config.accentColor) + val background = Color.parseColor(config.backgroundColor) + val bBackground = Color.parseColor(config.bottomBackground) + if (config.isNightTheme) { + context.putPrefInt(PreferKey.cNPrimary, primary) + context.putPrefInt(PreferKey.cNAccent, accent) + context.putPrefInt(PreferKey.cNBackground, background) + context.putPrefInt(PreferKey.cNBBackground, bBackground) + } else { + context.putPrefInt(PreferKey.cPrimary, primary) + context.putPrefInt(PreferKey.cAccent, accent) + context.putPrefInt(PreferKey.cBackground, background) + context.putPrefInt(PreferKey.cBBackground, bBackground) + } + AppConfig.isNightTheme = config.isNightTheme + App.INSTANCE.applyDayNight() + postEvent(EventBus.RECREATE, "") + } + + fun saveDayTheme(context: Context, name: String) { + val primary = + context.getPrefInt(PreferKey.cPrimary, context.getCompatColor(R.color.md_brown_500)) + val accent = + context.getPrefInt(PreferKey.cAccent, context.getCompatColor(R.color.md_red_600)) + val background = + context.getPrefInt(PreferKey.cBackground, context.getCompatColor(R.color.md_grey_100)) + val bBackground = + context.getPrefInt(PreferKey.cBBackground, context.getCompatColor(R.color.md_grey_200)) + val config = Config( + themeName = name, + isNightTheme = false, + primaryColor = "#${primary.hexString}", + accentColor = "#${accent.hexString}", + backgroundColor = "#${background.hexString}", + bottomBackground = "#${bBackground.hexString}" + ) + addConfig(config) + } + + fun saveNightTheme(context: Context, name: String) { + val primary = + context.getPrefInt( + PreferKey.cNPrimary, + context.getCompatColor(R.color.md_blue_grey_600) + ) + val accent = + context.getPrefInt( + PreferKey.cNAccent, + context.getCompatColor(R.color.md_deep_orange_800) + ) + val background = + context.getPrefInt(PreferKey.cNBackground, context.getCompatColor(R.color.md_grey_900)) + val bBackground = + context.getPrefInt(PreferKey.cNBBackground, context.getCompatColor(R.color.md_grey_850)) + val config = Config( + themeName = name, + isNightTheme = true, + primaryColor = "#${primary.hexString}", + accentColor = "#${accent.hexString}", + backgroundColor = "#${background.hexString}", + bottomBackground = "#${bBackground.hexString}" + ) + addConfig(config) + } + + @Keep + class Config( + var themeName: String, + var isNightTheme: Boolean, + var primaryColor: String, + var accentColor: String, + var backgroundColor: String, + var bottomBackground: String + ) + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt b/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt index f501fcb71..a12816486 100644 --- a/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt +++ b/app/src/main/java/io/legado/app/help/http/AjaxWebView.kt @@ -1,12 +1,14 @@ package io.legado.app.help.http import android.annotation.SuppressLint -import android.os.Build import android.os.Handler import android.os.Looper import android.os.Message import android.text.TextUtils -import android.webkit.* +import android.webkit.CookieManager +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient import io.legado.app.App import io.legado.app.constant.AppConst import org.apache.commons.text.StringEscapeUtils @@ -142,30 +144,6 @@ class AjaxWebView { handler.postDelayed(runnable, 1000) } - override fun onReceivedError( - view: WebView, - errorCode: Int, - description: String, - failingUrl: String - ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - handler.obtainMessage(MSG_ERROR, Exception(description)) - .sendToTarget() - } - } - - override fun onReceivedError( - view: WebView, - request: WebResourceRequest, - error: WebResourceError - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - handler.obtainMessage( - MSG_ERROR, - Exception(error.description.toString()) - ).sendToTarget() - } - } } private class EvalJsRunnable( @@ -180,6 +158,7 @@ class AjaxWebView { mWebView.get()?.evaluateJavascript(mJavaScript) { if (it.isNotEmpty() && it != "null") { val content = StringEscapeUtils.unescapeJson(it) + .replace("^\"|\"$".toRegex(), "") handler.obtainMessage(MSG_SUCCESS, Res(url, content)) .sendToTarget() handler.removeCallbacks(this) @@ -212,31 +191,6 @@ class AjaxWebView { } } - override fun onReceivedError( - view: WebView, - errorCode: Int, - description: String, - failingUrl: String - ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - handler.obtainMessage(MSG_ERROR, Exception(description)) - .sendToTarget() - } - } - - override fun onReceivedError( - view: WebView, - request: WebResourceRequest, - error: WebResourceError - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - handler.obtainMessage( - MSG_ERROR, - Exception(error.description.toString()) - ).sendToTarget() - } - } - override fun onPageFinished(view: WebView, url: String) { params.setCookie(url) if (params.hasJavaScript()) { 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..067435ddb 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 @@ -1,16 +1,18 @@ package io.legado.app.help.http import android.text.TextUtils +import com.franmontiel.persistentcookiejar.persistence.CookiePersistor +import com.franmontiel.persistentcookiejar.persistence.SerializableCookie import io.legado.app.App import io.legado.app.data.entities.Cookie import io.legado.app.help.coroutine.Coroutine +import io.legado.app.utils.NetworkUtils - -object CookieStore { +object CookieStore : CookiePersistor { fun setCookie(url: String, cookie: String?) { Coroutine.async { - val cookieBean = Cookie(url, cookie ?: "") + val cookieBean = Cookie(NetworkUtils.getSubDomain(url), cookie ?: "") App.db.cookieDao().insert(cookieBean) } } @@ -31,16 +33,12 @@ object CookieStore { } fun getCookie(url: String): String { - val cookieBean = App.db.cookieDao().get(url) + val cookieBean = App.db.cookieDao().get(NetworkUtils.getSubDomain(url)) return cookieBean?.cookie ?: "" } fun removeCookie(url: String) { - App.db.cookieDao().delete(url) - } - - fun clearCookies() { - + App.db.cookieDao().delete(NetworkUtils.getSubDomain(url)) } private fun cookieToMap(cookie: String): MutableMap { @@ -80,5 +78,36 @@ object CookieStore { return builder.deleteCharAt(builder.lastIndexOf(";")).toString() } + override fun loadAll(): MutableList { + val cookies = arrayListOf() + App.db.cookieDao().getOkHttpCookies().forEach { + val serializedCookie = it.cookie + SerializableCookie().decode(serializedCookie)?.let { ck -> + cookies.add(ck) + } + } + return cookies + } + + override fun saveAll(cookies: MutableCollection?) { + val mCookies = arrayListOf() + cookies?.forEach { + mCookies.add(Cookie(createCookieKey(it), SerializableCookie().encode(it))) + } + App.db.cookieDao().insert(*mCookies.toTypedArray()) + } + + override fun removeAll(cookies: MutableCollection?) { + cookies?.forEach { + App.db.cookieDao().delete(createCookieKey(it)) + } + } + + override fun clear() { + App.db.cookieDao().deleteOkHttp() + } + private fun createCookieKey(cookie: okhttp3.Cookie): String { + return (if (cookie.secure()) "https" else "http") + "://" + cookie.domain() + cookie.path() + "|" + cookie.name() + } } \ No newline at end of file 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 df417e2f9..6364e9ed8 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,11 +1,13 @@ 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 import okhttp3.* import retrofit2.Retrofit -import java.util.* +import java.net.InetSocketAddress +import java.net.Proxy import java.util.concurrent.TimeUnit import kotlin.coroutines.resume @@ -13,14 +15,12 @@ import kotlin.coroutines.resume object HttpHelper { val client: OkHttpClient by lazy { - val default = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_2) - .build() - val specs = ArrayList() - specs.add(default) - specs.add(ConnectionSpec.COMPATIBLE_TLS) - specs.add(ConnectionSpec.CLEARTEXT) + val specs = arrayListOf( + ConnectionSpec.MODERN_TLS, + ConnectionSpec.COMPATIBLE_TLS, + ConnectionSpec.CLEARTEXT + ) val builder = OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) @@ -41,27 +41,38 @@ 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() } return null } + fun getBytes(url: String, queryMap: Map, headers: Map): ByteArray? { + NetworkUtils.getBaseUrl(url)?.let { baseUrl -> + return getByteRetrofit(baseUrl) + .create(HttpGetApi::class.java) + .getMapByte(url, queryMap, headers) + .execute() + .body() + } + return null + } + 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 } - suspend fun simpleGetByteAsync(url: String): ByteArray? { + suspend fun simpleGetBytesAsync(url: String): ByteArray? { 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 @@ -71,6 +82,18 @@ object HttpHelper { return getRetrofit(baseUrl, encode).create(T::class.java) } + inline fun getApiServiceWithProxy( + baseUrl: String, + encode: String? = null, + proxy: String? = null + ): T { + return getRetrofitWithProxy(baseUrl, encode, proxy).create(T::class.java) + } + + inline fun getBytesApiService(baseUrl: String): T { + return getByteRetrofit(baseUrl).create(T::class.java) + } + fun getRetrofit(baseUrl: String, encode: String? = null): Retrofit { return Retrofit.Builder().baseUrl(baseUrl) //增加返回值为字符串的支持(以实体类返回) @@ -79,6 +102,56 @@ object HttpHelper { .build() } + fun getRetrofitWithProxy( + baseUrl: String, + encode: String? = null, + proxy: String? = null + ): Retrofit { + val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?") + val ms = proxy?.let { r.findAll(it) }; + val group = ms?.first() + var type = "direct" //直接连接 + var host = "127.0.0.1" //代理服务器hostname + var port = 1080 //代理服务器port + var username = "" //代理服务器验证用户名 + var password = "" //代理服务器验证密码 + if (group != null) { + type = if (group.groupValues[1] == "http") { + "http" + } else { + "socks" + } + host = group.groupValues[2] + port = group.groupValues[3].toInt() + if (group.groupValues[4] != "") { + username = group.groupValues[4].split("@")[1] + password = group.groupValues[4].split("@")[2] + } + } + val builder = client.newBuilder() + if (type != "direct" && host != "") { + if (type == "http") { + builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port))); + } else { + builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))); + } + if (username != "" && password != "") { + builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码 + val credential: String = Credentials.basic(username, password) + response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build() + } + } + + } + return Retrofit.Builder().baseUrl(baseUrl) + //增加返回值为字符串的支持(以实体类返回) + .addConverterFactory(EncodeConverter(encode)) + .client(builder.build()) + .build() + } + fun getByteRetrofit(baseUrl: String): Retrofit { return Retrofit.Builder().baseUrl(baseUrl) .addConverterFactory(ByteConverter()) @@ -118,4 +191,4 @@ object HttpHelper { webView.load(params) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/help/http/Res.kt b/app/src/main/java/io/legado/app/help/http/Res.kt index b0ba9ed24..e54832b40 100644 --- a/app/src/main/java/io/legado/app/help/http/Res.kt +++ b/app/src/main/java/io/legado/app/help/http/Res.kt @@ -1,3 +1,6 @@ package io.legado.app.help.http -data class Res(val url: String, val body: String?) \ No newline at end of file +data class Res( + val url: String, + val body: String?, +) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt b/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt index 315dc881f..7138c8c20 100644 --- a/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt +++ b/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt @@ -32,6 +32,12 @@ interface HttpGetApi { @HeaderMap headers: Map ): Call + @GET + fun getByte( + @Url url: String, + @HeaderMap headers: Map + ): Call + @GET fun getMap( @Url url: String, @@ -39,6 +45,19 @@ interface HttpGetApi { @HeaderMap headers: Map ): Call + @GET + fun getMapByte( + @Url url: String, + @QueryMap(encoded = true) queryMap: Map, + @HeaderMap headers: Map + ): Call + + @GET + suspend fun getByteAsync( + @Url url: String, + @HeaderMap headers: Map + ): Response + @GET suspend fun getMapByteAsync( @Url url: String, diff --git a/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt b/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt index 9634b5b79..170894390 100644 --- a/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt +++ b/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt @@ -49,4 +49,12 @@ interface HttpPostApi { @FieldMap(encoded = true) fieldMap: Map, @HeaderMap headers: Map ): Response + + @POST + suspend fun postBodyByteAsync( + @Url url: String, + @Body body: RequestBody, + @HeaderMap headers: Map + ): Response + } diff --git a/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt b/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt index 4bbed5991..17104108b 100644 --- a/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt +++ b/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt @@ -1,11 +1,9 @@ package io.legado.app.help.permission -import android.os.Build import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import org.jetbrains.anko.startActivity -import java.util.ArrayList +import java.util.* class PermissionsCompat private constructor() { @@ -18,7 +16,7 @@ class PermissionsCompat private constructor() { companion object { // 检查权限, 如果已经拥有返回 true fun check(activity: AppCompatActivity, vararg permissions: String): Boolean { - var request: Request = Request(activity) + var request = Request(activity) var pers = ArrayList() pers.addAll(listOf(*permissions)) var data = request.getDeniedPermissions(pers.toTypedArray()) diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index c47951385..d1437ce7c 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -6,6 +6,7 @@ import androidx.documentfile.provider.DocumentFile import io.legado.app.App import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig +import io.legado.app.help.ThemeConfig import io.legado.app.help.coroutine.Coroutine import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO @@ -18,13 +19,25 @@ import java.util.concurrent.TimeUnit object Backup { val backupPath: String by lazy { - FileUtils.getDirFile(App.INSTANCE.filesDir, "backup").absolutePath + FileUtils.getFile(App.INSTANCE.filesDir, "backup").absolutePath } val backupFileNames by lazy { arrayOf( - "bookshelf.json", "bookGroup.json", "bookSource.json", "rssSource.json", - "rssStar.json", "replaceRule.json", ReadBookConfig.readConfigFileName, "config.xml" + "bookshelf.json", + "bookmark.json", + "bookGroup.json", + "bookSource.json", + "rssSource.json", + "rssStar.json", + "replaceRule.json", + "txtTocRule.json", + "readRecord.json", + "httpTTS.json", + ReadBookConfig.configFileName, + ReadBookConfig.shareConfigFileName, + ThemeConfig.configFileName, + "config.xml" ) } @@ -32,9 +45,7 @@ object Backup { val lastBackup = context.getPrefLong(PreferKey.lastBackup) if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) { Coroutine.async { - context.getPrefString(PreferKey.backupPath)?.let { - backup(context, it, true) - } + backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true) } } } @@ -44,13 +55,24 @@ object Backup { withContext(IO) { synchronized(this@Backup) { writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) + writeListToJson(App.db.bookmarkDao().all, "bookmark.json", backupPath) writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath) writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) - GSON.toJson(ReadBookConfig.configList)?.let { - FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) + writeListToJson(App.db.txtTocRule().all, "txtTocRule.json", backupPath) + writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath) + writeListToJson(App.db.httpTTSDao().all, "httpTTS.json", backupPath) + GSON.toJson(ReadBookConfig.configList).let { + FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.configFileName) + .writeText(it) + } + GSON.toJson(ReadBookConfig.shareConfig).let { + FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.shareConfigFileName) + } + GSON.toJson(ThemeConfig.configList).let { + FileUtils.createFileIfNotExist(backupPath + File.separator + ThemeConfig.configFileName) .writeText(it) } Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> @@ -71,7 +93,11 @@ object Backup { if (path.isContentPath()) { copyBackup(context, Uri.parse(path), isAuto) } else { - copyBackup(File(path), isAuto) + if (path.isEmpty()) { + copyBackup(context.getExternalFilesDir(null)!!, false) + } else { + copyBackup(File(path), isAuto) + } } } } @@ -95,7 +121,7 @@ object Backup { DocumentUtils.createFileIfNotExist( treeDoc, fileName, - subDirs = *arrayOf("auto") + subDirs = arrayOf("auto") )?.writeBytes(context, file.readBytes()) } else { treeDoc.findFile(fileName)?.delete() @@ -114,7 +140,7 @@ object Backup { if (file.exists()) { file.copyTo( if (isAuto) { - FileUtils.createFileIfNotExist(rootFile, fileName, "auto") + FileUtils.createFileIfNotExist(rootFile, "auto", fileName) } else { FileUtils.createFileIfNotExist(rootFile, fileName) }, true 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..a1741c845 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 @@ -5,114 +5,78 @@ import android.net.Uri 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,20 +100,9 @@ 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 { - if (it.isValid()){ - replaceRules.add(it) - } - } - } - App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) - return replaceRules.size + val rules = OldReplace.jsonToReplaceRules(json) + App.db.replaceRuleDao().insert(*rules.toTypedArray()) + return rules.size } } \ No newline at end of file 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..1b6a9a89e --- /dev/null +++ b/app/src/main/java/io/legado/app/help/storage/OldReplace.kt @@ -0,0 +1,46 @@ +package io.legado.app.help.storage + +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.utils.* + +object OldReplace { + + fun jsonToReplaceRules(json: String): List { + val replaceRules = mutableListOf() + val items: List> = Restore.jsonPath.parse(json).read("$") + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + jsonToReplaceRule(jsonItem.jsonString())?.let { + if (it.isValid()) { + replaceRules.add(it) + } + } + } + return replaceRules + } + + private 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 7696bb383..83084d5f3 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,18 +14,21 @@ 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?.searchUrl.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") ?: "" bookSourceGroup = jsonItem.readString("bookSourceGroup") loginUrl = jsonItem.readString("loginUrl") + bookSourceComment = jsonItem.readString("bookSourceComment") ?: "" bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") customOrder = jsonItem.readInt("serialNumber") ?: 0 header = uaToHeader(jsonItem.readString("httpUserAgent")) @@ -37,7 +40,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 +50,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 +60,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 +70,94 @@ 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, + replaceRegex = toNewRule(jsonItem.readString("ruleBookContentReplace")), 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.bookSourceComment = sourceAny.bookSourceComment + 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 bookSourceComment: String? = "", //书源注释 + 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 +180,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 +208,10 @@ object OldRule { } } if (allinone) { - newRule = "+" + newRule + newRule = "+$newRule" } if (reverse) { - newRule = "-" + newRule + newRule = "-$newRule" } return newRule } @@ -216,27 +282,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..18880822e 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 @@ -2,7 +2,6 @@ package io.legado.app.help.storage import android.content.Context import android.net.Uri -import androidx.appcompat.app.AppCompatDelegate import androidx.documentfile.provider.DocumentFile import com.jayway.jsonpath.Configuration import com.jayway.jsonpath.JsonPath @@ -10,22 +9,65 @@ import com.jayway.jsonpath.Option import com.jayway.jsonpath.ParseContext import io.legado.app.App import io.legado.app.BuildConfig +import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.* import io.legado.app.help.AppConfig import io.legado.app.help.LauncherIconHelp import io.legado.app.help.ReadBookConfig +import io.legado.app.help.ThemeConfig import io.legado.app.service.help.ReadBook -import io.legado.app.ui.book.read.page.ChapterProvider +import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext import org.jetbrains.anko.defaultSharedPreferences +import org.jetbrains.anko.toast import java.io.File object Restore { + private val ignoreConfigPath = FileUtils.getPath(App.INSTANCE.filesDir, "restoreIgnore.json") + val ignoreConfig: HashMap by lazy { + val file = FileUtils.createFileIfNotExist(ignoreConfigPath) + val json = file.readText() + GSON.fromJsonObject>(json) ?: hashMapOf() + } + + //忽略key + val ignoreKeys = arrayOf( + "readConfig", + PreferKey.themeMode, + PreferKey.bookshelfLayout, + PreferKey.showRss, + PreferKey.threadCount + ) + + //忽略标题 + val ignoreTitle = arrayOf( + App.INSTANCE.getString(R.string.read_config), + App.INSTANCE.getString(R.string.theme_mode), + App.INSTANCE.getString(R.string.bookshelf_layout), + App.INSTANCE.getString(R.string.show_rss), + App.INSTANCE.getString(R.string.thread_count) + ) + + //默认忽略keys + private val ignorePrefKeys = arrayOf( + PreferKey.versionCode, + PreferKey.defaultCover + ) + private val readPrefKeys = arrayOf( + PreferKey.readStyleSelect, + PreferKey.shareLayout, + PreferKey.pageAnim, + PreferKey.hideStatusBar, + PreferKey.hideNavigationBar, + PreferKey.bodyIndent, + PreferKey.autoReadSpeed + ) + val jsonPath: ParseContext by lazy { JsonPath.using( Configuration.builder() @@ -74,6 +116,9 @@ object Restore { fileToListT(path, "bookshelf.json")?.let { App.db.bookDao().insert(*it.toTypedArray()) } + fileToListT(path, "bookmark.json")?.let { + App.db.bookmarkDao().insert(*it.toTypedArray()) + } fileToListT(path, "bookGroup.json")?.let { App.db.bookGroupDao().insert(*it.toTypedArray()) } @@ -89,6 +134,26 @@ object Restore { fileToListT(path, "replaceRule.json")?.let { App.db.replaceRuleDao().insert(*it.toTypedArray()) } + fileToListT(path, "txtTocRule.json")?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + } + fileToListT(path, "readRecord.json")?.let { + it.forEach { readRecord -> + //判断是不是本机记录 + if (readRecord.androidId != App.androidId) { + App.db.readRecordDao().insert(readRecord) + } else { + val time = App.db.readRecordDao() + .getReadTime(readRecord.androidId, readRecord.bookName) + if (time == null || time < readRecord.readTime) { + App.db.readRecordDao().insert(readRecord) + } + } + } + } + fileToListT(path, "httpTTS.json")?.let { + App.db.httpTTSDao().insert(*it.toTypedArray()) + } } } @@ -96,28 +161,56 @@ object Restore { withContext(IO) { try { val file = - FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) - val configFile = - FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName) + FileUtils.createFileIfNotExist(path + File.separator + ThemeConfig.configFileName) if (file.exists()) { - file.copyTo(configFile, true) - ReadBookConfig.upConfig() + FileUtils.deleteFile(ThemeConfig.configFilePath) + file.copyTo(File(ThemeConfig.configFilePath)) + ThemeConfig.upConfig() } } catch (e: Exception) { e.printStackTrace() } - Preferences.getSharedPreferences(App.INSTANCE, path, "config")?.all?.map { + if (!ignoreReadConfig) { + try { + val file = + FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.configFileName) + if (file.exists()) { + FileUtils.deleteFile(ReadBookConfig.configFilePath) + file.copyTo(File(ReadBookConfig.configFilePath)) + ReadBookConfig.initConfigs() + } + } catch (e: Exception) { + e.printStackTrace() + } + try { + val file = + FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.shareConfigFileName) + if (file.exists()) { + FileUtils.deleteFile(ReadBookConfig.shareConfigFilePath) + file.copyTo(File(ReadBookConfig.shareConfigFilePath)) + ReadBookConfig.initShareConfig() + } + } catch (e: Exception) { + e.printStackTrace() + } + } + Preferences.getSharedPreferences(App.INSTANCE, path, "config")?.all?.let { map -> val edit = App.INSTANCE.defaultSharedPreferences.edit() - when (val value = it.value) { - is Int -> edit.putInt(it.key, value) - is Boolean -> edit.putBoolean(it.key, value) - is Long -> edit.putLong(it.key, value) - is Float -> edit.putFloat(it.key, value) - is String -> edit.putString(it.key, value) - else -> Unit + map.forEach { + if (keyIsNotIgnore(it.key)) { + when (val value = it.value) { + is Int -> edit.putInt(it.key, value) + is Boolean -> edit.putBoolean(it.key, value) + is Long -> edit.putLong(it.key, value) + is Float -> edit.putFloat(it.key, value) + is String -> edit.putString(it.key, value) + else -> Unit + } + } } - edit.putInt(PreferKey.versionCode, App.INSTANCE.versionCode) edit.apply() + AppConfig.replaceEnableDefault = + App.INSTANCE.getPrefBoolean(PreferKey.replaceEnableDefault, true) } ReadBookConfig.apply { styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) @@ -126,24 +219,51 @@ 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) } withContext(Main) { - if (AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) { - App.INSTANCE.applyDayNight() - } else if (!AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) { - App.INSTANCE.applyDayNight() - } else { - postEvent(EventBus.RECREATE, "true") - } + App.INSTANCE.toast(R.string.restore_success) if (!BuildConfig.DEBUG) { LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) } + LanguageUtils.setConfigurationOld(App.INSTANCE) + App.INSTANCE.applyDayNight() + postEvent(EventBus.SHOW_RSS, "") + postEvent(EventBus.RECREATE, "") + } + } + + private fun keyIsNotIgnore(key: String): Boolean { + return when { + ignorePrefKeys.contains(key) -> false + readPrefKeys.contains(key) && ignoreReadConfig -> false + PreferKey.themeMode == key && ignoreThemeMode -> false + PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false + PreferKey.showRss == key && ignoreShowRss -> false + PreferKey.threadCount == key && ignoreThreadCount -> false + else -> true } } + private val ignoreReadConfig: Boolean + get() = ignoreConfig["readConfig"] == true + private val ignoreThemeMode: Boolean + get() = ignoreConfig[PreferKey.themeMode] == true + private val ignoreBookshelfLayout: Boolean + get() = ignoreConfig[PreferKey.bookshelfLayout] == true + private val ignoreShowRss: Boolean + get() = ignoreConfig[PreferKey.showRss] == true + private val ignoreThreadCount: Boolean + get() = ignoreConfig[PreferKey.threadCount] == true + + fun saveIgnoreConfig() { + val json = GSON.toJson(ignoreConfig) + FileUtils.createFileIfNotExist(ignoreConfigPath).writeText(json) + } + private inline fun fileToListT(path: String, fileName: String): List? { try { val file = FileUtils.createFileIfNotExist(path + File.separator + fileName) diff --git a/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt b/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt index ddb4d6ef5..4e09e7c63 100644 --- a/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt +++ b/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt @@ -35,11 +35,11 @@ object SyncBookProgress { val json = file.readText() GSON.fromJsonArray(json)?.forEach { App.db.bookDao().upBookProgress( - it.bookUrl, - it.durChapterIndex, - it.durChapterPos, - it.durChapterTime, - it.durChapterTitle + bookUrl = it.bookUrl, + durChapterIndex = it.durChapterIndex, + durChapterPos = it.durChapterPos, + durChapterTime = it.durChapterTime, + durChapterTitle = it.durChapterTitle ) } } diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt index ad4c93a2a..e728c28de 100644 --- a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -4,8 +4,10 @@ import android.content.Context import android.os.Handler import android.os.Looper import io.legado.app.App +import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.help.coroutine.Coroutine +import io.legado.app.lib.dialogs.selector import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.utils.FileUtils @@ -15,7 +17,6 @@ import io.legado.app.utils.getPrefString import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext -import org.jetbrains.anko.selector import org.jetbrains.anko.toast import java.io.File import java.text.SimpleDateFormat @@ -28,16 +29,16 @@ object WebDavHelp { val rootWebDavUrl: String get() { - var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl) - if (url.isNullOrEmpty()) { - url = defaultWebDavUrl - } + var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl) + if (url.isNullOrEmpty()) { + url = defaultWebDavUrl + } if (!url.endsWith("/")) url = "${url}/" if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCreateDir, true)) { url = "${url}legado/" } - return url - } + return url + } fun initWebDav(): Boolean { val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount) @@ -50,42 +51,41 @@ object WebDavHelp { return false } + @Throws(Exception::class) private fun getWebDavFileNames(): ArrayList { val url = rootWebDavUrl val names = arrayListOf() if (initWebDav()) { - try { - var files = WebDav(url).listFiles() - files = files.reversed() - for (index: Int in 0 until min(10, files.size)) { - files[index].displayName?.let { - names.add(it) - } + var files = WebDav(url).listFiles() + files = files.reversed() + for (index: Int in 0 until min(10, files.size)) { + files[index].displayName?.let { + names.add(it) } - } catch (e: Exception) { - return names } } return names } - suspend fun showRestoreDialog(context: Context, restoreSuccess: () -> Unit): Boolean { + suspend fun showRestoreDialog(context: Context) { val names = withContext(IO) { getWebDavFileNames() } - return if (names.isNotEmpty()) { + if (names.isNotEmpty()) { withContext(Main) { - context.selector(title = "选择恢复文件", items = names) { _, index -> + context.selector( + title = context.getString(R.string.select_restore_file), + items = names + ) { _, index -> if (index in 0 until names.size) { - restoreWebDav(names[index], restoreSuccess) + restoreWebDav(names[index]) } } } - true } else { - false + throw Exception("Web dav no back up file") } } - private fun restoreWebDav(name: String, success: () -> Unit) { + private fun restoreWebDav(name: String) { Coroutine.async { rootWebDavUrl.let { if (name == SyncBookProgress.fileName) { @@ -99,8 +99,8 @@ object WebDavHelp { Restore.restoreConfig() } } - }.onSuccess { - success.invoke() + }.onError { + App.INSTANCE.toast("WebDavError:${it.localizedMessage}") } } diff --git a/app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt b/app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt index ae477acd6..b1f0f494d 100644 --- a/app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt +++ b/app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt @@ -75,7 +75,22 @@ interface AlertBuilder { fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) fun items(items: List, onItemSelected: (dialog: DialogInterface, index: Int) -> Unit) - fun items(items: List, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) + fun items( + items: List, + onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit + ) + + fun multiChoiceItems( + items: Array, + checkedItems: BooleanArray, + onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit + ) + + fun singleChoiceItems( + items: Array, + checkedItem: Int = 0, + onClick: ((dialog: DialogInterface, which: Int) -> Unit)? = null + ) fun build(): D fun show(): D diff --git a/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt b/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt index 52711a17a..9bfcab298 100644 --- a/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt +++ b/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt @@ -105,12 +105,35 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder items(items: List, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) { + override fun items( + items: List, + onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit + ) { builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which -> onItemSelected(dialog, items[which], which) } } + override fun multiChoiceItems( + items: Array, + checkedItems: BooleanArray, + onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit + ) { + builder.setMultiChoiceItems(items, checkedItems) { dialog, which, isChecked -> + onClick(dialog, which, isChecked) + } + } + + override fun singleChoiceItems( + items: Array, + checkedItem: Int, + onClick: ((dialog: DialogInterface, which: Int) -> Unit)? + ) { + builder.setSingleChoiceItems(items, checkedItem) { dialog, which -> + onClick?.invoke(dialog, which) + } + } + override fun build(): AlertDialog = builder.create() override fun show(): AlertDialog = builder.show() diff --git a/app/src/main/java/io/legado/app/lib/dialogs/AndroidSelectors.kt b/app/src/main/java/io/legado/app/lib/dialogs/AndroidSelectors.kt index 17ed0dea8..1eb5dbff9 100644 --- a/app/src/main/java/io/legado/app/lib/dialogs/AndroidSelectors.kt +++ b/app/src/main/java/io/legado/app/lib/dialogs/AndroidSelectors.kt @@ -21,6 +21,7 @@ package io.legado.app.lib.dialogs import android.content.Context import android.content.DialogInterface import androidx.fragment.app.Fragment +import io.legado.app.utils.applyTint inline fun Fragment.selector( title: CharSequence? = null, @@ -38,6 +39,6 @@ fun Context.selector( this.title = title } items(items, onClick) - show() + show().applyTint() } } 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..f69c5d40a 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 @@ -5,9 +5,11 @@ import android.app.Activity import android.app.ActivityManager import android.content.Context import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.os.Build import android.view.View import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR +import android.view.Window import android.widget.EdgeEffect import android.widget.ScrollView import androidx.annotation.ColorInt @@ -15,8 +17,11 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.ViewPager import com.google.android.material.bottomnavigation.BottomNavigationView +import io.legado.app.App import io.legado.app.R import io.legado.app.help.AppConfig +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.dp import io.legado.app.utils.getCompatColor import kotlinx.android.synthetic.main.activity_main.view.* import org.jetbrains.anko.backgroundColor @@ -59,16 +64,16 @@ object ATH { } else { activity.window.statusBarColor = color } - setLightStatusBarAuto(activity, color) + setLightStatusBarAuto(activity.window, color) } - fun setLightStatusBarAuto(activity: Activity, bgColor: Int) { - setLightStatusBar(activity, ColorUtils.isColorLight(bgColor)) + fun setLightStatusBarAuto(window: Window, bgColor: Int) { + setLightStatusBar(window, ColorUtils.isColorLight(bgColor)) } - fun setLightStatusBar(activity: Activity, enabled: Boolean) { + fun setLightStatusBar(window: Window, enabled: Boolean) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val decorView = activity.window.decorView + val decorView = window.decorView val systemUiVisibility = decorView.systemUiVisibility if (enabled) { decorView.systemUiVisibility = @@ -93,18 +98,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)) } } @@ -143,6 +143,7 @@ object ATH { } fun setAlertDialogTint(dialog: AlertDialog): AlertDialog { + dialog.window?.setBackgroundDrawable(getDialogBackground()) val colorStateList = Selector.colorBuild() .setDefaultColor(ThemeStore.accentColor(dialog.context)) .setPressedColor(ColorUtils.darkenColor(ThemeStore.accentColor(dialog.context))) @@ -197,11 +198,14 @@ object ATH { //**************************************************************Directly*************************************************************// - fun applyBottomNavigationColor(bottomBar: BottomNavigationView?) { - bottomBar?.apply { - setBackgroundColor(ThemeStore.bottomBackground(context)) + fun applyBottomNavigationColor(bottomBar: BottomNavigationView) { + bottomBar.apply { + val bgColor = context.bottomBackground + setBackgroundColor(bgColor) + val textIsDark = ColorUtils.isColorLight(bgColor) + val textColor = context.getSecondaryTextColor(textIsDark) val colorStateList = Selector.colorBuild() - .setDefaultColor(context.getCompatColor(R.color.btn_bg_press_tp)) + .setDefaultColor(textColor) .setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create() itemIconTintList = colorStateList itemTextColor = colorStateList @@ -232,6 +236,13 @@ object ATH { } } + fun getDialogBackground(): GradientDrawable { + val background = GradientDrawable() + background.cornerRadius = 3F.dp + background.setColor(App.INSTANCE.backgroundColor) + return background + } + private val DEFAULT_EFFECT_FACTORY = object : RecyclerView.EdgeEffectFactory() { override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { val edgeEffect = super.createEdgeEffect(view, direction) diff --git a/app/src/main/java/io/legado/app/lib/theme/ColorUtils.kt b/app/src/main/java/io/legado/app/lib/theme/ColorUtils.kt deleted file mode 100644 index b4a17748e..000000000 --- a/app/src/main/java/io/legado/app/lib/theme/ColorUtils.kt +++ /dev/null @@ -1,156 +0,0 @@ -package io.legado.app.lib.theme - -import android.graphics.Color -import androidx.annotation.ColorInt -import androidx.annotation.FloatRange -import java.util.* - -object ColorUtils { - - fun intToString(intColor: Int): String { - return String.format("#%06X", 0xFFFFFF and intColor) - } - - - fun stripAlpha(@ColorInt color: Int): Int { - return -0x1000000 or color - } - - @ColorInt - fun shiftColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 2.0) by: Float): Int { - if (by == 1f) return color - val alpha = Color.alpha(color) - val hsv = FloatArray(3) - Color.colorToHSV(color, hsv) - hsv[2] *= by // value component - return (alpha shl 24) + (0x00ffffff and Color.HSVToColor(hsv)) - } - - @ColorInt - fun darkenColor(@ColorInt color: Int): Int { - return shiftColor(color, 0.9f) - } - - @ColorInt - fun lightenColor(@ColorInt color: Int): Int { - return shiftColor(color, 1.1f) - } - - fun isColorLight(@ColorInt color: Int): Boolean { - val darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 - return darkness < 0.4 - } - - @ColorInt - fun invertColor(@ColorInt color: Int): Int { - val r = 255 - Color.red(color) - val g = 255 - Color.green(color) - val b = 255 - Color.blue(color) - return Color.argb(Color.alpha(color), r, g, b) - } - - @ColorInt - fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int { - val alpha = Math.round(Color.alpha(color) * factor) - val red = Color.red(color) - val green = Color.green(color) - val blue = Color.blue(color) - return Color.argb(alpha, red, green, blue) - } - - @ColorInt - fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int { - val a = Math.min(255, Math.max(0, (alpha * 255).toInt())) shl 24 - val rgb = 0x00ffffff and baseColor - return a + rgb - } - - /** - * Taken from CollapsingToolbarLayout's CollapsingTextHelper class. - */ - fun blendColors(color1: Int, color2: Int, @FloatRange(from = 0.0, to = 1.0) ratio: Float): Int { - val inverseRatio = 1f - ratio - val a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio - val r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio - val g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio - val b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio - return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt()) - } - - /** - * 按条件的到随机颜色 - * - * @param alpha 透明 - * @param lower 下边界 - * @param upper 上边界 - * @return 颜色值 - */ - fun getRandomColor(alpha: Int, lower: Int, upper: Int): Int { - return RandomColor(alpha, lower, upper).color - } - - /** - * @return 获取随机色 - */ - fun getRandomColor(): Int { - return RandomColor(255, 80, 200).color - } - - - /** - * 随机颜色 - */ - class RandomColor(alpha: Int, lower: Int, upper: Int) { - private var alpha: Int = 0 - private var lower: Int = 0 - private var upper: Int = 0 - - //随机数是前闭 后开 - val color: Int - get() { - val red = getLower() + Random().nextInt(getUpper() - getLower() + 1) - val green = getLower() + Random().nextInt(getUpper() - getLower() + 1) - val blue = getLower() + Random().nextInt(getUpper() - getLower() + 1) - - return Color.argb(getAlpha(), red, green, blue) - } - - init { - require(upper > lower) { "must be lower < upper" } - setAlpha(alpha) - setLower(lower) - setUpper(upper) - } - - private fun getAlpha(): Int { - return alpha - } - - private fun setAlpha(alpha: Int) { - var alpha1 = alpha - if (alpha1 > 255) alpha1 = 255 - if (alpha1 < 0) alpha1 = 0 - this.alpha = alpha1 - } - - private fun getLower(): Int { - return lower - } - - private fun setLower(lower: Int) { - var lower1 = lower - if (lower1 < 0) lower1 = 0 - this.lower = lower1 - } - - private fun getUpper(): Int { - return upper - } - - private fun setUpper(upper: Int) { - var upper1 = upper - if (upper1 > 255) upper1 = 255 - this.upper = upper1 - } - } -} diff --git a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt index bc898d065..018e3dd60 100644 --- a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt +++ b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt @@ -6,6 +6,7 @@ import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import io.legado.app.R +import io.legado.app.utils.ColorUtils /** * @author Karim Abou Zeid (kabouzeid) 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..321d2a8f0 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,7 +9,9 @@ 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 +import io.legado.app.utils.ColorUtils /** * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) @@ -194,7 +196,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 +214,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 +238,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 +282,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 +299,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/lib/theme/TintHelper.kt b/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt index a54046dd3..76313cd46 100644 --- a/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt +++ b/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt @@ -18,6 +18,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import com.google.android.material.floatingactionbutton.FloatingActionButton import io.legado.app.R +import io.legado.app.utils.ColorUtils /** * @author afollestad, plusCubed diff --git a/app/src/main/java/io/legado/app/lib/theme/view/ATERadioNoButton.kt b/app/src/main/java/io/legado/app/lib/theme/view/ATERadioNoButton.kt index 12e93ec4f..1b6731098 100644 --- a/app/src/main/java/io/legado/app/lib/theme/view/ATERadioNoButton.kt +++ b/app/src/main/java/io/legado/app/lib/theme/view/ATERadioNoButton.kt @@ -5,7 +5,10 @@ import android.util.AttributeSet import androidx.appcompat.widget.AppCompatRadioButton import io.legado.app.R import io.legado.app.lib.theme.Selector -import io.legado.app.lib.theme.ThemeStore +import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.utils.ColorUtils import io.legado.app.utils.dp import io.legado.app.utils.getCompatColor @@ -15,13 +18,54 @@ import io.legado.app.utils.getCompatColor class ATERadioNoButton(context: Context, attrs: AttributeSet) : AppCompatRadioButton(context, attrs) { + private val isBottomBackground: Boolean + init { - background = Selector.shapeBuild() - .setCornerRadius(2.dp) - .setStrokeWidth(2.dp) - .setCheckedBgColor(ThemeStore.accentColor(context)) - .setCheckedStrokeColor(ThemeStore.accentColor(context)) - .setDefaultStrokeColor(context.getCompatColor(R.color.tv_text_default)) - .create() + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ATERadioNoButton) + isBottomBackground = + typedArray.getBoolean(R.styleable.ATERadioNoButton_isBottomBackground, false) + typedArray.recycle() + initTheme() } + + private fun initTheme() { + when { + isInEditMode -> Unit + isBottomBackground -> { + val isLight = ColorUtils.isColorLight(context.bottomBackground) + val textColor = context.getPrimaryTextColor(isLight) + background = Selector.shapeBuild() + .setCornerRadius(2.dp) + .setStrokeWidth(2.dp) + .setCheckedBgColor(context.accentColor) + .setCheckedStrokeColor(context.accentColor) + .setDefaultStrokeColor(textColor) + .create() + setTextColor( + Selector.colorBuild() + .setDefaultColor(textColor) + .setCheckedColor(context.getPrimaryTextColor(ColorUtils.isColorLight(context.accentColor))) + .create() + ) + } + else -> { + val textColor = context.getCompatColor(R.color.primaryText) + background = Selector.shapeBuild() + .setCornerRadius(2.dp) + .setStrokeWidth(2.dp) + .setCheckedBgColor(context.accentColor) + .setCheckedStrokeColor(context.accentColor) + .setDefaultStrokeColor(textColor) + .create() + setTextColor( + Selector.colorBuild() + .setDefaultColor(textColor) + .setCheckedColor(context.getPrimaryTextColor(ColorUtils.isColorLight(context.accentColor))) + .create() + ) + } + } + + } + } diff --git a/app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt b/app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt index ceaec7f21..f121c35b0 100644 --- a/app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt +++ b/app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt @@ -12,7 +12,10 @@ import io.legado.app.lib.theme.accentColor class ATESwitch(context: Context, attrs: AttributeSet) : SwitchCompat(context, attrs) { init { - ATH.setTint(this, context.accentColor) + if (!isInEditMode) { + ATH.setTint(this, context.accentColor) + } + } } diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index 47aa59344..03fc0d974 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt @@ -3,12 +3,7 @@ package io.legado.app.lib.webdav import io.legado.app.help.http.HttpHelper import io.legado.app.lib.webdav.http.Handler import io.legado.app.lib.webdav.http.HttpAuth -import okhttp3.Credentials -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.Request -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response +import okhttp3.* import org.jsoup.Jsoup import java.io.File import java.io.IOException @@ -19,8 +14,7 @@ import java.net.URL import java.net.URLEncoder import java.util.* -class WebDav @Throws(MalformedURLException::class) -constructor(urlStr: String) { +class WebDav(urlStr: String) { companion object { // 指定返回哪些属性 private const val DIR = @@ -82,7 +76,7 @@ constructor(urlStr: String) { this.exists = false return false } - response.body?.let { + response.body()?.let { if (it.string().isNotEmpty()) { return true } @@ -102,7 +96,7 @@ constructor(urlStr: String) { fun listFiles(propsList: ArrayList = ArrayList()): List { propFindResponse(propsList)?.let { response -> if (response.isSuccessful) { - response.body?.let { body -> + response.body()?.let { body -> return parseDir(body.string()) } } @@ -127,7 +121,10 @@ constructor(urlStr: String) { .url(url) // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 - .method("PROPFIND", requestPropsStr.toRequestBody("text/plain".toMediaTypeOrNull())) + .method( + "PROPFIND", + RequestBody.create(MediaType.parse("text/plain"), requestPropsStr) + ) HttpAuth.auth?.let { request.header( @@ -205,9 +202,9 @@ constructor(urlStr: String) { fun upload(localPath: String, contentType: String? = null): Boolean { val file = File(localPath) if (!file.exists()) return false - val mediaType = contentType?.toMediaTypeOrNull() + val mediaType = contentType?.let { MediaType.parse(it) } // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 - val fileBody = file.asRequestBody(mediaType) + val fileBody = RequestBody.create(mediaType, file) httpUrl?.let { val request = Request.Builder() .url(it) @@ -241,7 +238,7 @@ constructor(urlStr: String) { request.header("Authorization", Credentials.basic(it.user, it.pass)) } try { - return HttpHelper.client.newCall(request.build()).execute().body?.byteStream() + return HttpHelper.client.newCall(request.build()).execute().body()?.byteStream() } catch (e: IOException) { e.printStackTrace() } catch (e: IllegalArgumentException) { diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 51ae12afd..4e419bef4 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -1,13 +1,13 @@ package io.legado.app.model import android.annotation.SuppressLint -import io.legado.app.data.entities.Book -import io.legado.app.data.entities.BookChapter -import io.legado.app.data.entities.RssArticle -import io.legado.app.data.entities.RssSource +import io.legado.app.data.entities.* import io.legado.app.help.coroutine.CompositeCoroutine +import io.legado.app.model.rss.Rss +import io.legado.app.model.webBook.WebBook import io.legado.app.utils.htmlFormat import io.legado.app.utils.isAbsUrl +import io.legado.app.utils.msg import java.text.SimpleDateFormat import java.util.* @@ -54,7 +54,8 @@ object Debug { cancelDebug() debugSource = rssSource.sourceUrl log(debugSource, "︾开始解析") - Rss.getArticles(rssSource, null) + val sort = rssSource.sortUrls().entries.first() + Rss.getArticles(sort.key, sort.value, rssSource, 1) .onSuccess { if (it.articles.isEmpty()) { log(debugSource, "⇒列表页解析成功,为空") @@ -76,7 +77,7 @@ object Debug { } } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } } @@ -88,7 +89,7 @@ object Debug { log(debugSource, "︽内容页解析完成", state = 1000) } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } } @@ -136,7 +137,8 @@ object Debug { private fun exploreDebug(webBook: WebBook, url: String) { log(debugSource, "︾开始解析发现页") - val explore = webBook.exploreBook(url, 1) + val variableBook = SearchBook() + val explore = webBook.exploreBook(url, 1, variableBook) .onSuccess { exploreBooks -> if (exploreBooks.isNotEmpty()) { log(debugSource, "︽发现页解析完成") @@ -147,18 +149,15 @@ object Debug { } } .onError { - log( - debugSource, - it.localizedMessage, - state = -1 - ) + log(debugSource, it.msg, state = -1) } tasks.add(explore) } private fun searchDebug(webBook: WebBook, key: String) { log(debugSource, "︾开始解析搜索页") - val search = webBook.searchBook(key, 1) + val variableBook = SearchBook() + val search = webBook.searchBook(key, 1, variableBook) .onSuccess { searchBooks -> if (searchBooks.isNotEmpty()) { log(debugSource, "︽搜索页解析完成") @@ -169,7 +168,7 @@ object Debug { } } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } tasks.add(search) } @@ -183,7 +182,7 @@ object Debug { tocDebug(webBook, book) } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } tasks.add(info) } @@ -202,7 +201,7 @@ object Debug { } } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } tasks.add(chapterList) } @@ -219,7 +218,7 @@ object Debug { log(debugSource, "︽正文页解析完成", state = 1000) } .onError { - log(debugSource, it.localizedMessage, state = -1) + log(debugSource, it.msg, state = -1) } tasks.add(content) } diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt deleted file mode 100644 index 1f6cac065..000000000 --- a/app/src/main/java/io/legado/app/model/SearchBookModel.kt +++ /dev/null @@ -1,93 +0,0 @@ -package io.legado.app.model - -import io.legado.app.App -import io.legado.app.data.entities.SearchBook -import io.legado.app.help.AppConfig -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.utils.getPrefString -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.asCoroutineDispatcher -import java.util.concurrent.Executors - -class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var mSearchId = System.currentTimeMillis() - private var searchPage = 1 - private var searchKey: String = "" - private var task: Coroutine<*>? = null - - private fun initSearchPool() { - searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - } - - fun search(searchId: Long, key: String) { - if (searchId != mSearchId) { - task?.cancel() - searchPool.close() - initSearchPool() - mSearchId = searchId - searchPage = 1 - if (key.isEmpty()) { - return - } else { - this.searchKey = key - } - } else { - searchPage++ - } - task = Coroutine.async(scope, searchPool) { - val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: "" - val bookSourceList = if (searchGroup.isBlank()) { - App.db.bookSourceDao().allEnabled - } else { - App.db.bookSourceDao().getEnabledByGroup(searchGroup) - } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this, - context = searchPool - ) - .timeout(30000L) - .onSuccess(IO) { - if (searchId == mSearchId) { - callBack.onSearchSuccess(it) - } - } - } - }.onStart { - callBack.onSearchStart() - } - - task?.invokeOnCompletion { - if (searchId == mSearchId) { - callBack.onSearchFinish() - } - } - } - - fun cancelSearch() { - task?.cancel() - mSearchId = 0 - callBack.onSearchCancel() - } - - fun close() { - task?.cancel() - mSearchId = 0 - searchPool.close() - } - - interface CallBack { - fun onSearchStart() - fun onSearchSuccess(searchBooks: ArrayList) - fun onSearchFinish() - fun onSearchCancel() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt index 7e49ac677..bf00f0d43 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt @@ -377,8 +377,9 @@ class AnalyzeByJSoup { textS.add(element.ownText()) } "html" -> { - elements.select("script, style").remove() - val html = elements.html() + elements.select("script").remove() + elements.select("style").remove() + val html = elements.outerHtml() textS.add(html) } "all" -> textS.add(elements.outerHtml()) 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/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index 9b1e7d3b0..f9ed9230f 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -324,17 +324,37 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { */ private fun replaceRegex(result: String, rule: SourceRule): String { var vResult = result - if (rule.replaceRegex.isNotEmpty()) { + val stringBuffer = StringBuffer() + val evalMatcher = replacePattern.matcher(rule.replaceRegex) + while (evalMatcher.find()) { + val jsEval = evalMatcher.group().let { + if (it.startsWith("@get:", true)) { + get(it.substring(6, it.lastIndex)) + } else { + evalJS(it.substring(2, it.length - 2), result) + } + } ?: "" + if (jsEval is String) { + evalMatcher.appendReplacement(stringBuffer, jsEval) + } else if (jsEval is Double && jsEval % 1.0 == 0.0) { + evalMatcher.appendReplacement(stringBuffer, String.format("%.0f", jsEval)) + } else { + evalMatcher.appendReplacement(stringBuffer, jsEval.toString()) + } + } + evalMatcher.appendTail(stringBuffer) + val replaceRegex = stringBuffer.toString() + if (replaceRegex.isNotEmpty()) { vResult = if (rule.replaceFirst) { - val pattern = Pattern.compile(rule.replaceRegex) + val pattern = Pattern.compile(replaceRegex) val matcher = pattern.matcher(vResult) if (matcher.find()) { - matcher.group(0)!!.replaceFirst(rule.replaceRegex.toRegex(), rule.replacement) + matcher.group(0)!!.replaceFirst(replaceRegex.toRegex(), rule.replacement) } else { "" } } else { - vResult.replace(rule.replaceRegex.toRegex(), rule.replacement) + vResult.replace(replaceRegex.toRegex(), rule.replacement) } } return vResult @@ -376,8 +396,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { val jsMatcher = JS_PATTERN.matcher(vRuleStr) while (jsMatcher.find()) { if (jsMatcher.start() > start) { - tmp = vRuleStr.substring(start, jsMatcher.start()).replace("\n", "") - .trim { it <= ' ' } + tmp = vRuleStr.substring(start, jsMatcher.start()).trim { it <= ' ' } if (!TextUtils.isEmpty(tmp)) { ruleList.add(SourceRule(tmp, mMode)) } @@ -386,7 +405,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { start = jsMatcher.end() } if (vRuleStr.length > start) { - tmp = vRuleStr.substring(start).replace("\n", "").trim { it <= ' ' } + tmp = vRuleStr.substring(start).trim { it <= ' ' } if (!TextUtils.isEmpty(tmp)) { ruleList.add(SourceRule(tmp, mMode)) } @@ -581,6 +600,14 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { } fun get(key: String): String { + when (key) { + "bookName" -> book?.let { + return it.name + } + "title" -> chapter?.let { + return it.title + } + } return chapter?.variableMap?.get(key) ?: book?.variableMap?.get(key) ?: "" @@ -589,19 +616,15 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { /** * 执行JS */ - @Throws(Exception::class) private fun evalJS(jsStr: String, result: Any?): Any? { - try { - val bindings = SimpleBindings() - bindings["java"] = this - bindings["book"] = book - bindings["result"] = result - bindings["baseUrl"] = baseUrl - return SCRIPT_ENGINE.eval(jsStr, bindings) - } catch (e: Exception) { - e.printStackTrace() - throw e - } + val bindings = SimpleBindings() + bindings["java"] = this + bindings["book"] = book + bindings["result"] = result + bindings["baseUrl"] = baseUrl + bindings["chapter"] = chapter + bindings["title"] = chapter?.title + return SCRIPT_ENGINE.eval(jsStr, bindings) } /** @@ -609,7 +632,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { */ override fun ajax(urlStr: String): String? { return try { - val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book) + val analyzeUrl = AnalyzeUrl(urlStr, baseUrl = baseUrl, book = book) val call = analyzeUrl.getResponse(urlStr) val response = call.execute() response.body() @@ -641,6 +664,10 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { "@get:\\{[^}]+?\\}|\\{\\{[\\w\\W]*?\\}\\}|\\$\\d{1,2}", Pattern.CASE_INSENSITIVE ) + private val replacePattern = Pattern.compile( + "@get:\\{[^}]+?\\}|\\{\\{[\\w\\W]*?\\}\\}", + Pattern.CASE_INSENSITIVE + ) } } 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 7b1488c96..067690afb 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 @@ -3,7 +3,11 @@ package io.legado.app.model.analyzeRule import android.annotation.SuppressLint import android.text.TextUtils import androidx.annotation.Keep +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.LazyHeaders import io.legado.app.constant.AppConst.SCRIPT_ENGINE +import io.legado.app.constant.AppConst.UA_NAME +import io.legado.app.constant.AppConst.userAgent import io.legado.app.constant.AppPattern.EXP_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.data.entities.BaseBook @@ -13,16 +17,14 @@ import io.legado.app.help.http.api.HttpGetApi import io.legado.app.help.http.api.HttpPostApi import io.legado.app.utils.* import okhttp3.FormBody -import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MediaType import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.toRequestBody import retrofit2.Call import java.net.URLEncoder import java.util.* import java.util.regex.Pattern import javax.script.SimpleBindings - /** * Created by GKF on 2018/1/24. * 搜索URL规则解析 @@ -33,42 +35,57 @@ class AnalyzeUrl( var ruleUrl: String, key: String? = null, page: Int? = null, + speakText: String? = null, + speakSpeed: Int? = null, headerMapF: Map? = null, baseUrl: String? = null, - book: BaseBook? = null, - var useWebView: Boolean = false + val book: BaseBook? = null, + var useWebView: Boolean = false, ) : JsExtensions { companion object { private val pagePattern = Pattern.compile("<(.*?)>") - private val jsonType = "application/json; charset=utf-8".toMediaTypeOrNull() + private val jsonType = MediaType.parse("application/json; charset=utf-8") } private var baseUrl: String = "" lateinit var url: String private set - var path: String? = null - private set + private lateinit var urlHasQuery: String val headerMap = HashMap() 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 + private val splitUrlRegex = Regex(",\\s*(?=\\{)") + private var proxy: String? = null init { baseUrl?.let { - this.baseUrl = it.split(",[^\\{]*".toRegex(), 1)[0] + this.baseUrl = it.split(splitUrlRegex, 1)[0] + } + headerMapF?.let { + headerMap.putAll(it) + if (it.containsKey("proxy")) { + proxy = it["proxy"] + headerMap.remove("proxy") + } } - headerMapF?.let { headerMap.putAll(it) } //替换参数 - analyzeJs(key, page, book) - replaceKeyPageJs(key, page, book) + analyzeJs(key, page, speakText, speakSpeed, book) + replaceKeyPageJs(key, page, speakText, speakSpeed, book) //处理URL initUrl() } - private fun analyzeJs(key: String?, page: Int?, book: BaseBook?) { + private fun analyzeJs( + key: String?, + page: Int?, + speakText: String?, + speakSpeed: Int?, + book: BaseBook?, + ) { val ruleList = arrayListOf() var start = 0 var tmp: String @@ -95,11 +112,13 @@ class AnalyzeUrl( when { ruleStr.startsWith("") -> { ruleStr = ruleStr.substring(4, ruleStr.lastIndexOf("<")) - ruleUrl = evalJS(ruleStr, ruleUrl, page, key, book) as String + ruleUrl = + evalJS(ruleStr, ruleUrl, page, key, speakText, speakSpeed, book) as String } ruleStr.startsWith("@js", true) -> { ruleStr = ruleStr.substring(4) - ruleUrl = evalJS(ruleStr, ruleUrl, page, key, book) as String + ruleUrl = + evalJS(ruleStr, ruleUrl, page, key, speakText, speakSpeed, book) as String } else -> ruleUrl = ruleStr.replace("@result", ruleUrl) } @@ -109,7 +128,13 @@ class AnalyzeUrl( /** * 替换关键字,页数,JS */ - private fun replaceKeyPageJs(key: String?, page: Int?, book: BaseBook?) { + private fun replaceKeyPageJs( + key: String?, + page: Int?, + speakText: String?, + speakSpeed: Int?, + book: BaseBook?, + ) { //page page?.let { val matcher = pagePattern.matcher(ruleUrl) @@ -125,16 +150,20 @@ class AnalyzeUrl( //js if (ruleUrl.contains("{{") && ruleUrl.contains("}}")) { var jsEval: Any - val sb = StringBuffer(ruleUrl.length) + val sb = StringBuffer() val simpleBindings = SimpleBindings() simpleBindings["java"] = this simpleBindings["baseUrl"] = baseUrl simpleBindings["page"] = page simpleBindings["key"] = key + simpleBindings["speakText"] = speakText + simpleBindings["speakSpeed"] = speakSpeed simpleBindings["book"] = book val expMatcher = EXP_PATTERN.matcher(ruleUrl) while (expMatcher.find()) { - jsEval = SCRIPT_ENGINE.eval(expMatcher.group(1), simpleBindings) + jsEval = expMatcher.group(1)?.let { + SCRIPT_ENGINE.eval(it, simpleBindings) + } ?: "" if (jsEval is String) { expMatcher.appendReplacement(sb, jsEval) } else if (jsEval is Double && jsEval % 1.0 == 0.0) { @@ -152,21 +181,38 @@ class AnalyzeUrl( * 处理URL */ private fun initUrl() { - var urlArray = ruleUrl.split(",[^\\{]*".toRegex(), 2) + var urlArray = ruleUrl.split(splitUrlRegex, 2) url = urlArray[0] + urlHasQuery = urlArray[0] NetworkUtils.getBaseUrl(url)?.let { 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<*, *>) { + headers.forEach { entry -> + headerMap[entry.key.toString()] = entry.value.toString() + } + } else if (headers is String) { + GSON.fromJsonObject>(headers) + ?.let { headerMap.putAll(it) } + } + } + headerMap[UA_NAME] = headerMap[UA_NAME] ?: userAgent + 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) { @@ -180,24 +226,22 @@ class AnalyzeUrl( } } RequestMethod.POST -> { - bodyTxt?.let { + body?.let { if (it.isJson()) { - body = it.toRequestBody(jsonType) + requestBody = RequestBody.create(jsonType, it) } else { analyzeFields(it) } } ?: let { - body = FormBody.Builder().build() + requestBody = FormBody.Builder().build() } } } } - /** * 解析QueryMap */ - @Throws(Exception::class) private fun analyzeFields(fieldsTxt: String) { queryStr = fieldsTxt val queryS = fieldsTxt.splitNotBlank("&") @@ -221,25 +265,36 @@ class AnalyzeUrl( /** * 执行JS */ - @Throws(Exception::class) private fun evalJS( jsStr: String, result: Any?, page: Int?, key: String?, - book: BaseBook? + speakText: String?, + speakSpeed: Int?, + book: BaseBook?, ): Any { val bindings = SimpleBindings() bindings["java"] = this bindings["page"] = page bindings["key"] = key + bindings["speakText"] = speakText + bindings["speakSpeed"] = speakSpeed bindings["book"] = book bindings["result"] = result bindings["baseUrl"] = baseUrl return SCRIPT_ENGINE.eval(jsStr, bindings) } - @Throws(Exception::class) + fun put(key: String, value: String): String { + book?.putVariable(key, value) + return value + } + + fun get(key: String): String { + return book?.variableMap?.get(key) ?: "" + } + fun getResponse(tag: String): Call { val cookie = CookieStore.getCookie(tag) if (cookie.isNotEmpty()) { @@ -254,7 +309,7 @@ class AnalyzeUrl( } else { HttpHelper .getApiService(baseUrl, charset) - .postBody(url, body!!, headerMap) + .postBody(url, requestBody!!, headerMap) } } fieldMap.isEmpty() -> HttpHelper @@ -266,11 +321,10 @@ class AnalyzeUrl( } } - @Throws(Exception::class) suspend fun getResponseAwait( tag: String, jsStr: String? = null, - sourceRegex: String? = null + sourceRegex: String? = null, ): Res { if (useWebView) { val params = AjaxWebView.AjaxParams(url) @@ -278,7 +332,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) } @@ -289,23 +343,111 @@ class AnalyzeUrl( val res = when { method == RequestMethod.POST -> { if (fieldMap.isNotEmpty()) { + if (proxy == null) { + HttpHelper + .getApiService(baseUrl, charset) + .postMapAsync(url, fieldMap, headerMap) + } else { + HttpHelper + .getApiServiceWithProxy(baseUrl, charset, proxy) + .postMapAsync(url, fieldMap, headerMap) + } + } else { + if (proxy == null) { + HttpHelper + .getApiService(baseUrl, charset) + .postBodyAsync(url, requestBody!!, headerMap) + } else { + HttpHelper + .getApiServiceWithProxy(baseUrl, charset, proxy) + .postBodyAsync(url, requestBody!!, headerMap) + } + } + } + fieldMap.isEmpty() -> { + if (proxy == null) { HttpHelper - .getApiService(baseUrl, charset) - .postMapAsync(url, fieldMap, headerMap) + .getApiService(baseUrl, charset) + .getAsync(url, headerMap) + } else { HttpHelper - .getApiService(baseUrl, charset) - .postBodyAsync(url, body!!, headerMap) + .getApiServiceWithProxy(baseUrl, charset, proxy) + .getAsync(url, headerMap) + } + + } + else -> { + if (proxy == null) { + HttpHelper + .getApiService(baseUrl, charset) + .getMapAsync(url, fieldMap, headerMap) + } else { + HttpHelper + .getApiServiceWithProxy(baseUrl, charset, proxy) + .getMapAsync(url, fieldMap, headerMap) + } + + } + } + return Res(NetworkUtils.getUrl(res), res.body()) + } + + fun getImageBytes(tag: String): ByteArray? { + val cookie = CookieStore.getCookie(tag) + if (cookie.isNotEmpty()) { + headerMap["Cookie"] += cookie + } + return if (fieldMap.isEmpty()) { + HttpHelper.getBytes(url, mapOf(), headerMap) + } else { + HttpHelper.getBytes(url, fieldMap, headerMap) + } + } + + suspend fun getResponseBytes(tag: String? = null): ByteArray? { + if (tag != null) { + val cookie = CookieStore.getCookie(tag) + if (cookie.isNotEmpty()) { + headerMap["Cookie"] = cookie + } + } + val response = when { + method == RequestMethod.POST -> { + if (fieldMap.isNotEmpty()) { + HttpHelper + .getBytesApiService(baseUrl) + .postMapByteAsync(url, fieldMap, headerMap) + } else { + HttpHelper + .getBytesApiService(baseUrl) + .postBodyByteAsync(url, requestBody!!, headerMap) } } fieldMap.isEmpty() -> HttpHelper - .getApiService(baseUrl, charset) - .getAsync(url, headerMap) + .getBytesApiService(baseUrl) + .getByteAsync(url, headerMap) else -> HttpHelper - .getApiService(baseUrl, charset) - .getMapAsync(url, fieldMap, headerMap) + .getBytesApiService(baseUrl) + .getMapByteAsync(url, fieldMap, headerMap) } - return Res(NetworkUtils.getUrl(res), res.body()) + return response.body() } + fun getGlideUrl(): GlideUrl { + val headers = LazyHeaders.Builder() + headerMap.forEach { (key, value) -> + headers.addHeader(key, value) + } + return GlideUrl(urlHasQuery, headers.build()) + } + + 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 9199ae05a..b7f1b015e 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 @@ -1,6 +1,5 @@ package io.legado.app.model.localBook -import android.content.Context import android.net.Uri import io.legado.app.App import io.legado.app.data.entities.Book @@ -13,32 +12,48 @@ import java.nio.charset.Charset import java.util.regex.Matcher import java.util.regex.Pattern -object AnalyzeTxtFile { - private const val folderName = "bookTxt" - private const val BLANK: Byte = 0x0a - //默认从文件中获取数据的长度 - private const val BUFFER_SIZE = 512 * 1024 - //没有标题的时候,每个章节的最大长度 - private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 - val cacheFolder: File by lazy { - val rootFile = App.INSTANCE.getExternalFilesDir(null) - ?: App.INSTANCE.externalCacheDir - ?: App.INSTANCE.cacheDir - FileUtils.createFolderIfNotExist(rootFile, subDirs = *arrayOf(folderName)) - } +class AnalyzeTxtFile { - fun analyze(context: Context, book: Book): ArrayList { - val bookFile = getBookFile(context, book) - book.charset = EncodingDetect.getEncode(bookFile) - val charset = book.fileCharset() - val toc = arrayListOf() + private val tocRules = arrayListOf() + private lateinit var charset: Charset + + @Throws(Exception::class) + fun analyze(book: Book): ArrayList { + val bookFile = getBookFile(book) + if (book.charset == null) { + book.charset = EncodingDetect.getEncode(bookFile) + } + charset = book.fileCharset() + val rulePattern = if (book.tocUrl.isNotEmpty()) { + Pattern.compile(book.tocUrl, Pattern.MULTILINE) + } else { + tocRules.addAll(getTocRules()) + null + } //获取文件流 val bookStream = RandomAccessFile(bookFile, "r") - val rulePattern = getTocRule(book, bookStream, charset) + return analyze(bookStream, book, rulePattern) + } + @Throws(Exception::class) + private fun analyze( + bookStream: RandomAccessFile, + book: Book, + pattern: Pattern? + ): ArrayList { + bookStream.seek(0) + val toc = arrayListOf() + var tocRule: TxtTocRule? = null + val rulePattern = pattern ?: let { + tocRule = getTocRule(bookStream) + tocRule?.let { + Pattern.compile(it.rule, Pattern.MULTILINE) + } + } //加载章节 val buffer = ByteArray(BUFFER_SIZE) //获取到的块起始点,在文件中的位置 + bookStream.seek(0) var curOffset: Long = 0 //block的个数 var blockPos = 0 @@ -47,13 +62,14 @@ object AnalyzeTxtFile { var allLength = 0 //获取文件中的数据到buffer,直到没有数据为止 - while (bookStream.read(buffer, 0, buffer.size).also { length = it } > 0) { - ++blockPos + while (bookStream.read(buffer).also { length = it } > 0) { + blockPos++ //如果存在Chapter - if (rulePattern != null) { //将数据转换成String + if (rulePattern != null) { + //将数据转换成String, 不能超过length var blockContent = String(buffer, 0, length, charset) val lastN = blockContent.lastIndexOf("\n") - if (lastN != 0) { + if (lastN > 0) { blockContent = blockContent.substring(0, lastN) length = blockContent.toByteArray(charset).size allLength += length @@ -66,40 +82,50 @@ object AnalyzeTxtFile { //如果存在相应章节 while (matcher.find()) { //获取匹配到的字符在字符串中的起始位置 val chapterStart = matcher.start() + //获取章节内容 + val chapterContent = blockContent.substring(seekPos, chapterStart) + val chapterLength = chapterContent.toByteArray(charset).size + val lastStart = toc.lastOrNull()?.start ?: 0 + if (curOffset + chapterLength - lastStart > 50000 && pattern == null) { + //移除不匹配的规则 + tocRules.remove(tocRule) + return analyze(bookStream, book, null) + } //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 - //第一种情况一定是序章 第二种情况可能是上一个章节的内容 + //第一种情况一定是序章 第二种情况是上一个章节的内容 if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容 - val chapterContent = blockContent.substring(seekPos, chapterStart) - //设置指针偏移 - seekPos += chapterContent.length - if (toc.size == 0) { //如果当前没有章节,那么就是序章 + if (toc.isEmpty()) { //如果当前没有章节,那么就是序章 //加入简介 - book.intro = chapterContent + if (StringUtils.trim(chapterContent).isNotEmpty()) { + val qyChapter = BookChapter() + qyChapter.title = "前言" + qyChapter.start = 0 + qyChapter.end = chapterLength.toLong() + toc.add(qyChapter) + } //创建当前章节 val curChapter = BookChapter() curChapter.title = matcher.group() - curChapter.start = chapterContent.toByteArray(charset).size.toLong() + curChapter.start = chapterLength.toLong() toc.add(curChapter) } else { //否则就block分割之后,上一个章节的剩余内容 //获取上一章节 val lastChapter = toc.last() //将当前段落添加上一章去 lastChapter.end = - lastChapter.end!! + chapterContent.toByteArray(charset).size + lastChapter.end!! + chapterLength.toLong() //创建当前章节 val curChapter = BookChapter() curChapter.title = matcher.group() curChapter.start = lastChapter.end toc.add(curChapter) } - } else { //是否存在章节 - if (toc.size != 0) { //获取章节内容 - val chapterContent = blockContent.substring(seekPos, matcher.start()) - seekPos += chapterContent.length + } else { + if (toc.isNotEmpty()) { //获取章节内容 //获取上一章节 val lastChapter = toc.last() lastChapter.end = - lastChapter.start!! + chapterContent.toByteArray(charset).size + lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong() //创建当前章节 val curChapter = BookChapter() curChapter.title = matcher.group() @@ -108,11 +134,18 @@ object AnalyzeTxtFile { } else { //如果章节不存在则创建章节 val curChapter = BookChapter() curChapter.title = matcher.group() - curChapter.start = 0L - curChapter.end = 0L + curChapter.start = 0 + curChapter.end = 0 toc.add(curChapter) } } + //设置指针偏移 + seekPos += chapterContent.length + } + if (seekPos == 0 && length > 50000 && pattern == null) { + //移除不匹配的规则 + tocRules.remove(tocRule) + return analyze(bookStream, book, null) } } else { //进行本地虚拟分章 //章节在buffer的偏移量 @@ -156,7 +189,8 @@ object AnalyzeTxtFile { //block的偏移点 curOffset += length.toLong() - if (rulePattern != null) { //设置上一章的结尾 + if (rulePattern != null) { + //设置上一章的结尾 val lastChapter = toc.last() lastChapter.end = curOffset } @@ -172,47 +206,21 @@ object AnalyzeTxtFile { val bean = toc[i] bean.index = i bean.bookUrl = book.bookUrl - bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title) ?: "") + bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title)) } book.latestChapterTitle = toc.last().title + book.totalChapterNum = toc.size System.gc() System.runFinalization() - return toc - } - - fun getContent(book: Book, bookChapter: BookChapter): String { - val bookFile = getBookFile(App.INSTANCE, book) - //获取文件流 - val bookStream = RandomAccessFile(bookFile, "r") - bookStream.seek(bookChapter.start ?: 0) - val extent = (bookChapter.end!! - bookChapter.start!!).toInt() - val content = ByteArray(extent) - bookStream.read(content, 0, extent) - return String(content, book.fileCharset()) - } - - private fun getBookFile(context: Context, book: Book): File { - if (book.bookUrl.isContentPath()) { - val uri = Uri.parse(book.bookUrl) - val bookFile = FileUtils.getFile(cacheFolder, book.originName, subDirs = *arrayOf()) - if (!bookFile.exists()) { - bookFile.createNewFile() - DocumentUtils.readBytes(context, uri)?.let { - bookFile.writeBytes(it) - } - } - return bookFile + tocRule?.let { + book.tocUrl = it.rule } - return File(book.bookUrl) + return toc } - private fun getTocRule(book: Book, bookStream: RandomAccessFile, charset: Charset): Pattern? { - if (book.tocUrl.isNotEmpty()) { - return Pattern.compile(book.tocUrl, Pattern.MULTILINE) - } - val tocRules = getTocRules() - var rulePattern: Pattern? = null + private fun getTocRule(bookStream: RandomAccessFile): TxtTocRule? { + var txtTocRule: TxtTocRule? = null //首先获取128k的数据 val buffer = ByteArray(BUFFER_SIZE / 4) val length = bookStream.read(buffer, 0, buffer.size) @@ -221,30 +229,74 @@ object AnalyzeTxtFile { val pattern = Pattern.compile(tocRule.rule, Pattern.MULTILINE) val matcher = pattern.matcher(content) if (matcher.find()) { - book.tocUrl = tocRule.rule - rulePattern = pattern + txtTocRule = tocRule break } } - bookStream.seek(0) - return rulePattern + + return txtTocRule } - private fun getTocRules(): List { - val rules = App.db.txtTocRule().all - if (rules.isEmpty()) { - return getDefaultRules() + companion object { + private const val folderName = "bookTxt" + private const val BLANK: Byte = 0x0a + + //默认从文件中获取数据的长度 + private const val BUFFER_SIZE = 512 * 1024 + + //没有标题的时候,每个章节的最大长度 + private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 + val cacheFolder: File by lazy { + val rootFile = App.INSTANCE.getExternalFilesDir(null) + ?: App.INSTANCE.externalCacheDir + ?: App.INSTANCE.cacheDir + FileUtils.createFolderIfNotExist(rootFile, folderName) + } + + fun getContent(book: Book, bookChapter: BookChapter): String { + val bookFile = getBookFile(book) + //获取文件流 + val bookStream = RandomAccessFile(bookFile, "r") + val content = ByteArray((bookChapter.end!! - bookChapter.start!!).toInt()) + bookStream.seek(bookChapter.start!!) + bookStream.read(content) + return String(content, book.fileCharset()) } - return rules - } - fun getDefaultRules(): List { - App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> - GSON.fromJsonArray(String(byteArray))?.let { - App.db.txtTocRule().insert(*it.toTypedArray()) - return it + private fun getBookFile(book: Book): File { + if (book.bookUrl.isContentPath()) { + val uri = Uri.parse(book.bookUrl) + val bookFile = FileUtils.getFile(cacheFolder, book.originName) + if (!bookFile.exists()) { + bookFile.createNewFile() + DocumentUtils.readBytes(App.INSTANCE, uri)?.let { + bookFile.writeBytes(it) + } + } + return bookFile + } + return File(book.bookUrl) + } + + private fun getTocRules(): List { + val rules = App.db.txtTocRule().enabled + if (rules.isEmpty()) { + return getDefaultEnabledRules() } + return rules + } + + fun getDefaultEnabledRules(): List { + App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> + GSON.fromJsonArray(String(byteArray))?.let { txtTocRules -> + App.db.txtTocRule().insert(*txtTocRules.toTypedArray()) + return txtTocRules.filter { + it.enable + } + } + } + return emptyList() } - return emptyList() } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt b/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt new file mode 100644 index 000000000..067837f17 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt @@ -0,0 +1,185 @@ +package io.legado.app.model.localBook + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.text.TextUtils +import io.legado.app.App +import io.legado.app.data.entities.BookChapter +import io.legado.app.utils.* +import nl.siegmann.epublib.domain.Book +import nl.siegmann.epublib.domain.TOCReference +import nl.siegmann.epublib.epub.EpubReader +import org.jsoup.Jsoup +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.nio.charset.Charset +import java.util.* + +class EPUBFile(val book: io.legado.app.data.entities.Book) { + + companion object { + private var eFile: EPUBFile? = null + + @Synchronized + private fun getEFile(book: io.legado.app.data.entities.Book): EPUBFile { + if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) { + eFile = EPUBFile(book) + return eFile!! + } + return eFile!! + } + + @Synchronized + fun getChapterList(book: io.legado.app.data.entities.Book): ArrayList { + return getEFile(book).getChapterList() + } + + @Synchronized + fun getContent(book: io.legado.app.data.entities.Book, chapter: BookChapter): String? { + return getEFile(book).getContent(chapter) + } + + @Synchronized + fun getImage( + book: io.legado.app.data.entities.Book, + href: String + ): InputStream? { + return getEFile(book).getImage(href) + } + } + + private var epubBook: Book? = null + private var mCharset: Charset = Charset.defaultCharset() + + init { + try { + val epubReader = EpubReader() + val inputStream = if (book.bookUrl.isContentPath()) { + val uri = Uri.parse(book.bookUrl) + App.INSTANCE.contentResolver.openInputStream(uri) + } else { + File(book.bookUrl).inputStream() + } + epubBook = epubReader.readEpub(inputStream) + if (book.coverUrl.isNullOrEmpty()) { + book.coverUrl = FileUtils.getPath( + App.INSTANCE.externalFilesDir, + "covers", + "${MD5Utils.md5Encode16(book.bookUrl)}.jpg" + ) + } + if (!File(book.coverUrl!!).exists()) { + epubBook!!.coverImage?.inputStream?.use { + val cover = BitmapFactory.decodeStream(it) + val out = FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!)) + cover.compress(Bitmap.CompressFormat.JPEG, 90, out) + out.flush() + out.close() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun getContent(chapter: BookChapter): String? { + epubBook?.let { eBook -> + val resource = eBook.resources.getByHref(chapter.url) + val doc = Jsoup.parse(String(resource.data, mCharset)) + val elements = doc.body().children() + elements.select("script").remove() + elements.select("style").remove() + return elements.outerHtml().htmlFormat() + } + return null + } + + private fun getImage(href: String): InputStream? { + val abHref = href.replace("../", "") + return epubBook?.resources?.getByHref(abHref)?.inputStream + } + + private fun getChapterList(): ArrayList { + val chapterList = ArrayList() + epubBook?.let { eBook -> + val metadata = eBook.metadata + book.name = metadata.firstTitle + if (metadata.authors.size > 0) { + val author = + metadata.authors[0].toString().replace("^, |, $".toRegex(), "") + book.author = author + } + if (metadata.descriptions.size > 0) { + book.intro = Jsoup.parse(metadata.descriptions[0]).text() + } + + val refs = eBook.tableOfContents.tocReferences + if (refs == null || refs.isEmpty()) { + val spineReferences = eBook.spine.spineReferences + var i = 0 + val size = spineReferences.size + while (i < size) { + val resource = + spineReferences[i].resource + var title = resource.title + if (TextUtils.isEmpty(title)) { + try { + val doc = + Jsoup.parse(String(resource.data, mCharset)) + val elements = doc.getElementsByTag("title") + if (elements.size > 0) { + title = elements[0].text() + } + } catch (e: IOException) { + e.printStackTrace() + } + } + val chapter = BookChapter() + chapter.index = i + chapter.bookUrl = book.bookUrl + chapter.url = resource.href + if (i == 0 && title.isEmpty()) { + chapter.title = "封面" + } else { + chapter.title = title + } + chapterList.add(chapter) + i++ + } + } else { + parseMenu(chapterList, refs, 0) + for (i in chapterList.indices) { + chapterList[i].index = i + } + } + } + book.latestChapterTitle = chapterList.lastOrNull()?.title + book.totalChapterNum = chapterList.size + return chapterList + } + + private fun parseMenu( + chapterList: ArrayList, + refs: List?, + level: Int + ) { + if (refs == null) return + for (ref in refs) { + if (ref.resource != null) { + val chapter = BookChapter() + chapter.bookUrl = book.bookUrl + chapter.title = ref.title + chapter.url = ref.completeHref + chapterList.add(chapter) + } + if (ref.children != null && ref.children.isNotEmpty()) { + parseMenu(chapterList, ref.children, level + 1) + } + } + } + + +} \ No newline at end of file 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..1af6f027e 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 @@ -4,15 +4,34 @@ import android.net.Uri import androidx.documentfile.provider.DocumentFile import io.legado.app.App import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.utils.FileUtils +import io.legado.app.utils.MD5Utils +import io.legado.app.utils.externalFilesDir import io.legado.app.utils.isContentPath import java.io.File object LocalBook { - fun importFile(path: String) { + fun getChapterList(book: Book): ArrayList { + return if (book.isEpub()) { + EPUBFile.getChapterList(book) + } else { + AnalyzeTxtFile().analyze(book) + } + } + + fun getContext(book: Book, chapter: BookChapter): String? { + return if (book.isEpub()) { + EPUBFile.getContent(book, chapter) + } else { + AnalyzeTxtFile.getContent(book, chapter) + } + } + + fun importFile(path: String): Book { val fileName = if (path.isContentPath()) { val doc = DocumentFile.fromSingleUri(App.INSTANCE, Uri.parse(path)) doc?.name ?: "" @@ -29,7 +48,7 @@ object LocalBook { } else { name = str.substring(0, authorIndex) author = str.substring(authorIndex) - author = BookHelp.formatAuthor(author) + author = BookHelp.formatBookAuthor(author) } val smhStart = name.indexOf("《") val smhEnd = name.indexOf("》") @@ -40,14 +59,20 @@ object LocalBook { bookUrl = path, name = name, author = author, - originName = fileName + originName = fileName, + coverUrl = FileUtils.getPath( + App.INSTANCE.externalFilesDir, + "covers", + "${MD5Utils.md5Encode16(path)}.jpg" + ) ) App.db.bookDao().insert(book) + return book } 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/Rss.kt b/app/src/main/java/io/legado/app/model/rss/Rss.kt similarity index 88% rename from app/src/main/java/io/legado/app/model/Rss.kt rename to app/src/main/java/io/legado/app/model/rss/Rss.kt index 24dadade0..f9ea78195 100644 --- a/app/src/main/java/io/legado/app/model/Rss.kt +++ b/app/src/main/java/io/legado/app/model/rss/Rss.kt @@ -1,4 +1,4 @@ -package io.legado.app.model +package io.legado.app.model.rss import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource @@ -15,18 +15,21 @@ import kotlin.coroutines.CoroutineContext object Rss { fun getArticles( + sortName: String, + sortUrl: String, rssSource: RssSource, - pageUrl: String? = null, + page: Int, scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine { return Coroutine.async(scope, context) { val analyzeUrl = AnalyzeUrl( - pageUrl ?: rssSource.sourceUrl, + sortUrl, + page = page, headerMapF = rssSource.getHeaderMap() ) val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body - RssParserByRule.parseXML(body, rssSource) + RssParserByRule.parseXML(sortName, sortUrl, body, rssSource) } } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParser.kt b/app/src/main/java/io/legado/app/model/rss/RssParser.kt index 7bda16e6d..09cd756bb 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParser.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParser.kt @@ -11,7 +11,7 @@ import java.io.StringReader object RssParser { @Throws(XmlPullParserException::class, IOException::class) - fun parseXML(xml: String, sourceUrl: String): Result { + fun parseXML(sortName: String, xml: String, sourceUrl: String): Result { val articleList = mutableListOf() var currentArticle = RssArticle() @@ -87,6 +87,7 @@ object RssParser { // The item is correctly parsed insideItem = false currentArticle.origin = sourceUrl + currentArticle.sort = sortName articleList.add(currentArticle) currentArticle = RssArticle() } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index b06950031..047d39459 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -8,12 +8,13 @@ import io.legado.app.data.entities.RssSource import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.utils.NetworkUtils +import java.util.* @Keep object RssParserByRule { @Throws(Exception::class) - fun parseXML(body: String?, rssSource: RssSource): Result { + fun parseXML(sortName: String, sortUrl: String, body: String?, rssSource: RssSource): Result { val sourceUrl = rssSource.sourceUrl var nextUrl: String? = null if (body.isNullOrBlank()) { @@ -28,11 +29,11 @@ object RssParserByRule { var ruleArticles = rssSource.ruleArticles if (ruleArticles.isNullOrBlank()) { Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析") - return RssParser.parseXML(body, sourceUrl) + return RssParser.parseXML(sortName, body, sourceUrl) } else { val articleList = mutableListOf() val analyzeRule = AnalyzeRule() - analyzeRule.setContent(body, rssSource.sourceUrl) + analyzeRule.setContent(body, sortUrl) var reverse = false if (ruleArticles.startsWith("-")) { reverse = true @@ -43,9 +44,13 @@ object RssParserByRule { Debug.log(sourceUrl, "└列表大小:${collections.size}") if (!rssSource.ruleNextPage.isNullOrEmpty()) { Debug.log(sourceUrl, "┌获取下一页链接") - nextUrl = analyzeRule.getString(rssSource.ruleNextPage) - if (nextUrl.isNotEmpty()) { - nextUrl = NetworkUtils.getAbsoluteURL(sourceUrl, nextUrl) + if (rssSource.ruleNextPage!!.toUpperCase(Locale.getDefault()) == "PAGE") { + nextUrl = sortUrl + } else { + nextUrl = analyzeRule.getString(rssSource.ruleNextPage) + if (nextUrl.isNotEmpty()) { + nextUrl = NetworkUtils.getAbsoluteURL(sortUrl, nextUrl) + } } Debug.log(sourceUrl, "└$nextUrl") } @@ -59,7 +64,8 @@ object RssParserByRule { sourceUrl, item, analyzeRule, index == 0, ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink )?.let { - it.origin = rssSource.sourceUrl + it.sort = sortName + it.origin = sourceUrl articleList.add(it) } } @@ -101,7 +107,7 @@ object RssParserByRule { rssArticle.image = analyzeRule.getString(ruleImage, true) Debug.log(sourceUrl, "└${rssArticle.image}", log) Debug.log(sourceUrl, "┌获取文章链接", log) - rssArticle.link = analyzeRule.getString(ruleLink) + rssArticle.link = NetworkUtils.getAbsoluteURL(sourceUrl, analyzeRule.getString(ruleLink))!! Debug.log(sourceUrl, "└${rssArticle.link}", log) if (rssArticle.title.isBlank()) { return null diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 8559351f4..78a139345 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -193,6 +193,7 @@ object BookChapterList { list.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle if (book.totalChapterNum < list.size) { book.lastCheckCount = list.size - book.totalChapterNum + book.latestChapterTime = System.currentTimeMillis() } book.totalChapterNum = list.size return list diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 873b6b3a9..c2ae45071 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -2,7 +2,6 @@ package io.legado.app.model.webBook 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.data.entities.BookSource @@ -28,10 +27,7 @@ object BookContent { nextChapterUrlF: String? = null ): String { body ?: throw Exception( - App.INSTANCE.getString( - R.string.error_get_web_content, - baseUrl - ) + App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") val content = StringBuilder() @@ -40,11 +36,7 @@ object BookContent { var contentData = analyzeContent( book, baseUrl, body, contentRule, bookChapter, bookSource ) - if (bookSource.bookSourceType == BookType.default) { - content.append(contentData.content.replace(bookChapter.title, "")).append("\n") - } else { - content.append(contentData.content).append("\n") - } + content.append(contentData.content).append("\n") if (contentData.nextUrl.size == 1) { var nextUrl = contentData.nextUrl[0] @@ -92,20 +84,28 @@ object BookContent { analyzeContent( book, item.nextUrl, it, contentRule, bookChapter, bookSource, false ) - item.content = contentData.content - } + item.content = contentData.content + } } } for (item in contentDataList) { content.append(item.content).append("\n") } } - + content.deleteCharAt(content.length - 1) + var contentStr = content.toString().htmlFormat() + val replaceRegex = bookSource.ruleContent?.replaceRegex + if (!replaceRegex.isNullOrEmpty()) { + val analyzeRule = AnalyzeRule(book) + analyzeRule.setContent(contentStr, baseUrl) + analyzeRule.chapter = bookChapter + contentStr = analyzeRule.getString(replaceRegex) + } Debug.log(bookSource.bookSourceUrl, "┌获取章节名称") Debug.log(bookSource.bookSourceUrl, "└${bookChapter.title}") Debug.log(bookSource.bookSourceUrl, "┌获取正文内容") - Debug.log(bookSource.bookSourceUrl, "└\n$content") - return content.toString() + Debug.log(bookSource.bookSourceUrl, "└\n$contentStr") + return contentStr } @Throws(Exception::class) @@ -130,7 +130,7 @@ object BookContent { } Debug.log(bookSource.bookSourceUrl, "└" + nextUrlList.joinToString(","), printLog) } - val content = analyzeRule.getString(contentRule.content).htmlFormat() + val content = analyzeRule.getString(contentRule.content) return ContentData(content, nextUrlList) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt index 70c1a92be..7d6c3a94c 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt @@ -2,12 +2,13 @@ package io.legado.app.model.webBook import io.legado.app.App import io.legado.app.R -import io.legado.app.constant.AppPattern import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookSource +import io.legado.app.help.BookHelp import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.utils.NetworkUtils +import io.legado.app.utils.StringUtils.wordCountFormat import io.legado.app.utils.htmlFormat object BookInfo { @@ -17,7 +18,8 @@ object BookInfo { book: Book, body: String?, bookSource: BookSource, - baseUrl: String + baseUrl: String, + canReName: Boolean, ) { body ?: throw Exception( App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) @@ -33,13 +35,17 @@ object BookInfo { } } Debug.log(bookSource.bookSourceUrl, "┌获取书名") - analyzeRule.getString(infoRule.name).let { - if (it.isNotEmpty()) book.name = it + BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let { + if (it.isNotEmpty() && (canReName || book.name.isEmpty())) { + book.name = it + } } Debug.log(bookSource.bookSourceUrl, "└${book.name}") Debug.log(bookSource.bookSourceUrl, "┌获取作者") - analyzeRule.getString(infoRule.author).let { - if (it.isNotEmpty()) book.author = it.replace(AppPattern.authorRegex, "") + BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let { + if (it.isNotEmpty() && (canReName || book.name.isEmpty())) { + book.author = it + } } Debug.log(bookSource.bookSourceUrl, "└${book.author}") Debug.log(bookSource.bookSourceUrl, "┌获取分类") @@ -50,7 +56,7 @@ object BookInfo { } Debug.log(bookSource.bookSourceUrl, "└${book.kind}") Debug.log(bookSource.bookSourceUrl, "┌获取字数") - analyzeRule.getString(infoRule.wordCount).let { + wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let { if (it.isNotEmpty()) book.wordCount = it } Debug.log(bookSource.bookSourceUrl, "└${book.wordCount}") 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..05570b6ff 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,11 +4,14 @@ 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 import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.NetworkUtils +import io.legado.app.utils.StringUtils.wordCountFormat +import io.legado.app.utils.htmlFormat import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.isActive @@ -22,7 +25,8 @@ object BookList { bookSource: BookSource, analyzeUrl: AnalyzeUrl, baseUrl: String, - isSearch: Boolean = true + variableBook: SearchBook, + isSearch: Boolean = true, ): ArrayList { val bookList = ArrayList() body ?: throw Exception( @@ -33,21 +37,22 @@ object BookList { ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") if (!scope.isActive) throw CancellationException() - val analyzeRule = AnalyzeRule(null) + val analyzeRule = AnalyzeRule(variableBook) analyzeRule.setContent(body, baseUrl) bookSource.bookUrlPattern?.let { if (baseUrl.matches(it.toRegex())) { Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") - getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> - searchBook.infoHtml = body - bookList.add(searchBook) - } + getInfoItem(scope, analyzeRule, bookSource, baseUrl, variableBook.variable) + ?.let { searchBook -> + searchBook.infoHtml = body + bookList.add(searchBook) + } return 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() @@ -64,10 +69,11 @@ object BookList { collections = analyzeRule.getElements(ruleList) if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") - getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> - searchBook.infoHtml = body - bookList.add(searchBook) - } + getInfoItem(scope, analyzeRule, bookSource, baseUrl, variableBook.variable) + ?.let { searchBook -> + searchBook.infoHtml = body + bookList.add(searchBook) + } } else { val ruleName = analyzeRule.splitSourceRule(bookListRule.name) val ruleBookUrl = analyzeRule.splitSourceRule(bookListRule.bookUrl) @@ -81,10 +87,21 @@ object BookList { for ((index, item) in collections.withIndex()) { if (!scope.isActive) throw CancellationException() getSearchItem( - scope, item, analyzeRule, bookSource, baseUrl, index == 0, - ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor, - ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind, - ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount + scope, + item, + analyzeRule, + bookSource, + baseUrl, + variableBook.variable, + index == 0, + ruleName = ruleName, + ruleBookUrl = ruleBookUrl, + ruleAuthor = ruleAuthor, + ruleCoverUrl = ruleCoverUrl, + ruleIntro = ruleIntro, + ruleKind = ruleKind, + ruleLastChapter = ruleLastChapter, + ruleWordCount = ruleWordCount )?.let { searchBook -> if (baseUrl == searchBook.bookUrl) { searchBook.infoHtml = body @@ -104,9 +121,10 @@ object BookList { scope: CoroutineScope, analyzeRule: AnalyzeRule, bookSource: BookSource, - baseUrl: String + baseUrl: String, + variable: String? ): SearchBook? { - val searchBook = SearchBook() + val searchBook = SearchBook(variable = variable) searchBook.bookUrl = baseUrl searchBook.origin = bookSource.bookSourceUrl searchBook.originName = bookSource.bookSourceName @@ -123,12 +141,12 @@ object BookList { } if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名") - searchBook.name = analyzeRule.getString(name) + searchBook.name = BookHelp.formatBookName(analyzeRule.getString(name)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}") if (searchBook.name.isNotEmpty()) { if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者") - searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author)) + searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(author)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}") if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类") @@ -136,7 +154,7 @@ object BookList { Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}") if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数") - searchBook.wordCount = analyzeRule.getString(wordCount) + searchBook.wordCount = wordCountFormat(analyzeRule.getString(wordCount)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}") if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") @@ -144,7 +162,7 @@ object BookList { Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}") if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介") - searchBook.intro = analyzeRule.getString(intro) + searchBook.intro = analyzeRule.getString(intro).htmlFormat() Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", true) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") @@ -163,6 +181,7 @@ object BookList { analyzeRule: AnalyzeRule, bookSource: BookSource, baseUrl: String, + variable: String?, log: Boolean, ruleName: List, ruleBookUrl: List, @@ -173,7 +192,7 @@ object BookList { ruleIntro: List, ruleLastChapter: List ): SearchBook? { - val searchBook = SearchBook() + val searchBook = SearchBook(variable = variable) searchBook.origin = bookSource.bookSourceUrl searchBook.originName = bookSource.bookSourceName searchBook.type = bookSource.bookSourceType @@ -182,12 +201,12 @@ object BookList { analyzeRule.setContent(item) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) - searchBook.name = analyzeRule.getString(ruleName) + searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}", log) if (searchBook.name.isNotEmpty()) { if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) - searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor)) + searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) @@ -195,7 +214,7 @@ object BookList { Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}", log) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) - searchBook.wordCount = analyzeRule.getString(ruleWordCount) + searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}", log) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) @@ -203,7 +222,7 @@ object BookList { Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}", log) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) - searchBook.intro = analyzeRule.getString(ruleIntro) + searchBook.intro = analyzeRule.getString(ruleIntro).htmlFormat() Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", log, true) if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) diff --git a/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt new file mode 100644 index 000000000..9c257a7db --- /dev/null +++ b/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt @@ -0,0 +1,129 @@ +package io.legado.app.model.webBook + +import io.legado.app.App +import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.SearchBook +import io.legado.app.help.AppConfig +import io.legado.app.help.coroutine.CompositeCoroutine +import io.legado.app.utils.getPrefString +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors +import kotlin.math.min + +class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) { + val threadCount = AppConfig.threadCount + private var searchPool: ExecutorCoroutineDispatcher? = null + private var mSearchId = 0L + private var searchPage = 1 + private var searchKey: String = "" + private var tasks = CompositeCoroutine() + private var bookSourceList = arrayListOf() + private val variableBookMap = hashMapOf() + + @Volatile + private var searchIndex = -1 + + private fun initSearchPool() { + searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + } + + fun search(searchId: Long, key: String) { + callBack.onSearchStart() + if (searchId != mSearchId) { + if (key.isEmpty()) { + callBack.onSearchCancel() + return + } else { + this.searchKey = key + } + if (mSearchId != 0L) { + close() + } + initSearchPool() + mSearchId = searchId + searchPage = 1 + val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: "" + bookSourceList.clear() + if (searchGroup.isBlank()) { + bookSourceList.addAll(App.db.bookSourceDao().allEnabled) + } else { + bookSourceList.addAll(App.db.bookSourceDao().getEnabledByGroup(searchGroup)) + } + } else { + searchPage++ + } + searchIndex = -1 + for (i in 0 until threadCount) { + search(searchId) + } + } + + private fun getVariableBook(sourceUrl: String): SearchBook { + var vBook = variableBookMap[sourceUrl] + if (vBook == null) { + vBook = SearchBook() + variableBookMap[sourceUrl] = vBook + } + return vBook + } + + private fun search(searchId: Long) { + synchronized(this) { + if (searchIndex >= bookSourceList.lastIndex) { + return + } + searchIndex++ + val source = bookSourceList[searchIndex] + val variableBook = getVariableBook(source.bookSourceUrl) + val task = WebBook(source).searchBook( + searchKey, + searchPage, + variableBook, + scope = scope, + context = searchPool!! + ).timeout(30000L) + .onSuccess(IO) { + if (searchId == mSearchId) { + callBack.onSearchSuccess(it) + } + } + .onFinally { + synchronized(this) { + if (searchIndex < bookSourceList.lastIndex) { + search(searchId) + } else { + searchIndex++ + } + if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size, + threadCount) + ) { + callBack.onSearchFinish() + } + } + } + tasks.add(task) + } + } + + fun cancelSearch() { + close() + callBack.onSearchCancel() + } + + fun close() { + tasks.clear() + searchPool?.close() + mSearchId = 0L + } + + interface CallBack { + fun onSearchStart() + fun onSearchSuccess(searchBooks: ArrayList) + fun onSearchFinish() + fun onSearchCancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt similarity index 90% rename from app/src/main/java/io/legado/app/model/WebBook.kt rename to app/src/main/java/io/legado/app/model/webBook/WebBook.kt index cb1742132..bcc087839 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt @@ -1,15 +1,12 @@ -package io.legado.app.model +package io.legado.app.model.webBook import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeUrl -import io.legado.app.model.webBook.BookChapterList -import io.legado.app.model.webBook.BookContent -import io.legado.app.model.webBook.BookInfo -import io.legado.app.model.webBook.BookList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext @@ -25,18 +22,20 @@ class WebBook(val bookSource: BookSource) { fun searchBook( key: String, page: Int? = 1, + variableBook: SearchBook, scope: CoroutineScope = Coroutine.DEFAULT, - context: CoroutineContext = Dispatchers.IO + context: CoroutineContext = Dispatchers.IO, ): Coroutine> { return Coroutine.async(scope, context) { - searchBookSuspend(scope, key, page) + searchBookSuspend(scope, key, page, variableBook) } } suspend fun searchBookSuspend( scope: CoroutineScope, key: String, - page: Int? = 1 + page: Int? = 1, + variableBook: SearchBook, ): ArrayList { bookSource.searchUrl?.let { searchUrl -> val analyzeUrl = AnalyzeUrl( @@ -44,7 +43,8 @@ class WebBook(val bookSource: BookSource) { key = key, page = page, baseUrl = sourceUrl, - headerMapF = bookSource.getHeaderMap() + headerMapF = bookSource.getHeaderMap(), + book = variableBook ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) return BookList.analyzeBookList( @@ -53,6 +53,7 @@ class WebBook(val bookSource: BookSource) { bookSource, analyzeUrl, res.url, + variableBook, true ) } @@ -65,8 +66,9 @@ class WebBook(val bookSource: BookSource) { fun exploreBook( url: String, page: Int? = 1, + variableBook: SearchBook, scope: CoroutineScope = Coroutine.DEFAULT, - context: CoroutineContext = Dispatchers.IO + context: CoroutineContext = Dispatchers.IO, ): Coroutine> { return Coroutine.async(scope, context) { val analyzeUrl = AnalyzeUrl( @@ -82,6 +84,7 @@ class WebBook(val bookSource: BookSource) { bookSource, analyzeUrl, res.url, + variableBook, false ) } @@ -93,7 +96,8 @@ class WebBook(val bookSource: BookSource) { fun getBookInfo( book: Book, scope: CoroutineScope = Coroutine.DEFAULT, - context: CoroutineContext = Dispatchers.IO + context: CoroutineContext = Dispatchers.IO, + canReName: Boolean = true, ): Coroutine { return Coroutine.async(scope, context) { book.type = bookSource.bookSourceType @@ -109,7 +113,7 @@ class WebBook(val bookSource: BookSource) { ) analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body } - BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl) + BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl, canReName) book } } diff --git a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt index 00c9d0522..fb1f5df59 100644 --- a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt @@ -8,9 +8,14 @@ import io.legado.app.App import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book import io.legado.app.help.ActivityHelp +import io.legado.app.service.AudioPlayService +import io.legado.app.service.BaseReadAloudService +import io.legado.app.service.help.AudioPlay +import io.legado.app.service.help.ReadAloud import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.main.MainActivity +import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.postEvent import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main @@ -50,11 +55,23 @@ class MediaButtonReceiver : BroadcastReceiver() { private fun readAloud(context: Context) { when { + BaseReadAloudService.isRun -> if (BaseReadAloudService.isPlay()) { + ReadAloud.pause(context) + AudioPlay.pause(context) + } else { + ReadAloud.resume(context) + AudioPlay.resume(context) + } + AudioPlayService.isRun -> if (AudioPlayService.pause) { + AudioPlay.resume(context) + } else { + AudioPlay.pause(context) + } ActivityHelp.isExist(AudioPlayActivity::class.java) -> postEvent(EventBus.MEDIA_BUTTON, true) ActivityHelp.isExist(ReadBookActivity::class.java) -> postEvent(EventBus.MEDIA_BUTTON, true) - else -> { + else -> if (context.getPrefBoolean("mediaButtonOnExit", true)) { GlobalScope.launch(Main) { val lastBook: Book? = withContext(IO) { App.db.bookDao().lastReadBook diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt index 986e7c4ed..1817da185 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,9 @@ 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 +143,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 +181,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 +196,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 +517,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..2fd69506e 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 { @@ -146,6 +145,9 @@ abstract class BaseReadAloudService : BaseService(), open fun resumeReadAloud() { pause = false upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING) + if (timeMinute > 1) { + doDs() + } } abstract fun upSpeechRate(reset: Boolean = false) @@ -164,12 +166,12 @@ abstract class BaseReadAloudService : BaseService(), } private fun addTimer() { - if (timeMinute == 60) { + if (timeMinute == 180) { timeMinute = 0 handler.removeCallbacks(dsRunnable) } else { timeMinute += 10 - if (timeMinute > 60) timeMinute = 60 + if (timeMinute > 180) timeMinute = 180 handler.removeCallbacks(dsRunnable) handler.postDelayed(dsRunnable, 60000) } @@ -204,7 +206,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 +218,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 +236,7 @@ abstract class BaseReadAloudService : BaseService(), PendingIntent.FLAG_CANCEL_CURRENT ) ) - mediaSessionCompat?.isActive = true + mediaSessionCompat.isActive = true } /** @@ -281,7 +282,7 @@ abstract class BaseReadAloudService : BaseService(), private fun upNotification() { var nTitle: String = when { pause -> getString(R.string.read_aloud_pause) - timeMinute in 1..60 -> getString( + timeMinute in 1..180 -> getString( R.string.read_aloud_timer, timeMinute ) @@ -325,7 +326,6 @@ abstract class BaseReadAloudService : BaseService(), ) builder.setStyle( androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(mediaSessionCompat?.sessionToken) .setShowActionsInCompactView(0, 1, 2) ) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -336,6 +336,7 @@ abstract class BaseReadAloudService : BaseService(), abstract fun aloudServicePendingIntent(actionStr: String): PendingIntent? open fun nextChapter() { + ReadBook.upReadStartTime() if (!ReadBook.moveToNextChapter(true)) { stopSelf() } diff --git a/app/src/main/java/io/legado/app/service/CacheBookService.kt b/app/src/main/java/io/legado/app/service/CacheBookService.kt new file mode 100644 index 000000000..ed00be50e --- /dev/null +++ b/app/src/main/java/io/legado/app/service/CacheBookService.kt @@ -0,0 +1,296 @@ +package io.legado.app.service + +import android.content.Intent +import android.os.Handler +import androidx.core.app.NotificationCompat +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseService +import io.legado.app.constant.AppConst +import io.legado.app.constant.EventBus +import io.legado.app.constant.IntentAction +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.IntentHelp +import io.legado.app.help.coroutine.CompositeCoroutine +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.webBook.WebBook +import io.legado.app.service.help.CacheBook +import io.legado.app.utils.postEvent +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.isActive +import org.jetbrains.anko.toast +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArraySet +import java.util.concurrent.Executors + +class CacheBookService : BaseService() { + private val threadCount = AppConfig.threadCount + private var searchPool = + Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + private var tasks = CompositeCoroutine() + private val handler = Handler() + private var runnable: Runnable = Runnable { upDownload() } + private val bookMap = ConcurrentHashMap() + private val webBookMap = ConcurrentHashMap() + private val downloadMap = ConcurrentHashMap>() + private val downloadCount = ConcurrentHashMap() + private val finalMap = ConcurrentHashMap>() + private val downloadingList = CopyOnWriteArraySet() + + @Volatile + private var downloadingCount = 0 + private var notificationContent = App.INSTANCE.getString(R.string.starting_download) + + private val notificationBuilder by lazy { + val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) + .setSmallIcon(R.drawable.ic_download) + .setOngoing(true) + .setContentTitle(getString(R.string.offline_cache)) + builder.addAction( + R.drawable.ic_stop_black_24dp, + getString(R.string.cancel), + IntentHelp.servicePendingIntent(this, IntentAction.stop) + ) + builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + } + + override fun onCreate() { + super.onCreate() + updateNotification(notificationContent) + handler.postDelayed(runnable, 1000) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + intent?.action?.let { action -> + when (action) { + IntentAction.start -> addDownloadData( + intent.getStringExtra("bookUrl"), + intent.getIntExtra("start", 0), + intent.getIntExtra("end", 0) + ) + IntentAction.remove -> removeDownload(intent.getStringExtra("bookUrl")) + IntentAction.stop -> stopDownload() + } + } + return super.onStartCommand(intent, flags, startId) + } + + override fun onDestroy() { + tasks.clear() + searchPool.close() + handler.removeCallbacks(runnable) + downloadMap.clear() + finalMap.clear() + super.onDestroy() + postEvent(EventBus.UP_DOWNLOAD, downloadMap) + } + + private fun getBook(bookUrl: String): Book? { + var book = bookMap[bookUrl] + if (book == null) { + synchronized(this) { + book = bookMap[bookUrl] + if (book == null) { + book = App.db.bookDao().getBook(bookUrl) + if (book == null) { + removeDownload(bookUrl) + } + } + } + } + return book + } + + private fun getWebBook(bookUrl: String, origin: String): WebBook? { + var webBook = webBookMap[origin] + if (webBook == null) { + synchronized(this) { + webBook = webBookMap[origin] + if (webBook == null) { + App.db.bookSourceDao().getBookSource(origin)?.let { + webBook = WebBook(it) + } + if (webBook == null) { + removeDownload(bookUrl) + } + } + } + } + return webBook + } + + private fun addDownloadData(bookUrl: String?, start: Int, end: Int) { + bookUrl ?: return + if (downloadMap.containsKey(bookUrl)) { + updateNotification(getString(R.string.already_in_download)) + toast(R.string.already_in_download) + return + } + downloadCount[bookUrl] = DownloadCount() + execute { + App.db.bookChapterDao().getChapterList(bookUrl, start, end).let { + if (it.isNotEmpty()) { + val chapters = CopyOnWriteArraySet() + chapters.addAll(it) + downloadMap[bookUrl] = chapters + } else { + CacheBook.addLog("${getBook(bookUrl)?.name} is empty") + } + } + for (i in 0 until threadCount) { + if (downloadingCount < threadCount) { + download() + } + } + } + } + + private fun removeDownload(bookUrl: String?) { + downloadMap.remove(bookUrl) + finalMap.remove(bookUrl) + } + + private fun download() { + downloadingCount += 1 + val task = Coroutine.async(this, context = searchPool) { + if (!isActive) return@async + val bookChapter: BookChapter? = synchronized(this@CacheBookService) { + downloadMap.forEach { + it.value.forEach { chapter -> + if (!downloadingList.contains(chapter.url)) { + downloadingList.add(chapter.url) + return@synchronized chapter + } + } + } + return@synchronized null + } + if (bookChapter == null) { + postDownloading(false) + } else { + val book = getBook(bookChapter.bookUrl) + if (book == null) { + postDownloading(true) + return@async + } + val webBook = getWebBook(bookChapter.bookUrl, book.origin) + if (webBook == null) { + postDownloading(true) + return@async + } + if (!BookHelp.hasContent(book, bookChapter)) { + webBook.getContent( + book, + bookChapter, + scope = this, + context = searchPool + ).timeout(60000L) + .onError { + synchronized(this) { + downloadingList.remove(bookChapter.url) + } + CacheBook.addLog("getContentError${it.localizedMessage}") + updateNotification("getContentError${it.localizedMessage}") + } + .onSuccess(IO) { content -> + BookHelp.saveContent(book, bookChapter, content) + synchronized(this@CacheBookService) { + downloadCount[book.bookUrl]?.increaseSuccess() + downloadCount[book.bookUrl]?.increaseFinished() + downloadCount[book.bookUrl]?.let { + updateNotification( + it, + downloadMap[book.bookUrl]?.size, + bookChapter.title + ) + } + val chapterMap = + finalMap[book.bookUrl] + ?: CopyOnWriteArraySet().apply { + finalMap[book.bookUrl] = this + } + chapterMap.add(bookChapter) + if (chapterMap.size == downloadMap[book.bookUrl]?.size) { + downloadMap.remove(book.bookUrl) + finalMap.remove(book.bookUrl) + downloadCount.remove(book.bookUrl) + } + } + }.onFinally(IO) { + postDownloading(true) + } + } else { + //无需下载的,设置为增加成功 + downloadCount[book.bookUrl]?.increaseSuccess() + downloadCount[book.bookUrl]?.increaseFinished() + postDownloading(true) + } + } + }.onError { + CacheBook.addLog("ERROR:${it.localizedMessage}") + updateNotification("ERROR:${it.localizedMessage}") + } + tasks.add(task) + } + + private fun postDownloading(hasChapter: Boolean) { + downloadingCount -= 1 + if (hasChapter) { + download() + } else { + if (downloadingCount < 1) { + stopDownload() + } + } + } + + private fun stopDownload() { + tasks.clear() + stopSelf() + } + + private fun upDownload() { + updateNotification(notificationContent) + postEvent(EventBus.UP_DOWNLOAD, downloadMap) + handler.removeCallbacks(runnable) + handler.postDelayed(runnable, 1000) + } + + private fun updateNotification( + downloadCount: DownloadCount, + totalCount: Int?, + content: String + ) { + notificationContent = + "进度:" + downloadCount.downloadFinishedCount + "/" + totalCount + ",成功:" + downloadCount.successCount + "," + content + } + + /** + * 更新通知 + */ + private fun updateNotification(content: String) { + notificationBuilder.setContentText(content) + val notification = notificationBuilder.build() + startForeground(AppConst.notificationIdDownload, notification) + } + + class DownloadCount { + @Volatile + var downloadFinishedCount = 0 // 下载完成的条目数量 + + @Volatile + var successCount = 0 //下载成功的条目数量 + + fun increaseSuccess() { + ++successCount + } + + fun increaseFinished() { + ++downloadFinishedCount + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index 336dfb18d..09900d0d0 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -9,21 +9,36 @@ import io.legado.app.constant.AppConst import io.legado.app.constant.IntentAction import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.model.WebBook +import io.legado.app.help.coroutine.CompositeCoroutine +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 task: Coroutine<*>? = null + private var tasks = CompositeCoroutine() private val allIds = ArrayList() private val checkedIds = ArrayList() private var processIndex = 0 + private val notificationBuilder by lazy { + NotificationCompat.Builder(this, AppConst.channelIdReadAloud) + .setSmallIcon(R.drawable.ic_network_check) + .setOngoing(true) + .setContentTitle(getString(R.string.check_book_source)) + .setContentIntent( + IntentHelp.activityPendingIntent(this, "activity") + ) + .addAction( + R.drawable.ic_stop_black_24dp, + getString(R.string.cancel), + IntentHelp.servicePendingIntent(this, IntentAction.stop) + ) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + } override fun onCreate() { super.onCreate() @@ -42,52 +57,61 @@ class CheckSourceService : BaseService() { override fun onDestroy() { super.onDestroy() - task?.cancel() + tasks.clear() searchPool.close() } private fun check(ids: List) { - task?.cancel() + if (allIds.isNotEmpty()) { + toast("已有书源在校验,等完成后再试") + return + } + tasks.clear() allIds.clear() checkedIds.clear() allIds.addAll(ids) processIndex = 0 + threadCount = min(allIds.size, threadCount) updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) - task = execute { - for (i in 0 until threadCount) { - check() - } - }.onError { - toast("校验书源出错:${it.localizedMessage}") + for (i in 0 until threadCount) { + check() } } - + /** + * 检测 + */ private fun check() { + val index = processIndex synchronized(this) { processIndex++ } - if (processIndex < allIds.size) { - val sourceUrl = allIds[processIndex] - App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> - val webBook = WebBook(source) - webBook.searchBook("我的", scope = this, context = searchPool) - .onError(IO) { - source.addGroup("失效") - App.db.bookSourceDao().update(source) - }.onFinally(IO) { - check() - checkedIds.add(sourceUrl) - updateNotification( - checkedIds.size, - getString(R.string.progress_show, checkedIds.size, allIds.size) - ) - synchronized(this) { - if (processIndex >= allIds.size + threadCount - 1) { - stopSelf() - } + execute { + if (index < allIds.size) { + val sourceUrl = allIds[index] + App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> + if (source.searchUrl.isNullOrEmpty()) { + onNext(sourceUrl) + } else { + CheckSource(source).check(this, searchPool) { + onNext(it) } } + } ?: onNext(sourceUrl) + } + } + } + + private fun onNext(sourceUrl: String) { + synchronized(this) { + check() + checkedIds.add(sourceUrl) + updateNotification( + checkedIds.size, + getString(R.string.progress_show, checkedIds.size, allIds.size) + ) + if (processIndex >= allIds.size + threadCount - 1) { + stopSelf() } } } @@ -96,23 +120,9 @@ class CheckSourceService : BaseService() { * 更新通知 */ private fun updateNotification(state: Int, msg: String) { - val builder = NotificationCompat.Builder(this, AppConst.channelIdReadAloud) - .setSmallIcon(R.drawable.ic_network_check) - .setOngoing(true) - .setContentTitle(getString(R.string.check_book_source)) - .setContentText(msg) - .setContentIntent( - IntentHelp.activityPendingIntent(this, "activity") - ) - .addAction( - R.drawable.ic_stop_black_24dp, - getString(R.string.cancel), - IntentHelp.servicePendingIntent(this, IntentAction.stop) - ) - builder.setProgress(allIds.size, state, false) - builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - val notification = builder.build() - startForeground(112202, notification) + notificationBuilder.setContentText(msg) + notificationBuilder.setProgress(allIds.size, state, false) + startForeground(112202, notificationBuilder.build()) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index dd48933e8..53868a977 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -1,208 +1,197 @@ package io.legado.app.service +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.os.Build import android.os.Handler import androidx.core.app.NotificationCompat -import io.legado.app.App +import androidx.core.content.FileProvider +import androidx.core.os.bundleOf +import io.legado.app.BuildConfig import io.legado.app.R import io.legado.app.base.BaseService import io.legado.app.constant.AppConst -import io.legado.app.constant.EventBus import io.legado.app.constant.IntentAction -import io.legado.app.data.entities.BookChapter -import io.legado.app.help.AppConfig -import io.legado.app.help.BookHelp import io.legado.app.help.IntentHelp -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.model.WebBook -import io.legado.app.utils.postEvent -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.isActive +import io.legado.app.utils.RealPathUtil +import io.legado.app.utils.msg +import org.jetbrains.anko.downloadManager import org.jetbrains.anko.toast -import java.util.concurrent.Executors +import java.io.File + class DownloadService : BaseService() { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var tasks: ArrayList> = arrayListOf() + + private val downloads = hashMapOf() + private val completeDownloads = hashSetOf() private val handler = Handler() - private var runnable: Runnable = Runnable { upDownload() } - private val downloadMap = hashMapOf>() - private val downloadCount = hashMapOf(); - private val finalMap = hashMapOf>() - private var notificationContent = "正在启动下载" - - private val notificationBuilder by lazy { - val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) - .setSmallIcon(R.drawable.ic_download) - .setOngoing(true) - .setContentTitle(getString(R.string.download_offline)) - builder.addAction( - R.drawable.ic_stop_black_24dp, - getString(R.string.cancel), - IntentHelp.servicePendingIntent(this, IntentAction.stop) - ) - builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + private val runnable = Runnable { + checkDownloadState() + } + + private val downloadReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + queryState() + } } override fun onCreate() { super.onCreate() - updateNotification(notificationContent) - handler.postDelayed(runnable, 1000) + registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - intent?.action?.let { action -> - when (action) { - IntentAction.start -> addDownloadData( - intent.getStringExtra("bookUrl"), - intent.getIntExtra("start", 0), - intent.getIntExtra("end", 0) - ) - IntentAction.remove -> removeDownload(intent.getStringExtra("bookUrl")) - IntentAction.stop -> stopDownload() - } - } - return super.onStartCommand(intent, flags, startId) + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + stopSelf() } override fun onDestroy() { - tasks.clear() - searchPool.close() - handler.removeCallbacks(runnable) - downloadMap.clear() - finalMap.clear() super.onDestroy() - postEvent(EventBus.UP_DOWNLOAD, downloadMap) + unregisterReceiver(downloadReceiver) } - private fun addDownloadData(bookUrl: String?, start: Int, end: Int) { - bookUrl ?: return - if (downloadMap.containsKey(bookUrl)) { - toast("该书已在下载列表") - return - } - execute { - val chapterMap = downloadMap[bookUrl] ?: linkedSetOf().apply { - downloadMap[bookUrl] = this + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + IntentAction.start -> startDownload( + intent.getLongExtra("downloadId", 0), + intent.getStringExtra("fileName") ?: "未知文件" + ) + IntentAction.play -> { + val id = intent.getLongExtra("downloadId", 0) + if (downloads[id]?.endsWith(".apk") == true) { + installApk(id) + } } - App.db.bookChapterDao().getChapterList(bookUrl, start, end).let { - chapterMap.addAll(it) + IntentAction.stop -> { + val downloadId = intent.getLongExtra("downloadId", 0) + downloads.remove(downloadId) + if (downloads.isEmpty()) { + stopSelf() + } } - download() } + return super.onStartCommand(intent, flags, startId) } - private fun removeDownload(bookUrl: String?) { - downloadMap.remove(bookUrl) - finalMap.remove(bookUrl) + private fun startDownload(downloadId: Long, fileName: String) { + if (downloadId > 0) { + downloads[downloadId] = fileName + queryState() + checkDownloadState() + } } - private fun updateNotification(downloadCount:DownloadCount, totalCount: Int, content: String){ - notificationContent = - "进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content" + private fun checkDownloadState() { + handler.removeCallbacks(runnable) + queryState() + handler.postDelayed(runnable, 1000) } - private fun download() { - val task = Coroutine.async(this, context = searchPool) { - downloadMap.forEach { entry -> - if (!isActive) return@async - if (!finalMap.containsKey(entry.key)) { - val book = App.db.bookDao().getBook(entry.key) ?: return@async - val bookSource = - App.db.bookSourceDao().getBookSource(book.origin) ?: return@async - val webBook = WebBook(bookSource) - - downloadCount[entry.key] = DownloadCount() - - entry.value.forEach { chapter -> - if (!isActive) return@async - if (downloadMap.containsKey(book.bookUrl)) { - if (!BookHelp.hasContent(book, chapter)) { - webBook.getContent( - book, - chapter, - scope = this, - context = searchPool - ) - //.onStart { - // notificationContent = "启动:" + chapter.title - //} - .onSuccess(IO) { content -> - downloadCount[entry.key]?.increaseSuccess() - BookHelp.saveContent(book, chapter, content) - } - .onFinally(IO) { - synchronized(this@DownloadService) { - downloadCount[entry.key]?.increaseFinished() - downloadCount[entry.key]?.let { updateNotification(it, entry.value.size, chapter.title) } - val chapterMap = - finalMap[book.bookUrl] - ?: linkedSetOf().apply { - finalMap[book.bookUrl] = this - } - chapterMap.add(chapter) - if (chapterMap.size == entry.value.size) { - downloadMap.remove(book.bookUrl) - finalMap.remove(book.bookUrl) - downloadCount.remove(entry.key) - } - } - } - } else{ - //无需下载的,设置为增加成功 - downloadCount[entry.key]?.increaseSuccess() - downloadCount[entry.key]?.increaseFinished() + //查询下载进度 + private fun queryState() { + val ids = downloads.keys + val query = DownloadManager.Query() + query.setFilterById(*ids.toLongArray()) + downloadManager.query(query).use { cursor -> + if (!cursor.moveToFirst()) return + val id = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) + val progress: Int = + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) + val max: Int = + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) + val status = + when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) { + DownloadManager.STATUS_PAUSED -> "暂停" + DownloadManager.STATUS_PENDING -> "待下载" + DownloadManager.STATUS_RUNNING -> "下载中" + DownloadManager.STATUS_SUCCESSFUL -> { + if (!completeDownloads.contains(id)) { + completeDownloads.add(id) + if (downloads[id]?.endsWith(".apk") == true) { + installApk(id) } } + "下载完成" } + DownloadManager.STATUS_FAILED -> "下载失败" + else -> "未知状态" } + updateNotification(id, "${downloads[id]} $status", max, progress) + } + } + private fun installApk(downloadId: Long) { + downloadManager.getUriForDownloadedFile(downloadId)?.let { + val filePath = RealPathUtil.getPath(this, it) ?: return + val file = File(filePath) + //调用系统安装apk + val intent = Intent() + intent.action = Intent.ACTION_VIEW + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0版本以上 + val uriForFile: Uri = + FileProvider.getUriForFile( + this, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.setDataAndType(uriForFile, "application/vnd.android.package-archive") + } else { + val uri: Uri = Uri.fromFile(file) + intent.setDataAndType(uri, "application/vnd.android.package-archive") } - } - tasks.add(task) - task.invokeOnCompletion { - tasks.remove(task) - if (tasks.isEmpty()) { - stopSelf() + try { + startActivity(intent) + } catch (e: Exception) { + toast(e.msg) } } } - private fun stopDownload() { - tasks.clear() - stopSelf() - } - - private fun upDownload() { - updateNotification(notificationContent) - postEvent(EventBus.UP_DOWNLOAD, downloadMap) - handler.removeCallbacks(runnable) - handler.postDelayed(runnable, 1000) - } - /** * 更新通知 */ - private fun updateNotification(content: String) { - val builder = notificationBuilder - builder.setContentText(content) - val notification = builder.build() - startForeground(AppConst.notificationIdDownload, notification) - } -} - -class DownloadCount{ - @Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量 - @Volatile public var successCount = 0 //下载成功的条目数量 - - fun increaseSuccess(){ - ++successCount; + private fun updateNotification(downloadId: Long, content: String, max: Int, progress: Int) { + val notificationBuilder = NotificationCompat.Builder(this, AppConst.channelIdDownload) + .setSmallIcon(R.drawable.ic_download) + .setOngoing(true) + .setContentTitle(getString(R.string.action_download)) + notificationBuilder.setContentIntent( + IntentHelp.servicePendingIntent( + this, + IntentAction.play, + bundleOf("downloadId" to downloadId) + ) + ) + notificationBuilder.addAction( + R.drawable.ic_stop_black_24dp, + getString(R.string.cancel), + IntentHelp.servicePendingIntent( + this, + IntentAction.stop, + bundleOf("downloadId" to downloadId) + ) + ) + notificationBuilder.setDeleteIntent( + IntentHelp.servicePendingIntent( + this, + IntentAction.stop, + bundleOf("downloadId" to downloadId) + ) + ) + notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + notificationBuilder.setContentText(content) + notificationBuilder.setProgress(max, progress, false) + notificationBuilder.setAutoCancel(true) + val notification = notificationBuilder.build() + startForeground(downloadId.toInt(), notification) } - fun increaseFinished(){ - ++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..4fe576dcf 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -1,13 +1,14 @@ package io.legado.app.service import android.app.PendingIntent +import android.content.Intent import android.media.MediaPlayer import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp import io.legado.app.help.coroutine.Coroutine -import io.legado.app.help.http.HttpHelper -import io.legado.app.help.http.api.HttpPostApi +import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook import io.legado.app.utils.FileUtils import io.legado.app.utils.LogUtils @@ -16,7 +17,6 @@ import kotlinx.coroutines.isActive import java.io.File import java.io.FileDescriptor import java.io.FileInputStream -import java.net.URLEncoder class HttpReadAloudService : BaseReadAloudService(), MediaPlayer.OnPreparedListener, @@ -30,12 +30,17 @@ class HttpReadAloudService : BaseReadAloudService(), override fun onCreate() { super.onCreate() - ttsFolder = cacheDir.absolutePath + File.separator + "bdTts" + ttsFolder = externalCacheDir!!.absolutePath + File.separator + "httpTTS" mediaPlayer.setOnErrorListener(this) mediaPlayer.setOnPreparedListener(this) mediaPlayer.setOnCompletionListener(this) } + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + stopSelf() + } + override fun onDestroy() { super.onDestroy() task?.cancel() @@ -66,17 +71,21 @@ class HttpReadAloudService : BaseReadAloudService(), FileUtils.deleteFile(ttsFolder) for (index in 0 until contentList.size) { if (isActive) { - val bytes = HttpHelper.getByteRetrofit("http://tts.baidu.com") - .create(HttpPostApi::class.java) - .postMapByteAsync( - "http://tts.baidu.com/text2audio", - getAudioBody(contentList[index]), mapOf() - ).body() - if (bytes != null && isActive) { - val file = getSpeakFile(index) - file.writeBytes(bytes) - if (index == nowSpeak) { - playAudio(FileInputStream(file).fd) + ReadAloud.httpTTS?.let { + AnalyzeUrl( + it.url, + speakText = contentList[index], + speakSpeed = AppConfig.ttsSpeechRate + ).getResponseBytes()?.let { bytes -> + if (isActive) { + val file = getSpeakFile(index) + file.writeBytes(bytes) + if (index == nowSpeak) { + @Suppress("BlockingMethodInNonBlockingContext") + val fis = FileInputStream(file) + playAudio(fis.fd) + } + } } } } else { @@ -105,31 +114,6 @@ class HttpReadAloudService : BaseReadAloudService(), return FileUtils.createFileIfNotExist("${ttsFolder}${File.separator}${index}.mp3") } - private fun getAudioBody(content: String): Map { - return mapOf( - Pair("tex", encodeTwo(content)), - Pair("spd", ((AppConfig.ttsSpeechRate + 5) / 10 + 4).toString()), - Pair("per", AppConfig.ttsSpeechPer), - Pair("cuid", "baidu_speech_demo"), - Pair("idx", "1"), - Pair("cod", "2"), - Pair("lan", "zh"), - Pair("ctp", "1"), - Pair("pdt", "1"), - Pair("vol", "5"), - Pair("pit", "5"), - Pair("_res_tag_", "audio") - ) - } - - private fun encodeTwo(content: String): String { - return try { - URLEncoder.encode(URLEncoder.encode(content, "UTF-8"), "UTF-8") - } catch (e: Exception) { - " " - } - } - override fun pauseReadAloud(pause: Boolean) { super.pauseReadAloud(pause) mediaPlayer.pause() @@ -204,7 +188,7 @@ class HttpReadAloudService : BaseReadAloudService(), } else { nextChapter() } - }, 1000) + }, 50) 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..981d4bc68 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,7 @@ package io.legado.app.service import android.app.PendingIntent -import android.os.Build +import android.content.Intent import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import io.legado.app.R @@ -20,35 +20,51 @@ 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() } + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + clearTTS() + stopSelf() + } + + 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 +74,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 +100,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 +158,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/AudioPlay.kt b/app/src/main/java/io/legado/app/service/help/AudioPlay.kt index 7c8d120b3..06bb6b5a9 100644 --- a/app/src/main/java/io/legado/app/service/help/AudioPlay.kt +++ b/app/src/main/java/io/legado/app/service/help/AudioPlay.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.constant.IntentAction import io.legado.app.constant.Status import io.legado.app.data.entities.Book -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import io.legado.app.service.AudioPlayService object AudioPlay { diff --git a/app/src/main/java/io/legado/app/service/help/CacheBook.kt b/app/src/main/java/io/legado/app/service/help/CacheBook.kt new file mode 100644 index 000000000..f2748826a --- /dev/null +++ b/app/src/main/java/io/legado/app/service/help/CacheBook.kt @@ -0,0 +1,47 @@ +package io.legado.app.service.help + +import android.content.Context +import android.content.Intent +import io.legado.app.constant.IntentAction +import io.legado.app.service.CacheBookService + +object CacheBook { + + val logs = arrayListOf() + + fun addLog(log: String?) { + log ?: return + synchronized(this) { + if (logs.size > 1000) { + logs.removeAt(0) + } + logs.add(log) + } + } + + fun start(context: Context, bookUrl: String, start: Int, end: Int) { + Intent(context, CacheBookService::class.java).let { + it.action = IntentAction.start + it.putExtra("bookUrl", bookUrl) + it.putExtra("start", start) + it.putExtra("end", end) + context.startService(it) + } + } + + fun remove(context: Context, bookUrl: String) { + Intent(context, CacheBookService::class.java).let { + it.action = IntentAction.remove + it.putExtra("bookUrl", bookUrl) + context.startService(it) + } + } + + fun stop(context: Context) { + Intent(context, CacheBookService::class.java).let { + it.action = IntentAction.stop + context.startService(it) + } + } + +} \ No newline at end of file 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..352c4b799 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,66 @@ 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.data.entities.SearchBook +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.webBook.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) + val variableBook = SearchBook(origin = source.bookSourceUrl) + return webBook + .searchBook(keyword, scope = scope, context = context, variableBook = variableBook) + .timeout(60000L) + .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/Download.kt b/app/src/main/java/io/legado/app/service/help/Download.kt index 9f93dc358..cddbf59b6 100644 --- a/app/src/main/java/io/legado/app/service/help/Download.kt +++ b/app/src/main/java/io/legado/app/service/help/Download.kt @@ -7,20 +7,11 @@ import io.legado.app.service.DownloadService object Download { - fun start(context: Context, bookUrl: String, start: Int, end: Int) { + fun start(context: Context, downloadId: Long, fileName: String) { Intent(context, DownloadService::class.java).let { it.action = IntentAction.start - it.putExtra("bookUrl", bookUrl) - it.putExtra("start", start) - it.putExtra("end", end) - context.startService(it) - } - } - - fun remove(context: Context, bookUrl: String) { - Intent(context, DownloadService::class.java).let { - it.action = IntentAction.remove - it.putExtra("bookUrl", bookUrl) + it.putExtra("downloadId", downloadId) + it.putExtra("fileName", fileName) context.startService(it) } } diff --git a/app/src/main/java/io/legado/app/service/help/ReadAloud.kt b/app/src/main/java/io/legado/app/service/help/ReadAloud.kt index b5c12a965..b25c270c3 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadAloud.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadAloud.kt @@ -5,22 +5,31 @@ import android.content.Intent import io.legado.app.App import io.legado.app.constant.IntentAction import io.legado.app.constant.PreferKey +import io.legado.app.data.entities.HttpTTS import io.legado.app.service.BaseReadAloudService import io.legado.app.service.HttpReadAloudService import io.legado.app.service.TTSReadAloudService -import io.legado.app.utils.getPrefBoolean +import io.legado.app.utils.getPrefLong object ReadAloud { - var aloudClass: Class<*> = getReadAloudClass() + private var aloudClass: Class<*> = getReadAloudClass() + var httpTTS: HttpTTS? = null - fun getReadAloudClass(): Class<*> { - return if (App.INSTANCE.getPrefBoolean(PreferKey.readAloudOnLine)) { + private fun getReadAloudClass(): Class<*> { + val spId = App.INSTANCE.getPrefLong(PreferKey.speakEngine) + httpTTS = App.db.httpTTSDao().get(spId) + stop(App.INSTANCE) + return if (httpTTS != null) { HttpReadAloudService::class.java } else { TTSReadAloudService::class.java } } + fun upReadAloudClass() { + aloudClass = getReadAloudClass() + } + fun play( context: Context, title: String, diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index f48f13d11..2e96221f3 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,22 +1,30 @@ package io.legado.app.service.help +import android.util.Log 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.data.entities.BookSource +import io.legado.app.data.entities.ReadRecord +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 -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import io.legado.app.service.BaseReadAloudService -import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.entities.TextChapter +import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.book.read.page.provider.ImageProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.jetbrains.anko.getStackTraceString import org.jetbrains.anko.toast @@ -32,13 +40,17 @@ object ReadBook { var prevTextChapter: TextChapter? = null var curTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null + var bookSource: BookSource? = null var webBook: WebBook? = null var msg: String? = null private val loadingChapters = arrayListOf() + private val readRecord = ReadRecord() + var readStartTime: Long = System.currentTimeMillis() - fun resetData(book: Book, noSource: (name: String, author: String) -> Unit) { + fun resetData(book: Book) { this.book = book - titleDate.postValue(book.name) + readRecord.bookName = book.name + readRecord.readTime = App.db.readRecordDao().getReadTime(book.name) ?: 0 durChapterIndex = book.durChapterIndex durPageIndex = book.durChapterPos isLocalBook = book.origin == BookType.local @@ -46,27 +58,39 @@ object ReadBook { prevTextChapter = null curTextChapter = null nextTextChapter = null - upWebBook(book, noSource) + titleDate.postValue(book.name) + upWebBook(book) + ImageProvider.clearAllCache() } - fun upWebBook(book: Book?, noSource: (name: String, author: String) -> Unit) { - book ?: return + fun upWebBook(book: Book) { if (book.origin == BookType.local) { + bookSource = null webBook = null } else { - val bookSource = App.db.bookSourceDao().getBookSource(book.origin) - if (bookSource != null) { - webBook = WebBook(bookSource) - } else { + App.db.bookSourceDao().getBookSource(book.origin)?.let { + bookSource = it + webBook = WebBook(it) + } ?: let { + bookSource = null webBook = null - noSource.invoke(book.name, book.author) } } } + fun upReadStartTime() { + Coroutine.async { + readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime + readStartTime = System.currentTimeMillis() + App.db.readRecordDao().insert(readRecord) + } + } + fun upMsg(msg: String?) { - this.msg = msg - callBack?.upContent() + if (this.msg != msg) { + this.msg = msg + callBack?.upContent() + } } fun moveToNextPage() { @@ -107,7 +131,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,10 +173,11 @@ object ReadBook { } private fun curPageChanged() { - callBack?.upPageProgress() + callBack?.pageChanged() if (BaseReadAloudService.isRun) { readAloud(!BaseReadAloudService.pause) } + upReadStartTime() } /** @@ -176,10 +201,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 } @@ -211,7 +236,7 @@ object ReadBook { Coroutine.async { App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> BookHelp.getContent(book, chapter)?.let { - contentLoadFinish(chapter, it, upContent, resetPageOffset) + contentLoadFinish(book, chapter, it, upContent, resetPageOffset) removeLoading(chapter.index) } ?: download(chapter, resetPageOffset = resetPageOffset) } ?: removeLoading(index) @@ -242,11 +267,14 @@ object ReadBook { } private fun download(chapter: BookChapter, resetPageOffset: Boolean) { - book?.let { book -> - webBook?.getContent(book, chapter) - ?.onSuccess(Dispatchers.IO) { content -> + val book = book + val webBook = webBook + if (book != null && webBook != null) { + webBook.getContent(book, chapter) + .onSuccess(Dispatchers.IO) { content -> if (content.isEmpty()) { contentLoadFinish( + book, chapter, App.INSTANCE.getString(R.string.content_empty), resetPageOffset = resetPageOffset @@ -254,17 +282,33 @@ object ReadBook { removeLoading(chapter.index) } else { BookHelp.saveContent(book, chapter, content) - contentLoadFinish(chapter, content, resetPageOffset = resetPageOffset) + contentLoadFinish( + book, + chapter, + content, + resetPageOffset = resetPageOffset + ) removeLoading(chapter.index) } - }?.onError { + }.onError { contentLoadFinish( + book, chapter, it.localizedMessage ?: "未知错误", resetPageOffset = resetPageOffset ) removeLoading(chapter.index) } + } else if (book != null) { + contentLoadFinish( + book, + chapter, + "没有书源", + resetPageOffset = resetPageOffset + ) + removeLoading(chapter.index) + } else { + removeLoading(chapter.index) } } @@ -282,10 +326,56 @@ object ReadBook { } } + fun searchResultPositions(pages: List, indexWithinChapter: Int, query: String): Array{ + // + // calculate search result's pageIndex + var content = "" + pages.map{ + content+= it.text + } + var count = 1 + var index = content.indexOf(query) + while(count != indexWithinChapter){ + index = content.indexOf(query, index + 1); + count += 1 + } + val contentPosition = index + var pageIndex = 0 + var length = pages[pageIndex].text.length + while (length < contentPosition){ + pageIndex += 1 + if (pageIndex >pages.size){ + pageIndex = pages.size + break + } + length += pages[pageIndex].text.length + } + + // calculate search result's lineIndex + val currentPage = pages[pageIndex] + var lineIndex = 0 + length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length + while (length < contentPosition){ + lineIndex += 1 + if (lineIndex >currentPage.textLines.size){ + lineIndex = currentPage.textLines.size + break + } + length += currentPage.textLines[lineIndex].text.length + } + + // charIndex + val currentLine = currentPage.textLines[lineIndex] + length -= currentLine.text.length + val charIndex = contentPosition - length + return arrayOf(pageIndex, lineIndex, charIndex) + } + /** * 内容加载完成 */ private fun contentLoadFinish( + book: Book, chapter: BookChapter, content: String, upContent: Boolean = true, @@ -293,35 +383,72 @@ 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, + book.name, webBook?.bookSource?.bookSourceUrl, content, - book!!.useReplaceRule + book.useReplaceRule ) when (chapter.index) { durChapterIndex -> { - curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + curTextChapter = + ChapterProvider.getTextChapter( + book, + chapter, + contents, + chapterSize, + imageStyle + ) if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset) callBack?.upView() curPageChanged() callBack?.contentLoadFinish() + ImageProvider.clearOut(durChapterIndex) } durChapterIndex - 1 -> { - prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + prevTextChapter = + ChapterProvider.getTextChapter( + book, + chapter, + contents, + chapterSize, + imageStyle + ) if (upContent) callBack?.upContent(-1, resetPageOffset) } durChapterIndex + 1 -> { - nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) + nextTextChapter = + ChapterProvider.getTextChapter( + book, + chapter, + contents, + chapterSize, + imageStyle + ) if (upContent) callBack?.upContent(1, resetPageOffset) } } } }.onError { it.printStackTrace() - App.INSTANCE.toast(it.localizedMessage ?: "ChapterProvider ERROR") + App.INSTANCE.toast("ChapterProvider ERROR:\n${it.getStackTraceString()}") + } + } + + private val imageStyle get() = webBook?.bookSource?.ruleContent?.imageStyle + + fun setCharset(charset: String) { + book?.let { + it.charset = charset + callBack?.loadChapterList(it) } + saveRead() } fun saveRead() { @@ -331,7 +458,7 @@ object ReadBook { book.durChapterTime = System.currentTimeMillis() book.durChapterIndex = durChapterIndex book.durChapterPos = durPageIndex - curTextChapter?.let { + App.db.bookChapterDao().getChapter(book.bookUrl, durChapterIndex)?.let { book.durChapterTitle = it.title } App.db.bookDao().update(book) @@ -340,9 +467,11 @@ object ReadBook { } interface CallBack { + fun loadChapterList(book: Book) 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..271190561 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 @@ -8,6 +8,7 @@ import android.view.Menu import android.view.MenuItem import io.legado.app.R import io.legado.app.base.BaseActivity +import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.utils.openUrl import kotlinx.android.synthetic.main.activity_about.* @@ -17,6 +18,7 @@ import org.jetbrains.anko.share class AboutActivity : BaseActivity(R.layout.activity_about) { override fun onActivityCreated(savedInstanceState: Bundle?) { + ll_about.background = ATH.getDialogBackground() val fTag = "aboutFragment" var aboutFragment = supportFragmentManager.findFragmentByTag(fTag) if (aboutFragment == null) aboutFragment = AboutFragment() @@ -24,14 +26,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 365381fbd..44a85a7fe 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 @@ -11,6 +11,7 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.lib.dialogs.alert import io.legado.app.ui.widget.dialog.TextDialog +import io.legado.app.utils.applyTint import io.legado.app.utils.openUrl import io.legado.app.utils.sendToClip import io.legado.app.utils.toast @@ -22,17 +23,19 @@ class AboutFragment : PreferenceFragmentCompat() { private val qqGroups = linkedMapOf( Pair("(QQ群VIP1)701903217", "-iolizL4cbJSutKRpeImHlXlpLDZnzeF"), Pair("(QQ群VIP2)263949160", "xwfh7_csb2Gf3Aw2qexEcEtviLfLfd4L"), + Pair("(QQ群VIP3)680280282", "_N0i7yZObjKSeZQvzoe2ej7j02kLnOOK"), Pair("(QQ群1)805192012", "6GlFKjLeIk5RhQnR3PNVDaKB6j10royo"), Pair("(QQ群2)773736122", "5Bm5w6OgLupXnICbYvbgzpPUgf0UlsJF"), Pair("(QQ群3)981838750", "g_Sgmp2nQPKqcZQ5qPcKLHziwX_mpps9"), Pair("(QQ群4)256929088", "czEJPLDnT4Pd9SKQ6RoRVzKhDxLchZrO"), - Pair("(QQ群5)811843556", "zKZ2UYGZ7o5CzcA6ylxzlqi21si_iqaX") + Pair("(QQ群5)811843556", "zKZ2UYGZ7o5CzcA6ylxzlqi21si_iqaX"), + Pair("(QQ群6)870270970", "FeCF8iSxfQbe90HPvGsvcqs5P5oSeY5n") ) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.about) findPreference("check_update")?.summary = - "${getString(R.string.version)} ${App.INSTANCE.versionName}" + "${getString(R.string.version)} ${App.versionName}" } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -46,12 +49,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) } @@ -89,7 +93,7 @@ class AboutFragment : PreferenceFragmentCompat() { } } } - }.show() + }.show().applyTint() } private fun joinQQGroup(key: String): Boolean { 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/about/ReadRecordActivity.kt b/app/src/main/java/io/legado/app/ui/about/ReadRecordActivity.kt new file mode 100644 index 000000000..f171433e4 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/about/ReadRecordActivity.kt @@ -0,0 +1,107 @@ +package io.legado.app.ui.about + +import android.content.Context +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.data.entities.ReadRecordShow +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.utils.applyTint +import kotlinx.android.synthetic.main.activity_read_record.* +import kotlinx.android.synthetic.main.item_read_record.* +import kotlinx.android.synthetic.main.item_read_record.view.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.anko.sdk27.listeners.onClick + +class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) { + + lateinit var adapter: RecordAdapter + + override fun onActivityCreated(savedInstanceState: Bundle?) { + initView() + initData() + } + + private fun initView() { + tv_book_name.setText(R.string.all_read_time) + recycler_view.layoutManager = LinearLayoutManager(this) + adapter = RecordAdapter(this) + recycler_view.adapter = adapter + iv_remove.onClick { + alert(R.string.delete, R.string.sure_del) { + okButton { + App.db.readRecordDao().clear() + initData() + } + noButton() + }.show().applyTint() + } + } + + private fun initData() { + launch(IO) { + val allTime = App.db.readRecordDao().allTime + withContext(Main) { + tv_read_time.text = formatDuring(allTime) + } + val readRecords = App.db.readRecordDao().allShow + withContext(Main) { + adapter.setItems(readRecords) + } + } + } + + inner class RecordAdapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_read_record) { + + override fun convert( + holder: ItemViewHolder, + item: ReadRecordShow, + payloads: MutableList + ) { + holder.itemView.apply { + tv_book_name.text = item.bookName + tv_read_time.text = formatDuring(item.readTime) + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + iv_remove.onClick { + alert(R.string.delete, R.string.sure_del) { + okButton { + getItem(holder.layoutPosition)?.let { + App.db.readRecordDao().deleteByName(it.bookName) + initData() + } + } + noButton() + }.show().applyTint() + } + } + } + + } + + fun formatDuring(mss: Long): String { + val days = mss / (1000 * 60 * 60 * 24) + val hours = mss % (1000 * 60 * 60 * 24) / (1000 * 60 * 60) + val minutes = mss % (1000 * 60 * 60) / (1000 * 60) + val seconds = mss % (1000 * 60) / 1000 + val d = if (days > 0) "${days}天" else "" + val h = if (hours > 0) "${hours}小时" else "" + val m = if (minutes > 0) "${minutes}分钟" else "" + val s = if (seconds > 0) "${seconds}秒" else "" + return "$d$h$m$s" + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt new file mode 100644 index 000000000..6c5741414 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt @@ -0,0 +1,50 @@ +package io.legado.app.ui.association + +import android.content.Intent +import android.os.Bundle +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.Theme +import io.legado.app.ui.main.MainActivity +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_translucence.* +import org.jetbrains.anko.startActivity +import org.jetbrains.anko.toast + + +class FileAssociationActivity : VMBaseActivity( + R.layout.activity_translucence, + theme = Theme.Transparent +) { + + override val viewModel: FileAssociationViewModel + get() = getViewModel(FileAssociationViewModel::class.java) + + override fun onActivityCreated(savedInstanceState: Bundle?) { + rotate_loading.show() + viewModel.errorLiveData.observe(this, { + rotate_loading.hide() + toast(it) + finish() + }) + viewModel.successLiveData.observe(this, { + rotate_loading.hide() + startActivity(it) + finish() + }) + intent.data?.let { data -> + viewModel.dispatchIndent(data) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + finish() + //返回后直接跳转到主页面 + gotoMainActivity() + } + + private fun gotoMainActivity() { + startActivity() + } +} diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt new file mode 100644 index 000000000..ad7700d33 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt @@ -0,0 +1,90 @@ +package io.legado.app.ui.association + +import android.app.Application +import android.content.Intent +import android.net.Uri +import android.text.TextUtils +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.MutableLiveData +import io.legado.app.base.BaseViewModel +import io.legado.app.model.localBook.LocalBook +import io.legado.app.ui.book.read.ReadBookActivity +import io.legado.app.utils.isJsonArray +import io.legado.app.utils.isJsonObject +import io.legado.app.utils.readText +import java.io.File + +class FileAssociationViewModel(application: Application) : BaseViewModel(application) { + + val successLiveData = MutableLiveData() + val errorLiveData = MutableLiveData() + + fun dispatchIndent(uri: Uri) { + execute { + val url: String + //如果是普通的url,需要根据返回的内容判断是什么 + if (uri.scheme == "file" || uri.scheme == "content") { + var scheme = "" + val content = if (uri.scheme == "file") { + val file = File(uri.path.toString()) + if (file.exists()) { + file.readText() + } else { + null + } + } else { + DocumentFile.fromSingleUri(context, uri)?.readText(context) + } + 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)) { + val book = if (uri.scheme == "content") { + LocalBook.importFile(uri.toString()) + } else { + LocalBook.importFile(uri.path.toString()) + } + val intent = Intent(context, ReadBookActivity::class.java) + intent.putExtra("bookUrl", book.bookUrl) + successLiveData.postValue(intent) + return@execute + } + } else { + errorLiveData.postValue("文件不存在") + return@execute + } + // 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 { + url = "yuedu://booksource/importonline?src=${uri.path}" + } + val data = Uri.parse(url) + val newIndent = Intent(Intent.ACTION_VIEW) + newIndent.data = data + successLiveData.postValue(newIndent) + return@execute + }.onError { + it.printStackTrace() + errorLiveData.postValue(it.localizedMessage) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt new file mode 100644 index 000000000..07b6451da --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt @@ -0,0 +1,236 @@ +package io.legado.app.ui.association + +import android.content.Context +import android.content.DialogInterface +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 androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.VMBaseActivity +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.constant.Theme +import io.legado.app.data.entities.BookSource +import io.legado.app.help.IntentDataHelp +import io.legado.app.help.SourceHelp +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.okButton +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModel +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.activity_translucence.* +import kotlinx.android.synthetic.main.dialog_recycler_view.* +import kotlinx.android.synthetic.main.item_source_import.view.* +import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.toast + +class ImportBookSourceActivity : VMBaseActivity( + R.layout.activity_translucence, + theme = Theme.Transparent +) { + + override val viewModel: ImportBookSourceViewModel + get() = getViewModel(ImportBookSourceViewModel::class.java) + + override fun onActivityCreated(savedInstanceState: Bundle?) { + rotate_loading.show() + viewModel.errorLiveData.observe(this, { + rotate_loading.hide() + errorDialog(it) + }) + viewModel.successLiveData.observe(this, { + rotate_loading.hide() + if (it > 0) { + successDialog() + } else { + errorDialog(getString(R.string.wrong_format)) + } + }) + initData() + } + + private fun initData() { + intent.getStringExtra("dataKey")?.let { + IntentDataHelp.getData(it)?.let { source -> + viewModel.importSource(source) + return + } + } + intent.getStringExtra("source")?.let { + viewModel.importSource(it) + return + } + intent.getStringExtra("filePath")?.let { + viewModel.importSourceFromFilePath(it) + return + } + intent.data?.let { + when (it.path) { + "/importonline" -> it.getQueryParameter("src")?.let { url -> + if (url.startsWith("http", false)) { + viewModel.importSource(url) + } else { + viewModel.importSourceFromFilePath(url) + } + } + else -> { + rotate_loading.hide() + toast(R.string.wrong_format) + finish() + } + } + } + } + + private fun errorDialog(msg: String) { + alert(getString(R.string.error), msg) { + okButton { } + }.show().applyTint().setOnDismissListener { + finish() + } + } + + private fun successDialog() { + val bundle = Bundle() + val allSourceKey = IntentDataHelp.putData(viewModel.allSources, "source") + bundle.putString("allSourceKey", allSourceKey) + val checkStatusKey = IntentDataHelp.putData(viewModel.sourceCheckState, "check") + bundle.putString("checkStatusKey", checkStatusKey) + val selectStatusKey = IntentDataHelp.putData(viewModel.selectStatus, "select") + bundle.putString("selectStatusKey", selectStatusKey) + SourcesDialog().apply { + arguments = bundle + }.show(supportFragmentManager, "SourceDialog") + } + + class SourcesDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { + + lateinit var adapter: SourcesAdapter + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout( + (dm.widthPixels * 0.9).toInt(), + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_recycler_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setTitle(R.string.import_book_source) + initMenu() + arguments?.let { + adapter = SourcesAdapter(requireContext()) + val allSources = + IntentDataHelp.getData>(it.getString("allSourceKey")) + adapter.sourceCheckState = + IntentDataHelp.getData>(it.getString("checkStatusKey"))!! + adapter.selectStatus = + IntentDataHelp.getData>(it.getString("selectStatusKey"))!! + + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + recycler_view.adapter = adapter + adapter.setItems(allSources) + tv_cancel.visible() + tv_cancel.onClick { + dismiss() + } + tv_ok.visible() + tv_ok.onClick { + importSelect() + dismiss() + } + } + } + + private fun initMenu() { + tool_bar.setOnMenuItemClickListener(this) + tool_bar.inflateMenu(R.menu.import_source) + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_select_all -> { + adapter.selectStatus.forEachIndexed { index, b -> + if (!b) { + adapter.selectStatus[index] = true + } + } + adapter.notifyDataSetChanged() + } + R.id.menu_un_select_all -> { + adapter.selectStatus.forEachIndexed { index, b -> + if (b) { + adapter.selectStatus[index] = false + } + } + adapter.notifyDataSetChanged() + } + } + return false + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + activity?.finish() + } + + private fun importSelect() { + val selectSource = arrayListOf() + adapter.selectStatus.forEachIndexed { index, b -> + if (b) { + selectSource.add(adapter.getItem(index)!!) + } + } + SourceHelp.insertBookSource(*selectSource.toTypedArray()) + } + + } + + class SourcesAdapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_source_import) { + + lateinit var sourceCheckState: ArrayList + lateinit var selectStatus: ArrayList + + override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList) { + holder.itemView.apply { + cb_source_name.isChecked = selectStatus[holder.layoutPosition] + cb_source_name.text = item.bookSourceName + tv_source_state.text = if (sourceCheckState[holder.layoutPosition]) { + "已存在" + } else { + "新书源" + } + + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + cb_source_name.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed) { + selectStatus[holder.layoutPosition] = isChecked + } + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt new file mode 100644 index 000000000..3fbad78f2 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt @@ -0,0 +1,117 @@ +package io.legado.app.ui.association + +import android.app.Application +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.MutableLiveData +import com.jayway.jsonpath.JsonPath +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.BookSource +import io.legado.app.help.http.HttpHelper +import io.legado.app.help.storage.OldRule +import io.legado.app.help.storage.Restore +import io.legado.app.utils.* +import java.io.File + +class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) { + + val errorLiveData = MutableLiveData() + val successLiveData = MutableLiveData() + + val allSources = arrayListOf() + val sourceCheckState = arrayListOf() + val selectStatus = arrayListOf() + + fun importSourceFromFilePath(path: String) { + execute { + val content = if (path.isContentPath()) { + //在前面被解码了,如果不进行编码,中文会无法识别 + val newPath = Uri.encode(path, ":/.") + DocumentFile.fromSingleUri(context, Uri.parse(newPath))?.readText(context) + } else { + val file = File(path) + if (file.exists()) { + file.readText() + } else { + null + } + } + if (content != null) { + importSource(content) + } else { + errorLiveData.postValue(context.getString(R.string.error_read_file)) + } + }.onError { + it.printStackTrace() + errorLiveData.postValue(context.getString(R.string.error_read_file)) + } + } + + fun importSource(text: String) { + execute { + val text1 = text.trim() + when { + text1.isJsonObject() -> { + val json = JsonPath.parse(text1) + val urls = json.read>("$.sourceUrls") + if (!urls.isNullOrEmpty()) { + urls.forEach { + importSourceUrl(it) + } + } else { + OldRule.jsonToBookSource(text1)?.let { + allSources.add(it) + } + } + } + text1.isJsonArray() -> { + val items: List> = Restore.jsonPath.parse(text1).read("$") + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + OldRule.jsonToBookSource(jsonItem.jsonString())?.let { + allSources.add(it) + } + } + } + text1.isAbsUrl() -> { + importSourceUrl(text1) + } + else -> throw Exception(context.getString(R.string.wrong_format)) + } + }.onError { + it.printStackTrace() + errorLiveData.postValue(it.localizedMessage ?: "") + }.onSuccess { + comparisonSource() + } + } + + private fun importSourceUrl(url: String) { + HttpHelper.simpleGet(url, "UTF-8").let { body -> + if (body == null) { + throw Exception(context.getString(R.string.error_get_data)) + } + val items: List> = Restore.jsonPath.parse(body).read("$") + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + OldRule.jsonToBookSource(jsonItem.jsonString())?.let { source -> + allSources.add(source) + } + } + } + } + + private fun comparisonSource() { + execute { + allSources.forEach { + val has = App.db.bookSourceDao().getBookSource(it.bookSourceUrl) != null + sourceCheckState.add(has) + selectStatus.add(!has) + } + successLiveData.postValue(allSources.size) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleActivity.kt new file mode 100644 index 000000000..12ae447d1 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleActivity.kt @@ -0,0 +1,96 @@ +package io.legado.app.ui.association + +import android.os.Bundle +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.Theme +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.help.IntentDataHelp +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.utils.applyTint +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_translucence.* +import org.jetbrains.anko.toast + +class ImportReplaceRuleActivity : VMBaseActivity( + R.layout.activity_translucence, + theme = Theme.Transparent +) { + + override val viewModel: ImportReplaceRuleViewModel + get() = getViewModel(ImportReplaceRuleViewModel::class.java) + + override fun onActivityCreated(savedInstanceState: Bundle?) { + rotate_loading.show() + viewModel.errorLiveData.observe(this, { + rotate_loading.hide() + errorDialog(it) + }) + viewModel.successLiveData.observe(this, { + rotate_loading.hide() + if (it.size > 0) { + successDialog(it) + } else { + errorDialog("格式不对") + } + }) + initData() + } + + private fun initData() { + intent.getStringExtra("dataKey")?.let { + IntentDataHelp.getData(it)?.let { source -> + viewModel.import(source) + return + } + } + intent.getStringExtra("source")?.let { + viewModel.import(it) + return + } + intent.getStringExtra("filePath")?.let { + viewModel.importFromFilePath(it) + return + } + intent.data?.let { + when (it.path) { + "/importonline" -> it.getQueryParameter("src")?.let { url -> + if (url.startsWith("http", false)) { + viewModel.import(url) + } else { + viewModel.importFromFilePath(url) + } + } + else -> { + rotate_loading.hide() + toast("格式不对") + finish() + } + } + } + } + + private fun errorDialog(msg: String) { + alert("导入出错", msg) { + okButton { } + }.show().applyTint().setOnDismissListener { + finish() + } + } + + private fun successDialog(allSource: ArrayList) { + alert("解析结果", "共${allSource.size}个替换规则,是否确认导入?") { + okButton { + App.db.replaceRuleDao().insert(*allSource.toTypedArray()) + } + noButton { + + } + }.show().applyTint().setOnDismissListener { + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt new file mode 100644 index 000000000..960bafa9b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt @@ -0,0 +1,65 @@ +package io.legado.app.ui.association + +import android.app.Application +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.MutableLiveData +import io.legado.app.R +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.help.http.HttpHelper +import io.legado.app.help.storage.OldReplace +import io.legado.app.utils.isAbsUrl +import io.legado.app.utils.isContentPath +import io.legado.app.utils.readText +import java.io.File + +class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) { + val errorLiveData = MutableLiveData() + val successLiveData = MutableLiveData>() + + private val allRules = arrayListOf() + + fun importFromFilePath(path: String) { + execute { + val content = if (path.isContentPath()) { + //在前面被解码了,如果不进行编码,中文会无法识别 + val newPath = Uri.encode(path, ":/.") + DocumentFile.fromSingleUri(context, Uri.parse(newPath))?.readText(context) + } else { + val file = File(path) + if (file.exists()) { + file.readText() + } else { + null + } + } + if (content != null) { + import(content) + } else { + errorLiveData.postValue(context.getString(R.string.error_read_file)) + } + }.onError { + it.printStackTrace() + errorLiveData.postValue(context.getString(R.string.error_read_file)) + } + } + + fun import(text: String) { + execute { + if (text.isAbsUrl()) { + HttpHelper.simpleGet(text)?.let { + val rules = OldReplace.jsonToReplaceRules(it) + allRules.addAll(rules) + } + } else { + val rules = OldReplace.jsonToReplaceRules(text) + allRules.addAll(rules) + } + }.onError { + errorLiveData.postValue(it.localizedMessage ?: "ERROR") + }.onSuccess { + successLiveData.postValue(allRules) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceActivity.kt new file mode 100644 index 000000000..31413a879 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceActivity.kt @@ -0,0 +1,236 @@ +package io.legado.app.ui.association + +import android.content.Context +import android.content.DialogInterface +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 androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.VMBaseActivity +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.constant.Theme +import io.legado.app.data.entities.RssSource +import io.legado.app.help.IntentDataHelp +import io.legado.app.help.SourceHelp +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.okButton +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModel +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.activity_translucence.* +import kotlinx.android.synthetic.main.dialog_recycler_view.* +import kotlinx.android.synthetic.main.item_source_import.view.* +import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.toast + +class ImportRssSourceActivity : VMBaseActivity( + R.layout.activity_translucence, + theme = Theme.Transparent +) { + + override val viewModel: ImportRssSourceViewModel + get() = getViewModel(ImportRssSourceViewModel::class.java) + + override fun onActivityCreated(savedInstanceState: Bundle?) { + rotate_loading.show() + viewModel.errorLiveData.observe(this, { + rotate_loading.hide() + errorDialog(it) + }) + viewModel.successLiveData.observe(this, { + rotate_loading.hide() + if (it > 0) { + successDialog() + } else { + errorDialog(getString(R.string.wrong_format)) + } + }) + initData() + } + + private fun initData() { + intent.getStringExtra("dataKey")?.let { + IntentDataHelp.getData(it)?.let { source -> + viewModel.importSource(source) + return + } + } + intent.getStringExtra("source")?.let { + viewModel.importSource(it) + return + } + intent.getStringExtra("filePath")?.let { + viewModel.importSourceFromFilePath(it) + return + } + intent.data?.let { + when (it.path) { + "/importonline" -> it.getQueryParameter("src")?.let { url -> + if (url.startsWith("http", false)) { + viewModel.importSource(url) + } else { + viewModel.importSourceFromFilePath(url) + } + } + else -> { + rotate_loading.hide() + toast(R.string.wrong_format) + finish() + } + } + } + } + + private fun errorDialog(msg: String) { + alert(getString(R.string.error), msg) { + okButton { } + }.show().applyTint().setOnDismissListener { + finish() + } + } + + private fun successDialog() { + val bundle = Bundle() + val allSourceKey = IntentDataHelp.putData(viewModel.allSources, "source") + bundle.putString("allSourceKey", allSourceKey) + val checkStatusKey = IntentDataHelp.putData(viewModel.sourceCheckState, "check") + bundle.putString("checkStatusKey", checkStatusKey) + val selectStatusKey = IntentDataHelp.putData(viewModel.selectStatus, "select") + bundle.putString("selectStatusKey", selectStatusKey) + SourcesDialog().apply { + arguments = bundle + }.show(supportFragmentManager, "SourceDialog") + } + + class SourcesDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { + + lateinit var adapter: SourcesAdapter + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout( + (dm.widthPixels * 0.9).toInt(), + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_recycler_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.title = getString(R.string.import_rss_source) + initMenu() + arguments?.let { + adapter = SourcesAdapter(requireContext()) + val allSources = + IntentDataHelp.getData>(it.getString("allSourceKey")) + adapter.sourceCheckState = + IntentDataHelp.getData>(it.getString("checkStatusKey"))!! + adapter.selectStatus = + IntentDataHelp.getData>(it.getString("selectStatusKey"))!! + + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + recycler_view.adapter = adapter + adapter.setItems(allSources) + tv_cancel.visible() + tv_cancel.onClick { + dismiss() + } + tv_ok.visible() + tv_ok.onClick { + importSelect() + dismiss() + } + } + } + + private fun initMenu() { + tool_bar.setOnMenuItemClickListener(this) + tool_bar.inflateMenu(R.menu.import_source) + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_select_all -> { + adapter.selectStatus.forEachIndexed { index, b -> + if (!b) { + adapter.selectStatus[index] = true + } + } + adapter.notifyDataSetChanged() + } + R.id.menu_un_select_all -> { + adapter.selectStatus.forEachIndexed { index, b -> + if (b) { + adapter.selectStatus[index] = false + } + } + adapter.notifyDataSetChanged() + } + } + return false + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + activity?.finish() + } + + private fun importSelect() { + val selectSource = arrayListOf() + adapter.selectStatus.forEachIndexed { index, b -> + if (b) { + selectSource.add(adapter.getItem(index)!!) + } + } + SourceHelp.insertRssSource(*selectSource.toTypedArray()) + } + + } + + class SourcesAdapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_source_import) { + + lateinit var sourceCheckState: ArrayList + lateinit var selectStatus: ArrayList + + override fun convert(holder: ItemViewHolder, item: RssSource, payloads: MutableList) { + holder.itemView.apply { + cb_source_name.isChecked = selectStatus[holder.layoutPosition] + cb_source_name.text = item.sourceName + tv_source_state.text = if (sourceCheckState[holder.layoutPosition]) { + "已存在" + } else { + "新订阅源" + } + + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + cb_source_name.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed) { + selectStatus[holder.layoutPosition] = isChecked + } + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt new file mode 100644 index 000000000..13b752a79 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt @@ -0,0 +1,112 @@ +package io.legado.app.ui.association + +import android.app.Application +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.MutableLiveData +import com.jayway.jsonpath.JsonPath +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.http.HttpHelper +import io.legado.app.help.storage.Restore +import io.legado.app.utils.* +import java.io.File + +class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { + + val errorLiveData = MutableLiveData() + val successLiveData = MutableLiveData() + + val allSources = arrayListOf() + val sourceCheckState = arrayListOf() + val selectStatus = arrayListOf() + + + fun importSourceFromFilePath(path: String) { + execute { + val content = if (path.isContentPath()) { + //在前面被解码了,如果不进行编码,中文会无法识别 + val newPath = Uri.encode(path, ":/.") + DocumentFile.fromSingleUri(context, Uri.parse(newPath))?.readText(context) + } else { + val file = File(path) + if (file.exists()) { + file.readText() + } else { + null + } + } + if (null != content) { + GSON.fromJsonArray(content)?.let { + allSources.addAll(it) + } + } + }.onSuccess { + comparisonSource() + } + } + + fun importSource(text: String) { + execute { + val text1 = text.trim() + when { + text1.isJsonObject() -> { + val json = JsonPath.parse(text1) + val urls = json.read>("$.sourceUrls") + if (!urls.isNullOrEmpty()) { + urls.forEach { + importSourceUrl(it) + } + } else { + GSON.fromJsonArray(text1)?.let { + allSources.addAll(it) + } + } + } + text1.isJsonArray() -> { + val items: List> = Restore.jsonPath.parse(text1).read("$") + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + GSON.fromJsonObject(jsonItem.jsonString())?.let { + allSources.add(it) + } + } + } + text1.isAbsUrl() -> { + importSourceUrl(text1) + } + else -> throw Exception(context.getString(R.string.wrong_format)) + } + }.onError { + errorLiveData.postValue("ImportError:${it.localizedMessage}") + }.onSuccess { + comparisonSource() + } + } + + private fun importSourceUrl(url: String) { + HttpHelper.simpleGet(url, "UTF-8")?.let { body -> + val items: List> = Restore.jsonPath.parse(body).read("$") + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + GSON.fromJsonObject(jsonItem.jsonString())?.let { source -> + allSources.add(source) + } + } + } + } + + private fun comparisonSource() { + execute { + allSources.forEach { + val has = App.db.rssSourceDao().getByKey(it.sourceUrl) != null + sourceCheckState.add(has) + selectStatus.add(!has) + } + successLiveData.postValue(allSources.size) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt index 3c960093b..f1d2883fb 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.widget.SeekBar -import androidx.lifecycle.Observer import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions.bitmapTransform @@ -27,6 +26,7 @@ import io.legado.app.service.AudioPlayService import io.legado.app.service.help.AudioPlay import io.legado.app.ui.book.changesource.ChangeSourceDialog import io.legado.app.ui.book.chapterlist.ChapterListActivity +import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_audio_play.* import org.apache.commons.lang3.time.DateFormatUtils @@ -36,7 +36,7 @@ import org.jetbrains.anko.startActivityForResult class AudioPlayActivity : - VMBaseActivity(R.layout.activity_audio_play, theme = Theme.Dark), + VMBaseActivity(R.layout.activity_audio_play, toolBarTheme = Theme.Dark), ChangeSourceDialog.CallBack { override val viewModel: AudioPlayViewModel @@ -47,8 +47,8 @@ class AudioPlayActivity : override fun onActivityCreated(savedInstanceState: Bundle?) { title_bar.transparent() - AudioPlay.titleData.observe(this, Observer { title_bar.title = it }) - AudioPlay.coverData.observe(this, Observer { upCover(it) }) + AudioPlay.titleData.observe(this, { title_bar.title = it }) + AudioPlay.coverData.observe(this, { upCover(it) }) viewModel.initData(intent) initView() } @@ -117,8 +117,8 @@ class AudioPlayActivity : private fun upCover(path: String?) { ImageLoader.load(this, path) - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) + .placeholder(CoverImageView.defaultDrawable) + .error(CoverImageView.defaultDrawable) .into(iv_cover) ImageLoader.load(this, path) .transition(DrawableTransitionOptions.withCrossFade(1500)) @@ -128,7 +128,7 @@ class AudioPlayActivity : } private fun defaultCover(): RequestBuilder { - return ImageLoader.load(this, R.drawable.image_cover_default) + return ImageLoader.load(this, CoverImageView.defaultDrawable) .apply(bitmapTransform(BlurTransformation(this, 25))) } @@ -152,7 +152,10 @@ class AudioPlayActivity : if (!AudioPlay.inBookshelf) { this.alert(title = getString(R.string.add_to_shelf)) { message = getString(R.string.check_add_bookshelf, it.name) - okButton { AudioPlay.inBookshelf = true } + okButton { + AudioPlay.inBookshelf = true + setResult(Activity.RESULT_OK) + } noButton { viewModel.removeFromBookshelf { super.finish() } } }.show().applyTint() } else { diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index 8f0cd9fa4..4c1a5a85b 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -8,10 +8,9 @@ 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.BookHelp -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import io.legado.app.service.help.AudioPlay import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext class AudioPlayViewModel(application: Application) : BaseViewModel(application) { @@ -93,9 +92,6 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) AudioPlay.book?.let { book1.order = it.order App.db.bookDao().delete(it) - } - withContext(Dispatchers.Main) { - } App.db.bookDao().insert(book1) AudioPlay.book = book1 diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index e7d78a6f4..f23a2008c 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -5,16 +5,15 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.widget.PopupMenu import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.AppConst import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton @@ -22,6 +21,7 @@ import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getPrefInt @@ -93,7 +93,7 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi private fun initGroupData() { groupLiveData?.removeObservers(this) groupLiveData = App.db.bookGroupDao().liveDataAll() - groupLiveData?.observe(this, Observer { + groupLiveData?.observe(this, { groupList.clear() groupList.addAll(it) adapter.notifyDataSetChanged() @@ -105,13 +105,13 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi booksLiveData?.removeObservers(this) booksLiveData = when (groupId) { - -1 -> App.db.bookDao().observeAll() - -2 -> App.db.bookDao().observeLocal() - -3 -> App.db.bookDao().observeAudio() - -11 -> App.db.bookDao().observeNoGroup() + AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll() + AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal() + AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio() + AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } - booksLiveData?.observe(this, Observer { list -> + booksLiveData?.observe(this, { list -> val books = when (getPrefInt(PreferKey.bookshelfSort)) { 1 -> list.sortedByDescending { it.latestChapterTime } 2 -> list.sortedBy { it.name } @@ -129,22 +129,22 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi .show(supportFragmentManager, "groupManage") R.id.menu_no_group -> { title_bar.subtitle = getString(R.string.no_group) - groupId = -11 + groupId = AppConst.bookGroupNone.groupId initBookData() } R.id.menu_all -> { title_bar.subtitle = item.title - groupId = -1 + groupId = AppConst.bookGroupAll.groupId initBookData() } R.id.menu_local -> { title_bar.subtitle = item.title - groupId = -2 + groupId = AppConst.bookGroupLocal.groupId initBookData() } R.id.menu_audio -> { title_bar.subtitle = item.title - groupId = -3 + groupId = AppConst.bookGroupAudio.groupId initBookData() } else -> if (item.groupId == R.id.menu_group) { diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt index 4917ea6cb..84e070259 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt @@ -8,8 +8,8 @@ import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.theme.backgroundColor +import io.legado.app.ui.widget.recycler.ItemTouchCallback import kotlinx.android.synthetic.main.item_arrange_book.view.* import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.sdk27.listeners.onClick 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/cache/CacheActivity.kt similarity index 54% rename from app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt rename to app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index b0f860527..6dbbf7589 100644 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.book.download +package io.legado.app.ui.book.cache import android.app.Activity import android.content.Intent @@ -6,94 +6,153 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager 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.service.help.CacheBook import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.ui.widget.dialog.TextListDialog import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_download.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.jetbrains.anko.toast +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArraySet -class DownloadActivity : VMBaseActivity(R.layout.activity_download), +class CacheActivity : VMBaseActivity(R.layout.activity_download), FileChooserDialog.CallBack, - DownloadAdapter.CallBack { + CacheAdapter.CallBack { private val exportRequestCode = 32 private val exportBookPathKey = "exportBookPath" - lateinit var adapter: DownloadAdapter - private var bookshelfLiveData: LiveData>? = null + lateinit var adapter: CacheAdapter + 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 val viewModel: CacheViewModel + get() = getViewModel(CacheViewModel::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 -> - Download.start( - this@DownloadActivity, + adapter.getItems().forEach { book -> + CacheBook.start( + this@CacheActivity, book.bookUrl, book.durChapterIndex, book.totalChapterNum ) } } else { - Download.stop(this@DownloadActivity) + CacheBook.stop(this@CacheActivity) } } + R.id.menu_log -> { + TextListDialog.show(supportFragmentManager, getString(R.string.log), CacheBook.logs) + } + 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) } private fun initRecyclerView() { recycler_view.layoutManager = LinearLayoutManager(this) - adapter = DownloadAdapter(this, this) + adapter = CacheAdapter(this, this) 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, { 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, { + groupList.clear() + groupList.addAll(it) + adapter.notifyDataSetChanged() + upMenu() + }) + } + private fun initCacheSize(books: List) { launch(IO) { books.forEach { book -> @@ -113,7 +172,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow } override fun observeLiveBus() { - observeEvent>>(EventBus.UP_DOWNLOAD) { + observeEvent>>(EventBus.UP_DOWNLOAD) { if (it.isEmpty()) { menu?.findItem(R.id.menu_download)?.setIcon(R.drawable.ic_play_24dp) menu?.applyTint(this) @@ -131,13 +190,13 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow override fun export(position: Int) { exportPosition = position - FilePicker.selectFolder(this, exportRequestCode) { - val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) - if (path.isNullOrEmpty()) { - toast(R.string.no_default_path) - } else { - startExport(path) - } + val default = arrayListOf() + val path = ACache.get(this@CacheActivity).getAsString(exportBookPathKey) + if (!path.isNullOrEmpty()) { + default.add(path) + } + FilePicker.selectFolder(this, exportRequestCode, otherActions = default) { + startExport(it) } } @@ -154,7 +213,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { exportRequestCode -> { - ACache.get(this@DownloadActivity).put(exportBookPathKey, currentPath) + ACache.get(this@CacheActivity).put(exportBookPathKey, currentPath) startExport(currentPath) } } @@ -169,7 +228,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) - ACache.get(this@DownloadActivity).put(exportBookPathKey, uri.toString()) + ACache.get(this@CacheActivity).put(exportBookPathKey, uri.toString()) startExport(uri.toString()) } } diff --git a/app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt similarity index 83% rename from app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt index 082e5aecc..f90853d77 100644 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.book.download +package io.legado.app.ui.book.cache import android.content.Context import android.widget.ImageView @@ -7,16 +7,18 @@ import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter -import io.legado.app.service.help.Download +import io.legado.app.service.help.CacheBook import kotlinx.android.synthetic.main.item_download.view.* import org.jetbrains.anko.sdk27.listeners.onClick +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArraySet -class DownloadAdapter(context: Context, private val callBack: CallBack) : +class CacheAdapter(context: Context, private val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_download) { val cacheChapters = hashMapOf>() - var downloadMap: HashMap>? = null + var downloadMap: ConcurrentHashMap>? = null override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { @@ -45,9 +47,9 @@ class DownloadAdapter(context: Context, private val callBack: CallBack) : iv_download.onClick { getItem(holder.layoutPosition)?.let { if (downloadMap?.containsKey(it.bookUrl) == true) { - Download.remove(context, it.bookUrl) + CacheBook.remove(context, it.bookUrl) } else { - Download.start(context, it.bookUrl, 0, it.totalChapterNum) + CacheBook.start(context, it.bookUrl, 0, it.totalChapterNum) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt new file mode 100644 index 000000000..0673aaaab --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -0,0 +1,105 @@ +package io.legado.app.ui.book.cache + +import android.app.Application +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppPattern +import io.legado.app.data.entities.Book +import io.legado.app.help.BookHelp +import io.legado.app.utils.* +import java.io.File + + +class CacheViewModel(application: Application) : BaseViewModel(application) { + + + fun export(path: String, book: Book, finally: (msg: String) -> Unit) { + execute { + if (path.isContentPath()) { + val uri = Uri.parse(path) + DocumentFile.fromTreeUri(context, uri)?.let { + export(it, book) + } + } else { + export(FileUtils.createFolderIfNotExist(path), book) + } + }.onError { + finally(it.localizedMessage ?: "ERROR") + }.onSuccess { + finally(context.getString(R.string.success)) + } + } + + private fun export(doc: DocumentFile, book: Book) { + DocumentUtils.createFileIfNotExist(doc, "${book.name} 作者:${book.author}.txt") + ?.writeText(context, getAllContents(book)) + App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> + BookHelp.getContent(book, chapter).let { content -> + content?.split("\n")?.forEachIndexed { index, text -> + val matcher = AppPattern.imgPattern.matcher(text) + if (matcher.find()) { + var src = matcher.group(1) + src = NetworkUtils.getAbsoluteURL(chapter.url, src) + src?.let { + val vFile = BookHelp.getImage(book, src) + if (vFile.exists()) { + DocumentUtils.createFileIfNotExist(doc, + "${index}-${MD5Utils.md5Encode16(src)}.jpg", + subDirs = arrayOf("${book.name}_${book.author}", + "images", + chapter.title)) + ?.writeBytes(context, vFile.readBytes()) + } + } + } + } + } + } + } + + private fun export(file: File, book: Book) { + FileUtils.createFileIfNotExist(file, "${book.name} 作者:${book.author}.txt") + .writeText(getAllContents(book)) + App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> + BookHelp.getContent(book, chapter).let { content -> + content?.split("\n")?.forEachIndexed { index, text -> + val matcher = AppPattern.imgPattern.matcher(text) + if (matcher.find()) { + var src = matcher.group(1) + src = NetworkUtils.getAbsoluteURL(chapter.url, src) + src?.let { + val vFile = BookHelp.getImage(book, src) + if (vFile.exists()) { + FileUtils.createFileIfNotExist(file, + "${book.name}_${book.author}", + "images", + chapter.title, + "${index}-${MD5Utils.md5Encode16(src)}.jpg") + .writeBytes(vFile.readBytes()) + } + } + } + } + } + } + } + + private fun getAllContents(book: Book): String { + val stringBuilder = StringBuilder() + stringBuilder.append(book.name) + .append("\n") + .append(context.getString(R.string.author_show, book.author)) + App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> + BookHelp.getContent(book, chapter).let { + stringBuilder.append("\n\n") + .append(chapter.title) + .append("\n") + .append(it) + } + } + return stringBuilder.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt index 85df58172..4a898fed4 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt @@ -8,12 +8,12 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.Toolbar import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager import io.legado.app.R import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.Theme +import io.legado.app.lib.theme.primaryColor import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.dialog_change_source.* @@ -60,6 +60,7 @@ class ChangeCoverDialog : BaseDialogFragment(), } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) tool_bar.setTitle(R.string.change_cover_source) viewModel.initData(arguments) initMenu() @@ -68,7 +69,7 @@ class ChangeCoverDialog : BaseDialogFragment(), private fun initMenu() { tool_bar.inflateMenu(R.menu.change_cover) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) } @@ -81,7 +82,7 @@ class ChangeCoverDialog : BaseDialogFragment(), override fun observeLiveBus() { super.observeLiveBus() - viewModel.searchStateData.observe(viewLifecycleOwner, Observer { + viewModel.searchStateData.observe(viewLifecycleOwner, { refresh_progress_bar.isAutoLoading = it if (it) { stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) @@ -90,7 +91,7 @@ class ChangeCoverDialog : BaseDialogFragment(), } tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) }) - viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { + viewModel.searchBooksLiveData.observe(viewLifecycleOwner, { val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) adapter.setItems(it) diffResult.dispatchUpdatesTo(adapter) diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 0c91fe0f9..fc8f6adf5 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -6,24 +6,31 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppPattern +import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook import io.legado.app.help.AppConfig -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.model.WebBook +import io.legado.app.help.coroutine.CompositeCoroutine +import io.legado.app.model.webBook.WebBook import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.Executors +import kotlin.math.min class ChangeCoverViewModel(application: Application) : BaseViewModel(application) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private val threadCount = AppConfig.threadCount + private var searchPool: ExecutorCoroutineDispatcher? = null var name: String = "" var author: String = "" - private var task: Coroutine<*>? = null + private var tasks = CompositeCoroutine() + private var bookSourceList = arrayListOf() val searchStateData = MutableLiveData() val searchBooksLiveData = MutableLiveData>() private val searchBooks = ArrayList() + @Volatile + private var searchIndex = -1 + fun initData(arguments: Bundle?) { arguments?.let { bundle -> bundle.getString("name")?.let { @@ -35,13 +42,18 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } + private fun initSearchPool() { + searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + searchIndex = -1 + } + fun loadDbSearchBook() { execute { App.db.searchBookDao().getEnableHasCover(name, author).let { searchBooks.addAll(it) if (it.size <= 1) { searchBooksLiveData.postValue(searchBooks) - search() + startSearch() } else { searchBooksLiveData.postValue(searchBooks) } @@ -49,50 +61,72 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } - fun search() { - task = execute { - val bookSourceList = App.db.bookSourceDao().allEnabled - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook(name, scope = this@execute, context = searchPool) - .timeout(30000L) - .onSuccess(Dispatchers.IO) { - if (it.isNotEmpty()) { - val searchBook = it[0] - if (searchBook.name == name && searchBook.author == author - && !searchBook.coverUrl.isNullOrEmpty() - ) { - App.db.searchBookDao().insert(searchBook) - if (!searchBooks.contains(searchBook)) { - searchBooks.add(searchBook) - searchBooksLiveData.postValue(searchBooks) - } - } - } - } - } - }.onStart { + private fun startSearch() { + execute { + bookSourceList.clear() + bookSourceList.addAll(App.db.bookSourceDao().allEnabled) searchStateData.postValue(true) - }.onCancel { - searchStateData.postValue(false) + initSearchPool() + for (i in 0 until threadCount) { + search() + } } + } - task?.invokeOnCompletion { - searchStateData.postValue(false) + private fun search() { + synchronized(this) { + if (searchIndex >= bookSourceList.lastIndex) { + return + } + searchIndex++ + val source = bookSourceList[searchIndex] + val variableBook = SearchBook(origin = source.bookSourceUrl) + val task = WebBook(source) + .searchBook(name, scope = this, context = searchPool!!, variableBook = variableBook) + .timeout(60000L) + .onSuccess(Dispatchers.IO) { + if (it.isNotEmpty()) { + val searchBook = it[0] + if (searchBook.name == name && searchBook.author == author + && !searchBook.coverUrl.isNullOrEmpty() + ) { + App.db.searchBookDao().insert(searchBook) + if (!searchBooks.contains(searchBook)) { + searchBooks.add(searchBook) + searchBooksLiveData.postValue(searchBooks) + } + } + } + } + .onFinally { + synchronized(this) { + if (searchIndex < bookSourceList.lastIndex) { + search() + } else { + searchIndex++ + } + if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size, + threadCount) + ) { + searchStateData.postValue(false) + } + } + } + tasks.add(task) } } fun stopSearch() { - if (task?.isActive == true) { - task?.cancel() + if (tasks.isEmpty) { + startSearch() } else { - search() + tasks.clear() } } override fun onCleared() { super.onCleared() - searchPool.close() + searchPool?.close() } } \ No newline at end of file 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..bbd627466 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 @@ -9,16 +9,15 @@ import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.PreferKey -import io.legado.app.constant.Theme import io.legado.app.data.entities.Book import io.legado.app.data.entities.SearchBook +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getPrefBoolean @@ -68,6 +67,7 @@ class ChangeSourceDialog : BaseDialogFragment(), } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) viewModel.initData(arguments) showTitle() initMenu() @@ -84,7 +84,7 @@ class ChangeSourceDialog : BaseDialogFragment(), private fun initMenu() { tool_bar.inflateMenu(R.menu.change_source) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = getPrefBoolean(PreferKey.changeSourceLoadToc) @@ -134,16 +134,16 @@ class ChangeSourceDialog : BaseDialogFragment(), } private fun initLiveData() { - viewModel.searchStateData.observe(viewLifecycleOwner, Observer { + viewModel.searchStateData.observe(viewLifecycleOwner, { refresh_progress_bar.isAutoLoading = it if (it) { stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) } else { stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp) } - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) }) - viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { + viewModel.searchBooksLiveData.observe(viewLifecycleOwner, { val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) adapter.setItems(it) diffResult.dispatchUpdatesTo(adapter) @@ -166,25 +166,18 @@ class ChangeSourceDialog : BaseDialogFragment(), override fun changeTo(searchBook: SearchBook) { val book = searchBook.toBook() - callBack?.oldBook?.let { oldBook -> - book.group = oldBook.group - book.durChapterIndex = oldBook.durChapterIndex - book.durChapterPos = oldBook.durChapterPos - book.durChapterTitle = oldBook.durChapterTitle - book.customCoverUrl = oldBook.customCoverUrl - book.customIntro = oldBook.customIntro - book.order = oldBook.order - if (book.coverUrl.isNullOrEmpty()) { - book.coverUrl = oldBook.getDisplayCover() - } - callBack?.changeTo(book) - } + book.upInfoFromOld(callBack?.oldBook) + callBack?.changeTo(book) dismiss() } 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..ec423c3dd 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 @@ -9,26 +10,36 @@ import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppPattern import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook import io.legado.app.help.AppConfig -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.model.WebBook +import io.legado.app.help.coroutine.CompositeCoroutine +import io.legado.app.model.webBook.WebBook import io.legado.app.utils.getPrefBoolean import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import org.jetbrains.anko.debug +import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.Executors +import kotlin.math.min class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private val threadCount = AppConfig.threadCount + private var searchPool: ExecutorCoroutineDispatcher? = null + val handler = Handler() val searchStateData = MutableLiveData() val searchBooksLiveData = MutableLiveData>() var name: String = "" var author: String = "" - private var task: Coroutine<*>? = null + private var tasks = CompositeCoroutine() private var screenKey: String = "" - private val searchBooks = hashSetOf() + private var bookSourceList = arrayListOf() + private val searchBooks = CopyOnWriteArraySet() + private var postTime = 0L + private val sendRunnable = Runnable { upAdapter() } + @Volatile + private var searchIndex = -1 fun initData(arguments: Bundle?) { arguments?.let { bundle -> @@ -41,13 +52,18 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } + private fun initSearchPool() { + searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + searchIndex = -1 + } + fun loadDbSearchBook() { execute { App.db.searchBookDao().getByNameAuthorEnable(name, author).let { searchBooks.addAll(it) if (it.size <= 1) { upAdapter() - search() + startSearch() } else { upAdapter() } @@ -55,9 +71,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) { @@ -70,38 +94,60 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio upAdapter() } - fun search() { - task = execute { - val bookSourceList = App.db.bookSourceDao().allEnabled - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook(name, scope = this@execute, context = searchPool) - .timeout(30000L) - .onSuccess(IO) { - it.forEach { searchBook -> - if (searchBook.name == name && searchBook.author == author) { - if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { - if (searchBook.tocUrl.isEmpty()) { - loadBookInfo(searchBook.toBook()) - } else { - loadChapter(searchBook.toBook()) - } + private fun startSearch() { + execute { + bookSourceList.clear() + bookSourceList.addAll(App.db.bookSourceDao().allEnabled) + searchStateData.postValue(true) + initSearchPool() + for (i in 0 until threadCount) { + search() + } + } + } + + private fun search() { + synchronized(this) { + if (searchIndex >= bookSourceList.lastIndex) { + return + } + searchIndex++ + val source = bookSourceList[searchIndex] + val variableBook = SearchBook() + val task = WebBook(source) + .searchBook(name, variableBook = variableBook, scope = this, context = searchPool!!) + .timeout(60000L) + .onSuccess(IO) { + it.forEach { searchBook -> + if (searchBook.name == name && searchBook.author == author) { + if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { + if (searchBook.tocUrl.isEmpty()) { + loadBookInfo(searchBook.toBook()) } else { - searchFinish(searchBook) + loadChapter(searchBook.toBook()) } - return@onSuccess + } else { + searchFinish(searchBook) } + return@forEach } } - } - }.onStart { - searchStateData.postValue(true) - }.onCancel { - searchStateData.postValue(false) - } - - task?.invokeOnCompletion { - searchStateData.postValue(false) + } + .onFinally { + synchronized(this) { + if (searchIndex < bookSourceList.lastIndex) { + search() + } else { + searchIndex++ + } + if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size, + threadCount) + ) { + searchStateData.postValue(false) + } + } + } + tasks.add(task) } } @@ -153,16 +199,29 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } fun stopSearch() { - if (task?.isActive == true) { - task?.cancel() + if (tasks.isEmpty) { + startSearch() } else { - search() + tasks.clear() + searchPool?.close() + searchStateData.postValue(false) } } override fun onCleared() { super.onCleared() - searchPool.close() + 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/chapterlist/BookmarkAdapter.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt index 05e3751c9..e9c7384ea 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt @@ -1,6 +1,5 @@ package io.legado.app.ui.book.chapterlist -import android.os.AsyncTask.execute import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -47,19 +46,17 @@ class BookmarkAdapter(val callback: Callback) : PagedListAdapter(R.layout.fragment_ } private fun initData() { - bookmarkLiveData?.removeObservers(viewLifecycleOwner) - bookmarkLiveData = - LivePagedListBuilder(App.db.bookmarkDao().observeByBook(viewModel.bookUrl), 20).build() - bookmarkLiveData?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) + viewModel.book?.let { book -> + bookmarkLiveData?.removeObservers(viewLifecycleOwner) + bookmarkLiveData = + LivePagedListBuilder( + App.db.bookmarkDao().observeByBook(book.bookUrl, book.name, book.author), 20 + ).build() + bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) }) + } } override fun startBookmarkSearch(newText: String?) { @@ -60,11 +73,12 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ newText ), 20 ).build() - bookmarkLiveData?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) + bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) }) } } - override fun open(bookmark: Bookmark) { + + override fun onClick(bookmark: Bookmark) { val bookmarkData = Intent() bookmarkData.putExtra("index", bookmark.chapterIndex) bookmarkData.putExtra("pageIndex", bookmark.pageIndex) @@ -72,7 +86,33 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ activity?.finish() } - override fun delBookmark(bookmark: Bookmark) { - App.db.bookmarkDao().delByBookmark(bookmark.bookUrl, bookmark.chapterName) + @SuppressLint("InflateParams") + override fun onLongClick(bookmark: Bookmark) { + viewModel.book?.let { book -> + requireContext().alert(R.string.bookmark) { + var editText: EditText? = null + message = book.name + " • " + bookmark.chapterName + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view.apply { + setHint(R.string.note_content) + setText(bookmark.content) + } + } + } + yesButton { + editText?.text?.toString()?.let { editContent -> + AsyncTask.execute { + bookmark.content = editContent + App.db.bookmarkDao().update(bookmark) + } + } + } + noButton() + neutralButton(R.string.delete) { + App.db.bookmarkDao().delete(bookmark) + } + }.show().applyTint().requestInputMethod() + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt index 7420fc047..58f19a6b4 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt @@ -28,9 +28,12 @@ class ChapterListActivity : VMBaseActivity(R.layout.activi override fun onActivityCreated(savedInstanceState: Bundle?) { tab_layout.isTabIndicatorFullWidth = false tab_layout.setSelectedTabIndicatorColor(accentColor) - viewModel.bookUrl = intent.getStringExtra("bookUrl") ?: "" - view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) - tab_layout.setupWithViewPager(view_pager) + intent.getStringExtra("bookUrl")?.let { + viewModel.initBook(it) { + view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) + tab_layout.setupWithViewPager(view_pager) + } + } } override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt index b9d20920b..5d4b3621b 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt @@ -1,7 +1,7 @@ package io.legado.app.ui.book.chapterlist import android.content.Context -import android.widget.TextView +import android.view.View import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter @@ -21,26 +21,23 @@ class ChapterListAdapter(context: Context, val callback: Callback) : override fun convert(holder: ItemViewHolder, item: BookChapter, payloads: MutableList) { with(holder.itemView) { + val isDur = callback.durChapterIndex() == item.index + val cached = callback.isLocalBook + || cacheFileNames.contains(BookHelp.formatChapterName(item)) if (payloads.isEmpty()) { - if (callback.durChapterIndex() == item.index) { + if (isDur) { tv_chapter_name.setTextColor(context.accentColor) } else { - tv_chapter_name.setTextColor(context.getCompatColor(R.color.tv_text_default)) + tv_chapter_name.setTextColor(context.getCompatColor(R.color.primaryText)) } tv_chapter_name.text = item.title if (!item.tag.isNullOrEmpty()) { tv_tag.text = item.tag tv_tag.visible() } - upHasCache( - tv_chapter_name, - cacheFileNames.contains(BookHelp.formatChapterName(item)) - ) + upHasCache(this, isDur, cached) } else { - upHasCache( - tv_chapter_name, - cacheFileNames.contains(BookHelp.formatChapterName(item)) - ) + upHasCache(this, isDur, cached) } } } @@ -53,11 +50,18 @@ class ChapterListAdapter(context: Context, val callback: Callback) : } } - private fun upHasCache(textView: TextView, contains: Boolean) { - textView.paint.isFakeBoldText = contains + private fun upHasCache(itemView: View, isDur: Boolean, cached: Boolean) = itemView.apply { + tv_chapter_name.paint.isFakeBoldText = cached + iv_checked.setImageResource(R.drawable.ic_outline_cloud_24) + iv_checked.visible(!cached) + if (isDur) { + iv_checked.setImageResource(R.drawable.ic_check) + iv_checked.visible() + } } interface Callback { + val isLocalBook: Boolean fun openChapter(bookChapter: BookChapter) fun durChapterIndex(): Int } diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt index d717cb25f..d4339bb23 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt @@ -1,11 +1,11 @@ package io.legado.app.ui.book.chapterlist +import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle import android.view.View import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseFragment @@ -13,9 +13,11 @@ import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp -import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.ui.widget.recycler.UpLinearLayoutManager import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.ColorUtils import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.observeEvent import kotlinx.android.synthetic.main.fragment_chapter_list.* @@ -32,7 +34,6 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme get() = getViewModelOfActivity(ChapterListViewModel::class.java) lateinit var adapter: ChapterListAdapter - private var book: Book? = null private var durChapterIndex = 0 private lateinit var mLayoutManager: UpLinearLayoutManager private var tocLiveData: LiveData>? = null @@ -40,6 +41,12 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.chapterCallBack = this + val bbg = bottomBackground + val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) + ll_chapter_base_info.setBackgroundColor(bbg) + tv_current_chapter_info.setTextColor(btc) + iv_chapter_top.setColorFilter(btc) + iv_chapter_bottom.setColorFilter(btc) initRecyclerView() initView() initBook() @@ -54,7 +61,6 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } private fun initView() { - ll_chapter_base_info.setBackgroundColor(backgroundColor) iv_chapter_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } iv_chapter_bottom.onClick { if (adapter.itemCount > 0) { @@ -66,15 +72,14 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } } + @SuppressLint("SetTextI18n") private fun initBook() { launch { - withContext(IO) { - book = App.db.bookDao().getBook(viewModel.bookUrl) - } initDoc() - book?.let { + viewModel.book?.let { durChapterIndex = it.durChapterIndex - tv_current_chapter_info.text = it.durChapterTitle + tv_current_chapter_info.text = + "${it.durChapterTitle}(${it.durChapterIndex + 1}/${it.totalChapterNum})" initCacheFileNames(it) } } @@ -83,7 +88,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme private fun initDoc() { tocLiveData?.removeObservers(this@ChapterListFragment) tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl) - tocLiveData?.observe(viewLifecycleOwner, Observer { + tocLiveData?.observe(viewLifecycleOwner, { adapter.setItems(it) if (!scrollToDurChapter) { mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) @@ -103,7 +108,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme override fun observeLiveBus() { observeEvent(EventBus.SAVE_CONTENT) { chapter -> - book?.bookUrl?.let { bookUrl -> + viewModel.book?.bookUrl?.let { bookUrl -> if (chapter.bookUrl == bookUrl) { adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) adapter.notifyItemChanged(chapter.index, true) @@ -118,12 +123,15 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } else { tocLiveData?.removeObservers(this) tocLiveData = App.db.bookChapterDao().liveDataSearch(viewModel.bookUrl, newText) - tocLiveData?.observe(viewLifecycleOwner, Observer { + tocLiveData?.observe(viewLifecycleOwner, { adapter.setItems(it) }) } } + override val isLocalBook: Boolean + get() = viewModel.book?.isLocalBook() == true + override fun durChapterIndex(): Int { return durChapterIndex } diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt index ad3361fd1..7671facc9 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt @@ -2,13 +2,25 @@ package io.legado.app.ui.book.chapterlist import android.app.Application +import io.legado.app.App import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.Book class ChapterListViewModel(application: Application) : BaseViewModel(application) { var bookUrl: String = "" + var book: Book? = null var chapterCallBack: ChapterListCallBack? = null var bookMarkCallBack: BookmarkCallBack? = null + fun initBook(bookUrl: String, success: () -> Unit) { + this.bookUrl = bookUrl + execute { + book = App.db.bookDao().getBook(bookUrl) + }.onSuccess { + success.invoke() + } + } + fun startChapterListSearch(newText: String?) { chapterCallBack?.startChapterListSearch(newText) } diff --git a/app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt deleted file mode 100644 index e5886d100..000000000 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.legado.app.ui.book.download - -import android.app.Application -import android.net.Uri -import androidx.documentfile.provider.DocumentFile -import io.legado.app.App -import io.legado.app.R -import io.legado.app.base.BaseViewModel -import io.legado.app.data.entities.Book -import io.legado.app.help.BookHelp -import io.legado.app.utils.DocumentUtils -import io.legado.app.utils.FileUtils -import io.legado.app.utils.isContentPath -import io.legado.app.utils.writeText -import java.io.File - - -class DownloadViewModel(application: Application) : BaseViewModel(application) { - - - fun export(path: String, book: Book, finally: (msg: String) -> Unit) { - execute { - if (path.isContentPath()) { - val uri = Uri.parse(path) - DocumentFile.fromTreeUri(context, uri)?.let { - export(it, book) - } - } else { - export(FileUtils.createFolderIfNotExist(path), book) - } - }.onError { - finally(it.localizedMessage ?: "ERROR") - }.onSuccess { - finally(context.getString(R.string.success)) - } - } - - private fun export(doc: DocumentFile, book: Book) { - DocumentUtils.createFileIfNotExist(doc, "${book.name}.txt") - ?.writeText(context, getAllContents(book)) - } - - private fun export(file: File, book: Book) { - FileUtils.createFileIfNotExist(file, "${book.name}.txt") - .writeText(getAllContents(book)) - } - - private fun getAllContents(book: Book): String { - val stringBuilder = StringBuilder() - stringBuilder.append(book.name) - .append("\n") - .append(context.getString(R.string.author_show, book.author)) - App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> - BookHelp.getContent(book, chapter).let { - stringBuilder.append("\n\n") - .append(chapter.title) - .append("\n") - .append(it) - } - } - return stringBuilder.toString() - } -} \ No newline at end of file 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..a447bb9e1 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 @@ -1,7 +1,6 @@ package io.legado.app.ui.book.explore import android.os.Bundle -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R @@ -27,7 +26,7 @@ class ExploreShowActivity : VMBaseActivity(R.layout.activi override fun onActivityCreated(savedInstanceState: Bundle?) { title_bar.title = intent.getStringExtra("exploreName") initRecyclerView() - viewModel.booksData.observe(this, Observer { upData(it) }) + viewModel.booksData.observe(this, { upData(it) }) viewModel.initData(intent) } @@ -72,7 +71,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/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index bd22b3968..f2d3c2ef8 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -7,13 +7,14 @@ import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import kotlinx.coroutines.Dispatchers.IO class ExploreShowViewModel(application: Application) : BaseViewModel(application) { val booksData = MutableLiveData>() private var bookSource: BookSource? = null + private val variableBook = SearchBook() private var exploreUrl: String? = null private var page = 1 @@ -32,7 +33,7 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application val source = bookSource val url = exploreUrl if (source != null && url != null) { - WebBook(source).exploreBook(url, page, this) + WebBook(source).exploreBook(url, page, variableBook, this) .timeout(30000L) .onSuccess(IO) { searchBooks -> booksData.postValue(searchBooks) diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 60010f46d..0782c7c39 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -10,25 +10,22 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme import io.legado.app.data.entities.BookGroup import io.legado.app.help.AppConfig -import io.legado.app.help.ItemTouchCallback -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.dialogs.customView -import io.legado.app.lib.dialogs.noButton -import io.legado.app.lib.dialogs.yesButton +import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel @@ -41,7 +38,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick import java.util.* import kotlin.collections.ArrayList -class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { +class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { private lateinit var viewModel: GroupViewModel private lateinit var adapter: GroupAdapter private val callBack: CallBack? get() = parentFragment as? CallBack @@ -62,8 +59,8 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { return inflater.inflate(R.layout.dialog_recycler_view, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) tool_bar.title = getString(R.string.group_manage) initData() initMenu() @@ -77,7 +74,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tv_ok.setTextColor(requireContext().accentColor) tv_ok.visible() tv_ok.onClick { dismiss() } - App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { + App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, { val diffResult = DiffUtil.calculateDiff(GroupDiffCallBack(ArrayList(adapter.getItems()), it)) adapter.setItems(it, diffResult) @@ -92,13 +89,15 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tool_bar.setOnMenuItemClickListener(this) tool_bar.inflateMenu(R.menu.book_group_manage) tool_bar.menu.let { - it.applyTint(requireContext(), Theme.getTheme()) + it.applyTint(requireContext()) it.findItem(R.id.menu_group_all) .isChecked = AppConfig.bookGroupAllShow it.findItem(R.id.menu_group_local) .isChecked = AppConfig.bookGroupLocalShow it.findItem(R.id.menu_group_audio) .isChecked = AppConfig.bookGroupAudioShow + it.findItem(R.id.menu_group_none) + .isChecked = AppConfig.bookGroupNoneShow } } @@ -120,6 +119,10 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { AppConfig.bookGroupAudioShow = item.isChecked callBack?.upGroup() } + R.id.menu_group_none -> { + item.isChecked = !item.isChecked + AppConfig.bookGroupNoneShow = item.isChecked + } } return true } @@ -165,6 +168,15 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { }.show().applyTint().requestInputMethod() } + private fun deleteGroup(bookGroup: BookGroup) { + alert(R.string.delete, R.string.sure_del) { + okButton { + viewModel.delGroup(bookGroup) + } + noButton() + }.show().applyTint() + } + private class GroupDiffCallBack( private val oldItems: List, private val newItems: List @@ -198,15 +210,15 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun convert(holder: ItemViewHolder, item: BookGroup, payloads: MutableList) { holder.itemView.apply { + setBackgroundColor(context.backgroundColor) tv_group.text = item.groupName - } } override fun registerListener(holder: ItemViewHolder) { holder.itemView.apply { tv_edit.onClick { getItem(holder.layoutPosition)?.let { editGroup(it) } } - tv_del.onClick { getItem(holder.layoutPosition)?.let { viewModel.delGroup(it) } } + tv_del.onClick { getItem(holder.layoutPosition)?.let { deleteGroup(it) } } } } diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt index 29df624e7..bee66bcc6 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt @@ -10,24 +10,24 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme import io.legado.app.data.entities.BookGroup -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel @@ -40,7 +40,7 @@ import kotlinx.android.synthetic.main.item_group_select.view.* import org.jetbrains.anko.sdk27.listeners.onClick import java.util.* -class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { +class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { companion object { const val tag = "groupSelectDialog" @@ -78,8 +78,8 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { return inflater.inflate(R.layout.dialog_book_group_picker, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) callBack = activity as? CallBack arguments?.let { groupId = it.getInt("groupId") @@ -92,10 +92,9 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { private fun initView() { tool_bar.title = getString(R.string.group_select) tool_bar.inflateMenu(R.menu.book_group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) - tool_bar.menu.findItem(R.id.menu_group_local).isVisible = false - tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false + tool_bar.menu.setGroupVisible(R.id.menu_groups, false) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) @@ -113,7 +112,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { } private fun initData() { - App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { + App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, { adapter.setItems(it) }) } @@ -174,6 +173,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun convert(holder: ItemViewHolder, item: BookGroup, payloads: MutableList) { holder.itemView.apply { + setBackgroundColor(context.backgroundColor) cb_group.text = item.groupName cb_group.isChecked = (groupId and item.groupId) > 0 } 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..011def56a 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 @@ -9,7 +9,6 @@ import android.view.Menu import android.view.MenuItem import android.widget.CheckBox import android.widget.LinearLayout -import androidx.lifecycle.Observer import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions.bitmapTransform @@ -23,6 +22,9 @@ import io.legado.app.help.BlurTransformation import io.legado.app.help.ImageLoader import io.legado.app.help.IntentDataHelp import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.book.changecover.ChangeCoverDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog @@ -32,10 +34,8 @@ import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.utils.dp -import io.legado.app.utils.getViewModel -import io.legado.app.utils.gone -import io.legado.app.utils.visible +import io.legado.app.ui.widget.image.CoverImageView +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_info.* import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity @@ -44,7 +44,7 @@ import org.jetbrains.anko.toast class BookInfoActivity : - VMBaseActivity(R.layout.activity_book_info, theme = Theme.Dark), + VMBaseActivity(R.layout.activity_book_info, toolBarTheme = Theme.Dark), GroupSelectDialog.CallBack, ChapterListAdapter.CallBack, ChangeSourceDialog.CallBack, @@ -52,14 +52,21 @@ class BookInfoActivity : private val requestCodeChapterList = 568 private val requestCodeSourceEdit = 562 + private val requestCodeRead = 432 override val viewModel: BookInfoViewModel get() = getViewModel(BookInfoViewModel::class.java) + @SuppressLint("PrivateResource") override fun onActivityCreated(savedInstanceState: Bundle?) { title_bar.transparent() - viewModel.bookData.observe(this, Observer { showBook(it) }) - viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) + arc_view.setBgColor(backgroundColor) + ll_info.setBackgroundColor(backgroundColor) + scroll_view.setBackgroundColor(backgroundColor) + fl_action.setBackgroundColor(bottomBackground) + tv_shelf.setTextColor(getPrimaryTextColor(ColorUtils.isColorLight(bottomBackground))) + viewModel.bookData.observe(this, { showBook(it) }) + viewModel.chapterListData.observe(this, { upLoading(false, it) }) viewModel.initData(intent) initOnClick() } @@ -86,7 +93,10 @@ class BookInfoActivity : R.id.menu_refresh -> { upLoading(true) viewModel.bookData.value?.let { - viewModel.loadBookInfo(it) + if (it.isLocalBook()) { + it.tocUrl = "" + } + viewModel.loadBookInfo(it, false) } } R.id.menu_can_update -> { @@ -99,6 +109,7 @@ class BookInfoActivity : toast(R.string.after_add_bookshelf) } } + R.id.menu_clear_cache -> viewModel.clearCache() } return super.onCompatOptionsItemSelected(item) } @@ -138,7 +149,7 @@ class BookInfoActivity : } private fun defaultCover(): RequestBuilder { - return ImageLoader.load(this, R.drawable.image_cover_default) + return ImageLoader.load(this, CoverImageView.defaultDrawable) .apply(bitmapTransform(BlurTransformation(this, 25))) } @@ -295,11 +306,13 @@ class BookInfoActivity : private fun startReadActivity(book: Book) { when (book.type) { - BookType.audio -> startActivity( + BookType.audio -> startActivityForResult( + requestCodeRead, Pair("bookUrl", book.bookUrl), Pair("inBookshelf", viewModel.inBookshelf) ) - else -> startActivity( + else -> startActivityForResult( + requestCodeRead, Pair("bookUrl", book.bookUrl), Pair("inBookshelf", viewModel.inBookshelf), Pair("key", IntentDataHelp.putData(book)) @@ -348,23 +361,29 @@ class BookInfoActivity : override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { - requestCodeSourceEdit -> if (resultCode == Activity.RESULT_OK) { - viewModel.initData(intent) - } - requestCodeChapterList -> if (resultCode == Activity.RESULT_OK) { - viewModel.bookData.value?.let { - data?.getIntExtra("index", it.durChapterIndex)?.let { index -> - if (it.durChapterIndex != index) { - it.durChapterIndex = index - it.durChapterPos = 0 + requestCodeSourceEdit -> + if (resultCode == Activity.RESULT_OK) { + viewModel.upEditBook() + } + requestCodeChapterList -> + if (resultCode == Activity.RESULT_OK) { + viewModel.bookData.value?.let { + data?.getIntExtra("index", it.durChapterIndex)?.let { index -> + if (it.durChapterIndex != index) { + it.durChapterIndex = index + it.durChapterPos = 0 + } + startReadActivity(it) } - startReadActivity(it) + } + } else { + if (!viewModel.inBookshelf) { + viewModel.delBook() } } - } else { - if (!viewModel.inBookshelf) { - viewModel.delBook() - } + requestCodeRead -> if (resultCode == Activity.RESULT_OK) { + viewModel.inBookshelf = true + upTvBookshelf() } } } 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 e44018ee7..fb2a16bf8 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 @@ -9,9 +9,9 @@ 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.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.model.webBook.WebBook +import io.legado.app.service.help.ReadBook import kotlinx.coroutines.Dispatchers.IO class BookInfoViewModel(application: Application) : BaseViewModel(application) { @@ -22,13 +22,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) } } } @@ -49,15 +49,15 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { } fun loadBookInfo( - book: Book, - changeDruChapterIndex: ((chapters: List) -> Unit)? = null + book: Book, canReName: Boolean = true, + changeDruChapterIndex: ((chapters: List) -> Unit)? = null, ) { execute { if (book.isLocalBook()) { loadChapter(book, changeDruChapterIndex) } else { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> - WebBook(bookSource).getBookInfo(book, this) + WebBook(bookSource).getBookInfo(book, this, canReName = canReName) .onSuccess(IO) { bookData.postValue(book) if (inBookshelf) { @@ -81,7 +81,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { ) { execute { if (book.isLocalBook()) { - AnalyzeTxtFile.analyze(context, book).let { + LocalBook.getChapterList(book).let { App.db.bookDao().update(book) App.db.bookChapterDao().insert(*it.toTypedArray()) chapterListData.postValue(it) @@ -112,6 +112,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { toast(R.string.error_no_source) } } + }.onError { + toast("LoadTocError:${it.localizedMessage}") } } @@ -129,21 +131,16 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { } } - fun changeTo(book: Book) { + fun changeTo(newBook: Book) { execute { if (inBookshelf) { - bookData.value?.let { - book.group = it.group - book.order = it.order - App.db.bookDao().delete(it) - } - App.db.bookDao().insert(book) + bookData.value?.changeTo(newBook) } - bookData.postValue(book) - if (book.tocUrl.isEmpty()) { - loadBookInfo(book) { upChangeDurChapterIndex(book, it) } + bookData.postValue(newBook) + if (newBook.tocUrl.isEmpty()) { + loadBookInfo(newBook, false) { upChangeDurChapterIndex(newBook, it) } } else { - loadChapter(book) { upChangeDurChapterIndex(book, it) } + loadChapter(newBook) { upChangeDurChapterIndex(newBook, it) } } } } @@ -156,8 +153,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { chapters ) book.durChapterTitle = chapters[book.durChapterIndex].title - App.db.bookDao().insert(book) - App.db.bookChapterDao().insert(*chapters.toTypedArray()) + if (inBookshelf) { + App.db.bookDao().insert(book) + App.db.bookChapterDao().insert(*chapters.toTypedArray()) + } bookData.postValue(book) chapterListData.postValue(chapters) } @@ -169,7 +168,14 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { if (book.order == 0) { book.order = App.db.bookDao().maxOrder + 1 } + App.db.bookDao().getBook(book.name, book.author)?.let { + book.durChapterPos = it.durChapterPos + book.durChapterTitle = it.durChapterTitle + } App.db.bookDao().insert(book) + if (ReadBook.book?.name == book.name && ReadBook.book?.author == book.author) { + ReadBook.book = book + } } }.onSuccess { success?.invoke() @@ -192,6 +198,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { if (book.order == 0) { book.order = App.db.bookDao().maxOrder + 1 } + App.db.bookDao().getBook(book.name, book.author)?.let { + book.durChapterPos = it.durChapterPos + book.durChapterTitle = it.durChapterTitle + } App.db.bookDao().insert(book) } chapterListData.value?.let { @@ -206,7 +216,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) { execute { bookData.value?.let { - App.db.bookDao().delete(it) + it.delete() inBookshelf = false if (it.isLocalBook()) { LocalBook.deleteBook(it, deleteOriginal) @@ -216,4 +226,22 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { success?.invoke() } } + + fun clearCache() { + execute { + BookHelp.clearCache(bookData.value!!) + }.onSuccess { + toast(R.string.clear_cache_success) + }.onError { + toast(it.stackTraceToString()) + } + } + + fun upEditBook() { + bookData.value?.let { + App.db.bookDao().getBook(it.bookUrl)?.let { book -> + bookData.postValue(book) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/ChapterListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/info/ChapterListAdapter.kt index 4af4e96fa..cacd737cd 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/ChapterListAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/ChapterListAdapter.kt @@ -19,7 +19,7 @@ class ChapterListAdapter(context: Context, var callBack: CallBack) : if (item.index == callBack.durChapterIndex()) { tv_chapter_name.setTextColor(context.accentColor) } else { - tv_chapter_name.textColorResource = R.color.tv_text_secondary + tv_chapter_name.textColorResource = R.color.secondaryText } } 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..211c56cb3 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,26 +1,35 @@ package io.legado.app.ui.book.info.edit import android.app.Activity +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem -import androidx.lifecycle.Observer +import androidx.documentfile.provider.DocumentFile import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.data.entities.Book +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat import io.legado.app.ui.book.changecover.ChangeCoverDialog -import io.legado.app.utils.getViewModel +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_info_edit.* import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.toast +import java.io.File class BookInfoEditActivity : VMBaseActivity(R.layout.activity_book_info_edit), ChangeCoverDialog.CallBack { + + private val resultSelectCover = 132 + override val viewModel: BookInfoEditViewModel get() = getViewModel(BookInfoEditViewModel::class.java) override fun onActivityCreated(savedInstanceState: Bundle?) { - viewModel.bookData.observe(this, Observer { upView(it) }) + viewModel.bookData.observe(this, { upView(it) }) if (viewModel.bookData.value == null) { intent.getStringExtra("bookUrl")?.let { viewModel.loadBook(it) @@ -47,6 +56,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 +93,64 @@ 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() } + + private fun coverChangeTo(uri: Uri) { + if (uri.toString().isContentPath()) { + val doc = DocumentFile.fromSingleUri(this, uri) + doc?.name?.let { + var file = this.externalFilesDir + file = FileUtils.createFileIfNotExist(file, "covers", it) + kotlin.runCatching { + DocumentUtils.readBytes(this, doc.uri) + }.getOrNull()?.let { byteArray -> + file.writeBytes(byteArray) + coverChangeTo(file.absolutePath) + } ?: toast("获取文件出错") + } + } else { + PermissionsCompat.Builder(this) + .addPermissions( + Permissions.READ_EXTERNAL_STORAGE, + Permissions.WRITE_EXTERNAL_STORAGE + ) + .rationale(R.string.bg_image_per) + .onGranted { + RealPathUtil.getPath(this, uri)?.let { path -> + val imgFile = File(path) + if (imgFile.exists()) { + var file = this.externalFilesDir + file = FileUtils.createFileIfNotExist(file, "covers", imgFile.name) + file.writeBytes(imgFile.readBytes()) + coverChangeTo(file.absolutePath) + } + } + } + .request() + } + } + + 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) + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt index 66602c1d7..a0604035e 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.Book +import io.legado.app.service.help.ReadBook class BookInfoEditViewModel(application: Application) : BaseViewModel(application) { var book: Book? = null @@ -21,6 +22,9 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio fun saveBook(book: Book, success: (() -> Unit)?) { execute { + if (ReadBook.book?.bookUrl == book.bookUrl) { + ReadBook.book = book + } App.db.bookDao().insert(book) }.onSuccess { success?.invoke() diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt index 3ce47ee99..86c5e5ecd 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt @@ -12,7 +12,6 @@ import android.view.View import androidx.appcompat.widget.PopupMenu import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R @@ -20,6 +19,7 @@ import io.legado.app.base.VMBaseActivity import io.legado.app.help.AppConfig import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat +import io.legado.app.lib.theme.backgroundColor import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.widget.SelectActionBar @@ -62,6 +62,7 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity } private fun initView() { + lay_top.setBackgroundColor(backgroundColor) recycler_view.layoutManager = LinearLayoutManager(this) adapter = ImportBookAdapter(this, this) recycler_view.adapter = adapter @@ -112,7 +113,7 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity private fun initData() { localUriLiveData?.removeObservers(this) localUriLiveData = App.db.bookDao().observeLocalUri() - localUriLiveData?.observe(this, Observer { + localUriLiveData?.observe(this, { adapter.upBookHas(it) }) } @@ -172,7 +173,10 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity val item = docList[i] if (item.name.startsWith(".")) { docList.removeAt(i) - } else if (!item.isDir && !item.name.endsWith(".txt", true)) { + } else if (!item.isDir + && !item.name.endsWith(".txt", true) + && !item.name.endsWith(".epub", true) + ) { docList.removeAt(i) } } @@ -196,7 +200,9 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity Uri.parse(it.absolutePath) ) ) - } else if (it.name.endsWith(".txt", true)) { + } else if (it.name.endsWith(".txt", true) + || it.name.endsWith(".epub", true) + ) { docList.add( DocItem( it.name, diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 043c419eb..7120e047a 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,15 +4,17 @@ 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 +import android.util.Log import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.view.get import androidx.core.view.isVisible import androidx.core.view.size -import androidx.lifecycle.Observer import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import io.legado.app.BuildConfig import io.legado.app.R @@ -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 @@ -45,6 +48,8 @@ import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.delegate.PageDelegate +import io.legado.app.ui.book.searchContent.SearchListActivity +import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.replacerule.ReplaceRuleActivity @@ -53,7 +58,11 @@ import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.view_read_menu.* +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity @@ -69,44 +78,51 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo ReadAloudDialog.CallBack, ChangeSourceDialog.CallBack, ReadBook.CallBack, + AutoReadDialog.CallBack, TocRegexDialog.CallBack, ReplaceEditDialog.CallBack, ColorPickerDialogListener { private val requestCodeChapterList = 568 private val requestCodeEditSource = 111 private val requestCodeReplace = 312 + private val requestCodeSearchResult = 123 private var menu: Menu? = null private var textActionMenu: TextActionMenu? = null override val viewModel: ReadBookViewModel get() = getViewModel(ReadBookViewModel::class.java) + override val scope: CoroutineScope get() = this override val isInitFinish: Boolean get() = viewModel.isInitFinish private val mHandler = Handler() - private val keepScreenRunnable: Runnable = Runnable { Help.keepScreenOn(window, false) } - + private val keepScreenRunnable: Runnable = + Runnable { ReadBookActivityHelp.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 + private var loadStates: Boolean = false override val pageFactory: TextPageFactory get() = page_view.pageFactory override val headerHeight: Int get() = page_view.curPage.headerHeight override fun onCreate(savedInstanceState: Bundle?) { ReadBook.msg = null - Help.setOrientation(this) + ReadBookActivityHelp.setOrientation(this) super.onCreate(savedInstanceState) } override fun onActivityCreated(savedInstanceState: Bundle?) { - Help.upLayoutInDisplayCutoutMode(window) + ReadBookActivityHelp.upLayoutInDisplayCutoutMode(window) initView() upScreenTimeOut() ReadBook.callBack = this - ReadBook.titleDate.observe(this, Observer { + ReadBook.titleDate.observe(this) { title_bar.title = it upMenu() upView() - }) + } viewModel.initData(intent) } @@ -117,11 +133,13 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) + page_view.upStatusBar() ReadBook.loadContent(resetPageOffset = false) } override fun onResume() { super.onResume() + ReadBook.readStartTime = System.currentTimeMillis() upSystemUiVisibility() timeBatteryReceiver = TimeBatteryReceiver.register(this) page_view.upTime() @@ -129,11 +147,31 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onPause() { super.onPause() + ReadBook.saveRead() timeBatteryReceiver?.let { unregisterReceiver(it) timeBatteryReceiver = null } upSystemUiVisibility() + if (!BuildConfig.DEBUG) { + SyncBookProgress.uploadBookProgress() + Backup.autoBack(this) + } + } + + 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) + } + } } /** @@ -190,7 +228,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) { @@ -220,12 +258,12 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo viewModel.refreshContent(it) } } - R.id.menu_download -> Help.showDownloadDialog(this) - R.id.menu_add_bookmark -> Help.showBookMark(this) + R.id.menu_download -> ReadBookActivityHelp.showDownloadDialog(this) + R.id.menu_add_bookmark -> ReadBookActivityHelp.showBookMark(this) R.id.menu_copy_text -> TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent()) R.id.menu_update_toc -> ReadBook.book?.let { - viewModel.loadChapterList(it) + loadChapterList(it) } R.id.menu_enable_replace -> ReadBook.book?.let { it.useReplaceRule = !it.useReplaceRule @@ -233,7 +271,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, @@ -245,6 +286,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo Pair("loginUrl", it.loginUrl) ) } + R.id.menu_set_charset -> ReadBookActivityHelp.showCharsetConfig(this) } return super.onCompatOptionsItemSelected(item) } @@ -275,6 +317,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 @@ -289,18 +343,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) } @@ -339,6 +381,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo toast(R.string.read_aloud_pause) return true } + if (isAutoPage) { + autoPageStop() + return true + } } } } @@ -347,7 +393,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } /** - * view触摸 + * view触摸,文字选择 */ @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { @@ -436,7 +482,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onMenuItemSelected(itemId: Int): Boolean { when (itemId) { R.id.menu_replace -> { - ReplaceEditDialog.show(supportFragmentManager, pattern = selectedText) + val scopes = arrayListOf() + ReadBook.book?.name?.let { + scopes.add(it) + } + ReadBook.bookSource?.bookSourceUrl?.let { + scopes.add(it) + } + ReplaceEditDialog.show( + supportFragmentManager, + pattern = selectedText, + scope = scopes.joinToString(";") + ) return true } } @@ -449,7 +506,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onMenuActionFinally() { textActionMenu?.dismiss() page_view.curPage.cancelSelect() - page_view.pageDelegate?.isTextSelected = false + page_view.isTextSelected = false } /** @@ -461,6 +518,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 } @@ -469,6 +527,11 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo return false } + override fun loadChapterList(book: Book) { + ReadBook.upMsg(getString(R.string.toc_updateing)) + viewModel.loadChapterList(book) + } + /** * 内容加载完成 */ @@ -477,15 +540,19 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo intent.removeExtra("readAloud") ReadBook.readAloud() } + loadStates = true } /** * 更新内容 */ override fun upContent(relativePosition: Int, resetPageOffset: Boolean) { + autoPageProgress = 0 launch { page_view.upContent(relativePosition, resetPageOffset) + seek_read_page.progress = ReadBook.durPageIndex } + loadStates = false } /** @@ -502,18 +569,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 } @@ -534,10 +605,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() + } } } @@ -552,7 +629,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) } /** @@ -574,6 +677,19 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } + /** + * 打开搜索界面 + */ + //todo: change request code + override fun openSearchList() { + ReadBook.book?.let { + startActivityForResult( + requestCodeSearchResult, + Pair("bookUrl", it.bookUrl) + ) + } + } + /** * 替换规则变化 */ @@ -602,7 +718,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo * 更新状态栏,导航栏 */ override fun upSystemUiVisibility() { - Help.upSystemUiVisibility(this, !read_menu.isVisible) + ReadBookActivityHelp.upSystemUiVisibility(window, isInMultiWindow, !read_menu.isVisible) + upNavigationBarColor() } /** @@ -653,11 +770,36 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo requestCodeChapterList -> data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index -> if (index != ReadBook.durChapterIndex) { + val pageIndex = data.getIntExtra("pageIndex", 0) + viewModel.openChapter(index, pageIndex) + } + } + requestCodeSearchResult -> + data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index -> + launch(IO){ + val indexWithinChapter = data.getIntExtra("indexWithinChapter", 0) + val query = data.getStringExtra("query") viewModel.openChapter(index) + // block until load correct chapter and pages + var pages = ReadBook.curTextChapter?.pages + while (ReadBook.durChapterIndex != index || pages == null ){ + delay(100L) + pages = ReadBook.curTextChapter?.pages + } + val positions = ReadBook.searchResultPositions(pages, indexWithinChapter, query!!) + //todo: show selected text + val job1 = async(Main){ + ReadBook.skipToPage(positions[0]) + page_view.curPage.selectStartMoveIndex(positions[0], positions[1], 0) + page_view.curPage.selectEndMoveIndex(positions[0], positions[1], 0 + query.length ) + page_view.isTextSelected = true + } + job1.await() } } requestCodeReplace -> onReplaceRuleSave() } + } } @@ -666,7 +808,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo if (!ReadBook.inBookshelf) { this.alert(title = getString(R.string.add_to_shelf)) { message = getString(R.string.check_add_bookshelf, it.name) - okButton { ReadBook.inBookshelf = true } + okButton { + ReadBook.inBookshelf = true + setResult(Activity.RESULT_OK) + } noButton { viewModel.removeFromBookshelf { super.finish() } } }.show().applyTint() } else { @@ -705,6 +850,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo observeEvent(EventBus.UP_CONFIG) { upSystemUiVisibility() page_view.upBg() + page_view.upTipStyle() page_view.upStyle() if (it) { ReadBook.loadContent(resetPageOffset = false) @@ -741,6 +887,9 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo observeEvent(PreferKey.textSelectAble) { page_view.curPage.upSelectAble(it) } + observeEvent(PreferKey.showBrightnessView) { + read_menu.upBrightnessState() + } } private fun upScreenTimeOut() { @@ -755,16 +904,16 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo */ override fun screenOffTimerStart() { if (screenTimeOut < 0) { - Help.keepScreenOn(window, true) + ReadBookActivityHelp.keepScreenOn(window, true) return } val t = screenTimeOut - sysScreenOffTime if (t > 0) { mHandler.removeCallbacks(keepScreenRunnable) - Help.keepScreenOn(window, true) + ReadBookActivityHelp.keepScreenOn(window, true) mHandler.postDelayed(keepScreenRunnable, screenTimeOut) } else { - Help.keepScreenOn(window, false) + ReadBookActivityHelp.keepScreenOn(window, false) } } } \ 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/ReadBookActivityHelp.kt similarity index 64% rename from app/src/main/java/io/legado/app/ui/book/read/Help.kt rename to app/src/main/java/io/legado/app/ui/book/read/ReadBookActivityHelp.kt index f5144977e..2b7e8fd82 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/ReadBookActivityHelp.kt @@ -6,22 +6,23 @@ import android.content.Context import android.content.pm.ActivityInfo import android.os.AsyncTask import android.os.Build -import android.view.* -import android.view.View.NO_ID +import android.view.LayoutInflater +import android.view.View +import android.view.Window +import android.view.WindowManager import android.widget.EditText import io.legado.app.App import io.legado.app.R import io.legado.app.data.entities.Bookmark import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.dialogs.customView -import io.legado.app.lib.dialogs.noButton -import io.legado.app.lib.dialogs.yesButton +import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ThemeStore -import io.legado.app.service.help.Download +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.service.help.CacheBook import io.legado.app.service.help.ReadBook +import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.applyTint import io.legado.app.utils.requestInputMethod import kotlinx.android.synthetic.main.dialog_download_choice.view.* @@ -29,18 +30,22 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.* import org.jetbrains.anko.layoutInflater -object Help { - - private const val NAVIGATION = "navigationBarBackground" +object ReadBookActivityHelp { /** * 更新状态栏,导航栏 */ - fun upSystemUiVisibility(activity: Activity, toolBarHide: Boolean = true) { - var flag = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + fun upSystemUiVisibility( + window: Window, + isInMultiWindow: Boolean, + toolBarHide: Boolean = true + ) { + var flag = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) + if (!isInMultiWindow) { + flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + } if (ReadBookConfig.hideNavigationBar) { flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION } @@ -52,18 +57,20 @@ object Help { flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION } } - activity.window.decorView.systemUiVisibility = flag + window.decorView.systemUiVisibility = flag if (toolBarHide) { - ATH.setLightStatusBar(activity, ReadBookConfig.durConfig.statusIconDark()) + ATH.setLightStatusBar(window, ReadBookConfig.durConfig.statusIconDark()) } else { ATH.setLightStatusBarAuto( - activity, - ThemeStore.statusBarColor(activity, AppConfig.isTransparentStatusBar) + window, + ThemeStore.statusBarColor(App.INSTANCE, AppConfig.isTransparentStatusBar) ) } } - + /** + * 屏幕方向 + */ @SuppressLint("SourceLockedOrientationActivity") fun setOrientation(activity: Activity) = activity.apply { when (AppConfig.requestedDirection) { @@ -75,28 +82,6 @@ object Help { } - /** - * 返回NavigationBar是否存在 - * 该方法需要在View完全被绘制出来之后调用,否则判断不了 - * 在比如 onWindowFocusChanged()方法中可以得到正确的结果 - */ - fun isNavigationBarExist(activity: Activity?): Boolean { - activity?.let { - val vp = it.window.decorView as? ViewGroup - if (vp != null) { - for (i in 0 until vp.childCount) { - vp.getChildAt(i).context.packageName - if (vp.getChildAt(i).id != NO_ID - && NAVIGATION == activity.resources.getResourceEntryName(vp.getChildAt(i).id) - ) { - return true - } - } - } - } - return false - } - /** * 保持亮屏 */ @@ -112,7 +97,7 @@ object Help { * 适配刘海 */ fun upLayoutInDisplayCutoutMode(window: Window) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && AppConfig.readBodyToLh) { window.attributes = window.attributes.apply { layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES @@ -123,13 +108,14 @@ object Help { @SuppressLint("InflateParams") fun showDownloadDialog(context: Context) { ReadBook.book?.let { book -> - context.alert(titleResource = R.string.download_offline) { + context.alert(titleResource = R.string.offline_cache) { var view: View? = null customView { LayoutInflater.from(context).inflate(R.layout.dialog_download_choice, null) .apply { view = this - edit_start.setText(book.durChapterIndex.toString()) + setBackgroundColor(context.backgroundColor) + edit_start.setText((book.durChapterIndex + 1).toString()) edit_end.setText(book.totalChapterNum.toString()) } } @@ -137,7 +123,7 @@ object Help { view?.apply { val start = edit_start?.text?.toString()?.toInt() ?: 0 val end = edit_end?.text?.toString()?.toInt() ?: book.totalChapterNum - Download.start(context, book.bookUrl, start, end) + CacheBook.start(context, book.bookUrl, start - 1, end - 1) } } noButton() @@ -155,7 +141,7 @@ object Help { customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view.apply { - hint = "备注内容" + setHint(R.string.note_content) } } } @@ -163,13 +149,12 @@ object Help { editText?.text?.toString()?.let { editContent -> AsyncTask.execute { val bookmark = Bookmark( - book.durChapterTime, - book.bookUrl, - book.name, - ReadBook.durChapterIndex, - ReadBook.durPageIndex, - textChapter.title, - editContent + bookUrl = book.bookUrl, + bookName = book.name, + chapterIndex = ReadBook.durChapterIndex, + pageIndex = ReadBook.durPageIndex, + chapterName = textChapter.title, + content = editContent ) App.db.bookmarkDao().insert(bookmark) } @@ -178,4 +163,27 @@ object Help { noButton() }.show().applyTint().requestInputMethod() } + + @SuppressLint("InflateParams") + fun showCharsetConfig(context: Context) = with(context) { + val charsets = + arrayListOf("UTF-8", "GB2312", "GBK", "Unicode", "UTF-16", "UTF-16LE", "ASCII") + alert(R.string.set_charset) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + edit_view.setFilterValues(charsets) + edit_view.setText(ReadBook.book?.charset) + } + } + okButton { + val text = editText?.text?.toString() + text?.let { + ReadBook.setCharset(it) + } + } + cancelButton() + }.show().applyTint() + } } \ No newline at end of file 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 19c10ee83..b8efe1cee 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,10 +7,12 @@ 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.data.entities.SearchBook +import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.IntentDataHelp -import io.legado.app.model.WebBook -import io.legado.app.model.localBook.AnalyzeTxtFile +import io.legado.app.model.localBook.LocalBook +import io.legado.app.model.webBook.WebBook import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook @@ -34,15 +36,21 @@ 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) isInitFinish = true + if (!book.isLocalBook() && ReadBook.webBook == null) { + autoChangeSource(book.name, book.author) + return + } ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) if (ReadBook.chapterSize == 0) { if (book.tocUrl.isEmpty()) { @@ -56,14 +64,21 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } ReadBook.loadContent(resetPageOffset = true) } - if (ReadBook.inBookshelf) { - ReadBook.saveRead() - } } else { - isInitFinish = true + ReadBook.book = book + if (ReadBook.durChapterIndex != book.durChapterIndex) { + ReadBook.durChapterIndex = book.durChapterIndex + ReadBook.durPageIndex = book.durChapterPos + ReadBook.prevTextChapter = null + ReadBook.curTextChapter = null + ReadBook.nextTextChapter = null + } ReadBook.titleDate.postValue(book.name) - ReadBook.upWebBook(book) { name, author -> - autoChangeSource(name, author) + ReadBook.upWebBook(book) + isInitFinish = true + if (!book.isLocalBook() && ReadBook.webBook == null) { + autoChangeSource(book.name, book.author) + return } ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) if (ReadBook.chapterSize == 0) { @@ -75,6 +90,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } else { if (ReadBook.curTextChapter != null) { ReadBook.callBack?.upContent(resetPageOffset = false) + } else { + ReadBook.loadContent(resetPageOffset = true) } } } @@ -82,13 +99,13 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { private fun loadBookInfo( book: Book, - changeDruChapterIndex: ((chapters: List) -> Unit)? = null + changeDruChapterIndex: ((chapters: List) -> Unit)? = null, ) { execute { if (book.isLocalBook()) { loadChapterList(book, changeDruChapterIndex) } else { - ReadBook.webBook?.getBookInfo(book, this) + ReadBook.webBook?.getBookInfo(book, this, canReName = false) ?.onSuccess { loadChapterList(book, changeDruChapterIndex) } @@ -98,16 +115,21 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { fun loadChapterList( book: Book, - changeDruChapterIndex: ((chapters: List) -> Unit)? = null + changeDruChapterIndex: ((chapters: List) -> Unit)? = null, ) { execute { if (book.isLocalBook()) { - AnalyzeTxtFile.analyze(context, book).let { + LocalBook.getChapterList(book).let { App.db.bookChapterDao().delByBook(book.bookUrl) App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = it.size - ReadBook.loadContent(resetPageOffset = true) + if (it.isEmpty()) { + ReadBook.upMsg(context.getString(R.string.error_load_toc)) + } else { + ReadBook.upMsg(null) + ReadBook.loadContent(resetPageOffset = true) + } } } else { ReadBook.webBook?.getChapterList(book, this) @@ -129,16 +151,18 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { ReadBook.upMsg(context.getString(R.string.error_load_toc)) } } + }.onError { + ReadBook.upMsg("LoadTocError:${it.localizedMessage}") } } - fun changeTo(book1: Book) { + fun changeTo(newBook: Book) { execute { ReadBook.upMsg(null) - ReadBook.book?.let { - book1.group = it.group - book1.order = it.order - App.db.bookDao().delete(it) + ReadBook.book?.changeTo(newBook) + ReadBook.book = newBook + App.db.bookSourceDao().getBookSource(newBook.origin)?.let { + ReadBook.webBook = WebBook(it) } ReadBook.prevTextChapter = null ReadBook.curTextChapter = null @@ -146,36 +170,40 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { withContext(Main) { ReadBook.callBack?.upContent() } - App.db.bookDao().insert(book1) - ReadBook.book = book1 - App.db.bookSourceDao().getBookSource(book1.origin)?.let { - ReadBook.webBook = WebBook(it) - } - if (book1.tocUrl.isEmpty()) { - loadBookInfo(book1) { upChangeDurChapterIndex(book1, it) } + if (newBook.tocUrl.isEmpty()) { + loadBookInfo(newBook) { + upChangeDurChapterIndex(newBook, it) + } } else { - loadChapterList(book1) { upChangeDurChapterIndex(book1, it) } + loadChapterList(newBook) { + upChangeDurChapterIndex(newBook, it) + } } } } private fun autoChangeSource(name: String, author: String) { + if (!AppConfig.autoChangeSource) return execute { App.db.bookSourceDao().allTextEnabled.forEach { source -> try { - val searchBooks = WebBook(source).searchBookSuspend(this, name) - searchBooks.getOrNull(0)?.let { - if (it.name == name && (it.author == author || author == "")) { - changeTo(it.toBook()) - return@forEach + val variableBook = SearchBook() + WebBook(source) + .searchBookSuspend(this, name, variableBook = variableBook) + .getOrNull(0)?.let { + if (it.name == name && (it.author == author || author == "")) { + val book = it.toBook() + book.upInfoFromOld(ReadBook.book) + changeTo(book) + return@execute + } } - } } catch (e: Exception) { //nothing } } }.onStart { - ReadBook.upMsg("正在自动换源") + ReadBook.upMsg(context.getString(R.string.source_auto_changing)) }.onFinally { ReadBook.upMsg(null) } @@ -193,6 +221,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { App.db.bookDao().update(book) App.db.bookChapterDao().insert(*chapters.toTypedArray()) ReadBook.chapterSize = chapters.size + ReadBook.upMsg(null) ReadBook.loadContent(resetPageOffset = true) } } @@ -212,9 +241,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { fun removeFromBookshelf(success: (() -> Unit)?) { execute { - ReadBook.book?.let { - App.db.bookDao().delete(it) - } + ReadBook.book?.delete() }.onSuccess { success?.invoke() } 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..26423e701 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 @@ -1,20 +1,21 @@ package io.legado.app.ui.book.read import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.GradientDrawable 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.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig -import io.legado.app.lib.theme.accentColor -import io.legado.app.lib.theme.bottomBackground -import io.legado.app.lib.theme.buttonDisabledColor +import io.legado.app.lib.theme.* import io.legado.app.service.help.ReadBook import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_read_menu.view.* @@ -28,7 +29,11 @@ class ReadMenu : FrameLayout { private lateinit var menuTopOut: Animation private lateinit var menuBottomIn: Animation private lateinit var menuBottomOut: Animation + private val bgColor: Int + private val textColor: Int + private var bottomBackgroundList: ColorStateList private var onMenuOutEnd: (() -> Unit)? = null + val showBrightnessView get() = context.getPrefBoolean(PreferKey.showBrightnessView, true) constructor(context: Context) : super(context) @@ -42,6 +47,12 @@ class ReadMenu : FrameLayout { init { callBack = activity as? CallBack + bgColor = context.bottomBackground + textColor = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor)) + bottomBackgroundList = Selector.colorBuild() + .setDefaultColor(bgColor) + .setPressedColor(ColorUtils.darkenColor(bgColor)) + .create() inflate(context, R.layout.view_read_menu, this) if (AppConfig.isNightTheme) { fabNightTheme.setImageResource(R.drawable.ic_daytime) @@ -49,7 +60,27 @@ class ReadMenu : FrameLayout { fabNightTheme.setImageResource(R.drawable.ic_brightness) } initAnimation() - ll_bottom_bg.setBackgroundColor(context.bottomBackground) + val brightnessBackground = GradientDrawable() + brightnessBackground.cornerRadius = 5F.dp + brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f)) + ll_brightness.background = brightnessBackground + ll_bottom_bg.setBackgroundColor(bgColor) + fabAutoPage.backgroundTintList = bottomBackgroundList + fabAutoPage.setColorFilter(textColor) + fabReplaceRule.backgroundTintList = bottomBackgroundList + fabReplaceRule.setColorFilter(textColor) + fabNightTheme.backgroundTintList = bottomBackgroundList + fabNightTheme.setColorFilter(textColor) + tv_pre.setTextColor(textColor) + tv_next.setTextColor(textColor) + iv_catalog.setColorFilter(textColor) + tv_catalog.setTextColor(textColor) + iv_read_aloud.setColorFilter(textColor) + tv_read_aloud.setTextColor(textColor) + iv_font.setColorFilter(textColor) + tv_font.setTextColor(textColor) + iv_setting.setColorFilter(textColor) + tv_setting.setTextColor(textColor) vw_bg.onClick { } vwNavigationBar.onClick { } seek_brightness.progress = context.getPrefInt("brightness", 100) @@ -57,7 +88,7 @@ class ReadMenu : FrameLayout { bindEvent() } - private fun upBrightnessState() { + fun upBrightnessState() { if (brightnessAuto()) { iv_brightness_auto.setColorFilter(context.accentColor) seek_brightness.isEnabled = false @@ -100,7 +131,7 @@ class ReadMenu : FrameLayout { } private fun brightnessAuto(): Boolean { - return context.getPrefBoolean("brightnessAuto", true) + return context.getPrefBoolean("brightnessAuto", true) || !showBrightnessView } private fun bindEvent() { @@ -140,7 +171,11 @@ class ReadMenu : FrameLayout { }) //自动翻页 - fabAutoPage.onClick { callBack?.autoPage() } + fabAutoPage.onClick { + runMenuOut { + callBack?.autoPage() + } + } //替换 fabReplaceRule.onClick { callBack?.openReplaceRule() } @@ -149,6 +184,7 @@ class ReadMenu : FrameLayout { fabNightTheme.onClick { AppConfig.isNightTheme = !AppConfig.isNightTheme App.INSTANCE.applyDayNight() + postEvent(EventBus.RECREATE, "") } //上一章 @@ -164,6 +200,12 @@ class ReadMenu : FrameLayout { } } + ll_search.onClick { + runMenuOut { + callBack?.openSearchList() + } + } + //朗读 ll_read_aloud.onClick { runMenuOut { @@ -190,18 +232,22 @@ 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() + ll_brightness.visible(showBrightnessView) } override fun onAnimationEnd(animation: Animation) { vw_menu_bg.onClick { runMenuOut() } vwNavigationBar.layoutParams = vwNavigationBar.layoutParams.apply { height = - if (ReadBookConfig.hideNavigationBar && Help.isNavigationBarExist(activity)) + if (ReadBookConfig.hideNavigationBar + && SystemUtils.isNavigationBarExist(activity) + ) context.navigationBarHeight else 0 } @@ -213,8 +259,9 @@ 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) @@ -243,12 +290,14 @@ class ReadMenu : FrameLayout { fabAutoPage.setImageResource(R.drawable.ic_auto_page) fabAutoPage.contentDescription = context.getString(R.string.auto_next_page) } + fabAutoPage.setColorFilter(textColor) } interface CallBack { fun autoPage() fun openReplaceRule() fun openChapterList() + fun openSearchList() fun showReadStyle() fun showMoreSetting() fun showReadAloudDialog() 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..11c2fed44 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt @@ -0,0 +1,116 @@ +package io.legado.app.ui.book.read.config + +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.* +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.lib.theme.getPrimaryTextColor +import io.legado.app.service.BaseReadAloudService +import io.legado.app.service.help.ReadAloud +import io.legado.app.utils.ColorUtils +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 { + it.windowManager?.defaultDisplay?.getMetrics(dm) + } + dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + 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?) { + val bg = requireContext().bottomBackground + val isLight = ColorUtils.isColorLight(bg) + val textColor = requireContext().getPrimaryTextColor(isLight) + root_view.setBackgroundColor(bg) + tv_read_speed_title.setTextColor(textColor) + tv_read_speed.setTextColor(textColor) + iv_catalog.setColorFilter(textColor) + tv_catalog.setTextColor(textColor) + iv_main_menu.setColorFilter(textColor) + tv_main_menu.setTextColor(textColor) + iv_auto_page_stop.setColorFilter(textColor) + tv_auto_page_stop.setTextColor(textColor) + iv_setting.setColorFilter(textColor) + tv_setting.setTextColor(textColor) + initOnChange() + initData() + initEvent() + } + + private fun initData() { + val speed = if (ReadBookConfig.autoReadSpeed < 10) 10 else ReadBookConfig.autoReadSpeed + tv_read_speed.text = String.format("%ds", speed) + seek_auto_read.progress = speed + } + + private fun initOnChange() { + seek_auto_read.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + val speed = if (progress < 10) 10 else progress + tv_read_speed.text = String.format("%ds", speed) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar) { + ReadBookConfig.autoReadSpeed = + if (seek_auto_read.progress < 10) 10 else 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/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 73f85dce0..b8d71b515 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -9,13 +9,8 @@ import android.graphics.Color import android.net.Uri 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.view.* import androidx.documentfile.provider.DocumentFile -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legado.app.R import io.legado.app.base.BaseDialogFragment @@ -24,35 +19,53 @@ import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.constant.EventBus import io.legado.app.help.ImageLoader import io.legado.app.help.ReadBookConfig +import io.legado.app.help.http.HttpHelper import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat -import io.legado.app.ui.book.read.Help +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.noButton +import io.legado.app.lib.dialogs.okButton +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.lib.theme.getSecondaryTextColor +import io.legado.app.ui.book.read.ReadBookActivity +import io.legado.app.ui.filechooser.FileChooserDialog +import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_read_bg_text.* import kotlinx.android.synthetic.main.item_bg_image.view.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onClick +import java.io.File -class BgTextConfigDialog : BaseDialogFragment() { +class BgTextConfigDialog : BaseDialogFragment(), FileChooserDialog.CallBack { companion object { const val TEXT_COLOR = 121 const val BG_COLOR = 122 } - private val resultSelectBg = 123 + private val requestCodeBg = 123 + private val requestCodeExport = 131 + private val requestCodeImport = 132 + private val configFileName = "readConfig.zip" private lateinit var adapter: BgAdapter + var primaryTextColor = 0 + var secondaryTextColor = 0 override fun onStart() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) it.setBackgroundDrawableResource(R.color.background) - it.decorView.setPadding(0, 5, 0, 0) + it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -70,8 +83,9 @@ class BgTextConfigDialog : BaseDialogFragment() { } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - initData() initView() + initData() + initEvent() } override fun onDismiss(dialog: DialogInterface) { @@ -79,32 +93,42 @@ class BgTextConfigDialog : BaseDialogFragment() { ReadBookConfig.save() } + private fun initView() { + val bg = requireContext().bottomBackground + val isLight = ColorUtils.isColorLight(bg) + primaryTextColor = requireContext().getPrimaryTextColor(isLight) + secondaryTextColor = requireContext().getSecondaryTextColor(isLight) + root_view.setBackgroundColor(bg) + sw_dark_status_icon.setTextColor(primaryTextColor) + iv_import.setColorFilter(primaryTextColor) + iv_export.setColorFilter(primaryTextColor) + iv_delete.setColorFilter(primaryTextColor) + tv_bg_image.setTextColor(primaryTextColor) + } + @SuppressLint("InflateParams") private fun initData() = with(ReadBookConfig.durConfig) { sw_dark_status_icon.isChecked = statusIconDark() adapter = BgAdapter(requireContext()) - recycler_view.layoutManager = - LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) recycler_view.adapter = adapter val headerView = LayoutInflater.from(requireContext()) .inflate(R.layout.item_bg_image, recycler_view, false) adapter.addHeaderView(headerView) + headerView.tv_name.setTextColor(secondaryTextColor) headerView.tv_name.text = getString(R.string.select_image) headerView.iv_bg.setImageResource(R.drawable.ic_image) - headerView.iv_bg.setColorFilter(getCompatColor(R.color.tv_text_default)) + headerView.iv_bg.setColorFilter(primaryTextColor) headerView.onClick { selectImage() } requireContext().assets.list("bg/")?.let { adapter.setItems(it.toList()) } } - private fun initView() = with(ReadBookConfig.durConfig) { + private fun initEvent() = with(ReadBookConfig.durConfig) { sw_dark_status_icon.onCheckedChange { buttonView, isChecked -> if (buttonView?.isPressed == true) { setStatusIconDark(isChecked) - activity?.let { - Help.upSystemUiVisibility(it) - } + (activity as? ReadBookActivity)?.upSystemUiVisibility() } } tv_text_color.onClick { @@ -126,9 +150,35 @@ class BgTextConfigDialog : BaseDialogFragment() { .setDialogId(BG_COLOR) .show(requireActivity()) } - tv_default.onClick { - ReadBookConfig.resetDur() - postEvent(EventBus.UP_CONFIG, false) + iv_import.onClick { + val importFormNet = "网络导入" + val otherActions = arrayListOf(importFormNet) + FilePicker.selectFile( + this@BgTextConfigDialog, + requestCodeImport, + title = getString(R.string.import_str), + allowExtensions = arrayOf("zip"), + otherActions = otherActions + ) { action -> + when (action) { + importFormNet -> importNetConfigAlert() + } + } + } + iv_export.onClick { + FilePicker.selectFolder( + this@BgTextConfigDialog, + requestCodeExport, + title = getString(R.string.export_str) + ) + } + iv_delete.onClick { + if (ReadBookConfig.deleteDur()) { + postEvent(EventBus.UP_CONFIG, true) + dismiss() + } else { + toast("数量以是最少,不能删除.") + } } } @@ -136,10 +186,10 @@ class BgTextConfigDialog : BaseDialogFragment() { val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "image/*" - startActivityForResult(intent, resultSelectBg) + startActivityForResult(intent, requestCodeBg) } - class BgAdapter(context: Context) : + inner class BgAdapter(context: Context) : SimpleRecyclerAdapter(context, R.layout.item_bg_image) { override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList) { @@ -147,6 +197,7 @@ class BgTextConfigDialog : BaseDialogFragment() { ImageLoader.load(context, context.assets.open("bg/$item").readBytes()) .centerCrop() .into(iv_bg) + tv_name.setTextColor(secondaryTextColor) tv_name.text = item.substringBeforeLast(".") } } @@ -164,14 +215,160 @@ class BgTextConfigDialog : BaseDialogFragment() { } } + @Suppress("BlockingMethodInNonBlockingContext") + private fun exportConfig(uri: Uri) { + execute { + val exportFiles = arrayListOf() + val configDirPath = FileUtils.getPath(requireContext().eCacheDir, "readConfig") + FileUtils.deleteFile(configDirPath) + val configDir = FileUtils.createFolderIfNotExist(configDirPath) + val configExportPath = FileUtils.getPath(configDir, "readConfig.json") + FileUtils.deleteFile(configExportPath) + val configExportFile = FileUtils.createFileIfNotExist(configExportPath) + configExportFile.writeText(GSON.toJson(ReadBookConfig.getExportConfig())) + exportFiles.add(configExportFile) + val fontPath = ReadBookConfig.textFont + if (fontPath.isNotEmpty()) { + val fontName = FileUtils.getName(fontPath) + val fontBytes = fontPath.parseToUri().readBytes(requireContext()) + fontBytes?.let { + val fontExportFile = FileUtils.createFileIfNotExist(configDir, fontName) + fontExportFile.writeBytes(it) + exportFiles.add(fontExportFile) + } + } + if (ReadBookConfig.durConfig.bgType() == 2) { + val bgName = FileUtils.getName(ReadBookConfig.durConfig.bgStr()) + val bgFile = File(ReadBookConfig.durConfig.bgStr()) + if (bgFile.exists()) { + val bgExportFile = File(FileUtils.getPath(configDir, bgName)) + bgFile.copyTo(bgExportFile) + exportFiles.add(bgExportFile) + } + } + val configZipPath = FileUtils.getPath(requireContext().eCacheDir, configFileName) + if (ZipUtils.zipFiles(exportFiles, File(configZipPath))) { + if (uri.isContentPath()) { + DocumentFile.fromTreeUri(requireContext(), uri)?.let { treeDoc -> + treeDoc.findFile(configFileName)?.delete() + treeDoc.createFile("", configFileName) + ?.writeBytes(requireContext(), File(configZipPath).readBytes()) + } + } else { + val exportPath = FileUtils.getPath(File(uri.path!!), configFileName) + FileUtils.deleteFile(exportPath) + FileUtils.createFileIfNotExist(exportPath) + .writeBytes(File(configZipPath).readBytes()) + } + } + }.onSuccess { + toast("导出成功, 文件名为 $configFileName") + }.onError { + it.printStackTrace() + longToast("导出失败:${it.localizedMessage}") + } + } + + @SuppressLint("InflateParams") + private fun importNetConfigAlert() { + alert("输入地址") { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + } + } + okButton { + editText?.text?.toString()?.let { url -> + importNetConfig(url) + } + } + noButton { } + }.show().applyTint() + } + + private fun importNetConfig(url: String) { + execute { + HttpHelper.simpleGetBytesAsync(url)?.let { + importConfig(it) + } ?: throw Exception("获取失败") + }.onError { + longToast(it.msg) + } + } + + @Suppress("BlockingMethodInNonBlockingContext") + private fun importConfig(uri: Uri) { + execute { + importConfig(uri.readBytes(requireContext())!!) + }.onError { + it.printStackTrace() + longToast("导入失败:${it.localizedMessage}") + } + } + + @Suppress("BlockingMethodInNonBlockingContext") + private fun importConfig(byteArray: ByteArray) { + execute { + val configZipPath = FileUtils.getPath(requireContext().eCacheDir, configFileName) + FileUtils.deleteFile(configZipPath) + val zipFile = FileUtils.createFileIfNotExist(configZipPath) + zipFile.writeBytes(byteArray) + val configDirPath = FileUtils.getPath(requireContext().eCacheDir, "readConfig") + FileUtils.deleteFile(configDirPath) + ZipUtils.unzipFile(zipFile, FileUtils.createFolderIfNotExist(configDirPath)) + val configDir = FileUtils.createFolderIfNotExist(configDirPath) + val configFile = FileUtils.getFile(configDir, "readConfig.json") + val config: ReadBookConfig.Config = GSON.fromJsonObject(configFile.readText())!! + if (config.textFont.isNotEmpty()) { + val fontName = FileUtils.getName(config.textFont) + val fontPath = + FileUtils.getPath(requireContext().externalFilesDir, "font", fontName) + if (!FileUtils.exist(fontPath)) { + FileUtils.getFile(configDir, fontName).copyTo(File(fontPath)) + } + config.textFont = fontPath + } + if (config.bgType() == 2) { + val bgName = FileUtils.getName(config.bgStr()) + val bgPath = FileUtils.getPath(requireContext().externalFilesDir, "bg", bgName) + if (!FileUtils.exist(bgPath)) { + FileUtils.getFile(configDir, bgName).copyTo(File(bgPath)) + } + } + ReadBookConfig.durConfig = config + postEvent(EventBus.UP_CONFIG, true) + }.onSuccess { + toast("导入成功") + }.onError { + it.printStackTrace() + longToast("导入失败:${it.localizedMessage}") + } + } + + override fun onFilePicked(requestCode: Int, currentPath: String) { + when (requestCode) { + requestCodeImport -> importConfig(Uri.fromFile(File(currentPath))) + requestCodeExport -> exportConfig(Uri.fromFile(File(currentPath))) + } + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { - resultSelectBg -> { - if (resultCode == RESULT_OK) { - data?.data?.let { uri -> - setBgFromUri(uri) - } + requestCodeBg -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> + setBgFromUri(uri) + } + } + requestCodeImport -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> + importConfig(uri) + } + } + requestCodeExport -> if (resultCode == RESULT_OK) { + data?.data?.let { uri -> + exportConfig(uri) } } } @@ -181,9 +378,8 @@ class BgTextConfigDialog : BaseDialogFragment() { if (uri.toString().isContentPath()) { val doc = DocumentFile.fromSingleUri(requireContext(), uri) doc?.name?.let { - var file = requireContext().getExternalFilesDir(null) - ?: requireContext().filesDir - file = FileUtils.createFileIfNotExist(file, it, "bg") + val file = + FileUtils.createFileIfNotExist(requireContext().externalFilesDir, "bg", it) kotlin.runCatching { DocumentUtils.readBytes(requireContext(), doc.uri) }.getOrNull()?.let { byteArray -> diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ChineseConverter.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ChineseConverter.kt index dc8f36d67..af8408284 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ChineseConverter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ChineseConverter.kt @@ -10,6 +10,7 @@ import io.legado.app.help.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.accentColor import io.legado.app.ui.widget.text.StrokeTextView +import io.legado.app.utils.applyTint import org.jetbrains.anko.sdk27.listeners.onClick class ChineseConverter(context: Context, attrs: AttributeSet?) : StrokeTextView(context, attrs) { @@ -44,7 +45,7 @@ class ChineseConverter(context: Context, attrs: AttributeSet?) : StrokeTextView( upUi(i) onChanged?.invoke() } - }.show() + }.show().applyTint() } fun onChanged(unit: () -> Unit) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index a23725143..4a3b4bd23 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 @@ -1,23 +1,22 @@ package io.legado.app.ui.book.read.config +import android.annotation.SuppressLint import android.content.SharedPreferences 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.view.* import android.widget.LinearLayout import androidx.fragment.app.DialogFragment import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import io.legado.app.R +import io.legado.app.base.BasePreferenceFragment import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey 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.ui.book.read.ReadBookActivityHelp +import io.legado.app.utils.dp import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.postEvent @@ -28,17 +27,17 @@ class MoreConfigDialog : DialogFragment() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) 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) + it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 360.dp) } } @@ -63,9 +62,10 @@ class MoreConfigDialog : DialogFragment() { .commit() } - class ReadPreferenceFragment : PreferenceFragmentCompat(), + class ReadPreferenceFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { + @SuppressLint("RestrictedApi") override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_read) } @@ -94,6 +94,7 @@ class MoreConfigDialog : DialogFragment() { key: String? ) { when (key) { + PreferKey.readBodyToLh -> activity?.recreate() PreferKey.hideStatusBar -> { ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) postEvent(EventBus.UP_CONFIG, true) @@ -106,9 +107,16 @@ class MoreConfigDialog : DialogFragment() { PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) getString(R.string.pk_requested_direction) -> { activity?.let { - Help.setOrientation(it) + ReadBookActivityHelp.setOrientation(it) } } + PreferKey.textFullJustify, + PreferKey.textBottomJustify -> { + postEvent(EventBus.UP_CONFIG, true) + } + PreferKey.showBrightnessView -> { + postEvent(PreferKey.showBrightnessView, "") + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index ed1c296de..dda13716a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -6,27 +6,24 @@ import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import android.view.WindowManager import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.EventBus import io.legado.app.help.ReadBookConfig -import io.legado.app.ui.book.read.Help -import io.legado.app.utils.dp -import io.legado.app.utils.gone import io.legado.app.utils.postEvent -import io.legado.app.utils.visible import kotlinx.android.synthetic.main.dialog_read_padding.* -class PaddingConfigDialog : DialogFragment() { +class PaddingConfigDialog : BaseDialogFragment() { override fun onStart() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) val attr = it.attributes attr.dimAmount = 0.0f it.attributes = attr @@ -42,8 +39,7 @@ class PaddingConfigDialog : DialogFragment() { return inflater.inflate(R.layout.dialog_read_padding, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { initData() initView() } @@ -54,13 +50,6 @@ class PaddingConfigDialog : DialogFragment() { } private fun initData() = ReadBookConfig.apply { - if (hideStatusBar) { - ll_header_padding.visible() - tv_body_padding.setPadding(0, 10.dp, 0, 10.dp) - } else { - ll_header_padding.gone() - tv_body_padding.setPadding(0, 0.dp, 0, 10.dp) - } //正文 dsb_padding_top.progress = paddingTop dsb_padding_bottom.progress = paddingBottom diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt index 28a46b7f2..cfd632453 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 @@ -5,9 +5,11 @@ import android.content.Context import android.view.KeyEvent import io.legado.app.R import io.legado.app.constant.PreferKey +import io.legado.app.lib.theme.backgroundColor 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 @@ -16,14 +18,21 @@ class PageKeyDialog(context: Context) : Dialog(context, R.style.AppTheme_AlertDi init { setContentView(R.layout.dialog_page_key) + content_view.setBackgroundColor(context.backgroundColor) 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..3ae466454 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 @@ -10,15 +10,16 @@ import android.widget.LinearLayout import androidx.fragment.app.DialogFragment import androidx.preference.ListPreference import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat +import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BasePreferenceFragment 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 +import io.legado.app.utils.getPrefLong import io.legado.app.utils.postEvent class ReadAloudConfigDialog : DialogFragment() { @@ -28,11 +29,11 @@ class ReadAloudConfigDialog : DialogFragment() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) + + it.setBackgroundDrawableResource(R.color.transparent) it.setLayout((dm.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT) } } @@ -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 @@ -58,14 +59,21 @@ class ReadAloudConfigDialog : DialogFragment() { .commit() } - class ReadAloudPreferenceFragment : PreferenceFragmentCompat(), + class ReadAloudPreferenceFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { + private val speakEngineSummary: String + get() { + val eid = App.INSTANCE.getPrefLong(PreferKey.speakEngine) + val ht = App.db.httpTTSDao().get(eid) + return ht?.name ?: getString(R.string.local_tts) + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_aloud) upPreferenceSummary( - findPreference(PreferKey.ttsSpeechPer), - AppConfig.ttsSpeechPer + findPreference(PreferKey.speakEngine), + speakEngineSummary ) } @@ -84,6 +92,14 @@ class ReadAloudConfigDialog : DialogFragment() { super.onPause() } + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + when (preference?.key) { + PreferKey.speakEngine -> + SpeakEngineDialog().show(childFragmentManager, "speakEngine") + } + return super.onPreferenceTreeClick(preference) + } + override fun onSharedPreferenceChanged( sharedPreferences: SharedPreferences?, key: String? @@ -94,16 +110,9 @@ class ReadAloudConfigDialog : DialogFragment() { postEvent(EventBus.MEDIA_BUTTON, false) } } - PreferKey.readAloudOnLine -> { - ReadAloud.stop(requireContext()) - ReadAloud.aloudClass = ReadAloud.getReadAloudClass() - } - PreferKey.ttsSpeechPer -> { - upPreferenceSummary( - findPreference(PreferKey.ttsSpeechPer), - AppConfig.ttsSpeechPer - ) - ReadAloud.upTtsSpeechRate(requireContext()) + PreferKey.speakEngine -> { + upPreferenceSummary(findPreference(key), speakEngineSummary) + ReadAloud.upReadAloudClass() } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt index 9c118c068..7fcd0f26d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt @@ -2,20 +2,18 @@ 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.view.* import android.widget.SeekBar import io.legado.app.R import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook -import io.legado.app.ui.book.read.Help +import io.legado.app.utils.ColorUtils import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.observeEvent import io.legado.app.utils.putPrefBoolean @@ -29,10 +27,10 @@ class ReadAloudDialog : BaseDialogFragment() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) it.setBackgroundDrawableResource(R.color.background) it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes @@ -53,7 +51,28 @@ class ReadAloudDialog : BaseDialogFragment() { } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - root_view.setBackgroundColor(requireContext().bottomBackground) + val bg = requireContext().bottomBackground + val isLight = ColorUtils.isColorLight(bg) + val textColor = requireContext().getPrimaryTextColor(isLight) + root_view.setBackgroundColor(bg) + tv_pre.setTextColor(textColor) + tv_next.setTextColor(textColor) + iv_play_prev.setColorFilter(textColor) + iv_play_pause.setColorFilter(textColor) + iv_play_next.setColorFilter(textColor) + iv_stop.setColorFilter(textColor) + iv_timer.setColorFilter(textColor) + tv_timer.setTextColor(textColor) + tv_tts_speed.setTextColor(textColor) + iv_catalog.setColorFilter(textColor) + tv_catalog.setTextColor(textColor) + iv_main_menu.setColorFilter(textColor) + tv_main_menu.setTextColor(textColor) + iv_to_backstage.setColorFilter(textColor) + tv_to_backstage.setTextColor(textColor) + iv_setting.setColorFilter(textColor) + tv_setting.setTextColor(textColor) + cb_tts_follow_sys.setTextColor(textColor) initOnChange() initData() initEvent() @@ -121,6 +140,10 @@ class ReadAloudDialog : BaseDialogFragment() { } else { iv_play_pause.setImageResource(R.drawable.ic_play_24dp) } + val bg = requireContext().bottomBackground + val isLight = ColorUtils.isColorLight(bg) + val textColor = requireContext().getPrimaryTextColor(isLight) + iv_play_pause.setColorFilter(textColor) } private fun upTimerText(timeMinute: Int) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index f29c4ddba..12201ae29 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 @@ -4,44 +4,43 @@ import android.annotation.SuppressLint import android.content.DialogInterface 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.view.* import androidx.core.view.get -import androidx.fragment.app.DialogFragment import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.constant.EventBus -import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.selector import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.bottomBackground -import io.legado.app.lib.theme.primaryColor -import io.legado.app.ui.book.read.Help +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.widget.font.FontSelectDialog import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.dialog_read_book_style.* import kotlinx.android.synthetic.main.dialog_title_config.view.* +import kotlinx.android.synthetic.main.item_read_style.view.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick -class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { +class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack { val callBack get() = activity as? ReadBookActivity + private lateinit var styleAdapter: StyleAdapter override fun onStart() { super.onStart() val dm = DisplayMetrics() activity?.let { - Help.upSystemUiVisibility(it) it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { + it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) it.setBackgroundDrawableResource(R.color.background) it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes @@ -60,8 +59,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { return inflater.inflate(R.layout.dialog_read_book_style, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { initView() initData() initViewEvent() @@ -73,7 +71,13 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } private fun initView() { - root_view.setBackgroundColor(requireContext().bottomBackground) + val bg = requireContext().bottomBackground + val isLight = ColorUtils.isColorLight(bg) + val textColor = requireContext().getPrimaryTextColor(isLight) + root_view.setBackgroundColor(bg) + tv_page_anim.setTextColor(textColor) + tv_bg_ts.setTextColor(textColor) + tv_share_layout.setTextColor(textColor) dsb_text_size.valueFormat = { (it + 5).toString() } @@ -82,6 +86,20 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } dsb_line_size.valueFormat = { ((it - 10) / 10f).toString() } dsb_paragraph_spacing.valueFormat = { (it / 10f).toString() } + styleAdapter = StyleAdapter() + rv_style.adapter = styleAdapter + val footerView = LayoutInflater.from(requireContext()) + .inflate(R.layout.item_read_style, rv_style, false) + footerView.iv_style.setPadding(6.dp, 6.dp, 6.dp, 6.dp) + footerView.iv_style.setText(null) + footerView.iv_style.setColorFilter(textColor) + footerView.iv_style.borderColor = textColor + footerView.iv_style.setImageResource(R.drawable.ic_add) + styleAdapter.addFooterView(footerView) + footerView.onClick { + ReadBookConfig.configList.add(ReadBookConfig.Config()) + showBgTextConfig(ReadBookConfig.configList.lastIndex) + } } private fun initData() { @@ -92,8 +110,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } } upStyle() - setBg() - upBg() + styleAdapter.setItems(ReadBookConfig.configList) } private fun initViewEvent() { @@ -103,11 +120,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { tv_title_mode.onClick { showTitleConfig() } - tv_text_bold.onClick { - ReadBookConfig.apply { - textBold = !textBold - tv_text_bold.isSelected = textBold - } + text_font_weight_converter.onChanged { postEvent(EventBus.UP_CONFIG, true) } tv_text_font.onClick { @@ -126,6 +139,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { dismiss() callBack?.showPaddingConfig() } + tv_tip.onClick { + TipConfigDialog().show(childFragmentManager, "tipConfigDialog") + } rg_page_anim.onCheckedChange { _, checkedId -> ReadBookConfig.pageAnim = rg_page_anim.getIndexById(checkedId) callBack?.page_view?.upPageAnim() @@ -153,16 +169,6 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { ReadBookConfig.paragraphSpacing = it postEvent(EventBus.UP_CONFIG, true) } - bg0.onClick { changeBg(0) } - bg0.onLongClick { showBgTextConfig(0) } - bg1.onClick { changeBg(1) } - bg1.onLongClick { showBgTextConfig(1) } - bg2.onClick { changeBg(2) } - bg2.onLongClick { showBgTextConfig(2) } - bg3.onClick { changeBg(3) } - bg3.onLongClick { showBgTextConfig(3) } - bg4.onClick { changeBg(4) } - bg4.onLongClick { showBgTextConfig(4) } } @SuppressLint("InflateParams") @@ -192,15 +198,17 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } } customView = rootView - }.show() + }.show().applyTint() } private fun changeBg(index: Int) { - if (ReadBookConfig.styleSelect != index) { + val oldIndex = ReadBookConfig.styleSelect + if (index != oldIndex) { ReadBookConfig.styleSelect = index ReadBookConfig.upBg() upStyle() - upBg() + styleAdapter.notifyItemChanged(oldIndex) + styleAdapter.notifyItemChanged(index) postEvent(EventBus.UP_CONFIG, true) } } @@ -214,7 +222,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 @@ -222,44 +229,53 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } } - private fun setBg() = ReadBookConfig.apply { - bg0.setTextColor(getConfig(0).textColor()) - bg1.setTextColor(getConfig(1).textColor()) - bg2.setTextColor(getConfig(2).textColor()) - bg3.setTextColor(getConfig(3).textColor()) - bg4.setTextColor(getConfig(4).textColor()) - for (i in 0..4) { - val iv = when (i) { - 1 -> bg1 - 2 -> bg2 - 3 -> bg3 - 4 -> bg4 - else -> bg0 - } - iv.setImageDrawable(getConfig(i).bgDrawable(100, 150)) + override val curFontPath: String + get() = ReadBookConfig.textFont + + override fun selectFont(path: String) { + if (path != ReadBookConfig.textFont) { + ReadBookConfig.textFont = path + postEvent(EventBus.UP_CONFIG, true) } } - private fun upBg() = requireContext().apply { - bg0.borderColor = primaryColor - bg1.borderColor = primaryColor - bg2.borderColor = primaryColor - bg3.borderColor = primaryColor - bg4.borderColor = primaryColor - when (ReadBookConfig.styleSelect) { - 1 -> bg1.borderColor = accentColor - 2 -> bg2.borderColor = accentColor - 3 -> bg3.borderColor = accentColor - 4 -> bg4.borderColor = accentColor - else -> bg0.borderColor = accentColor + inner class StyleAdapter : + SimpleRecyclerAdapter(requireContext(), R.layout.item_read_style) { + + override fun convert( + holder: ItemViewHolder, + item: ReadBookConfig.Config, + payloads: MutableList + ) { + holder.itemView.apply { + iv_style.setTextColor(item.textColor()) + iv_style.setImageDrawable(item.bgDrawable(100, 150)) + if (ReadBookConfig.styleSelect == holder.layoutPosition) { + iv_style.borderColor = accentColor + iv_style.setTextBold(true) + } else { + iv_style.borderColor = item.textColor() + iv_style.setTextBold(false) + } + } } - } - override val curFontPath: String - get() = requireContext().getPrefString(PreferKey.readBookFont) ?: "" + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + iv_style.onClick { + if (iv_style.isInView) { + changeBg(holder.layoutPosition) + } + } + iv_style.onLongClick { + if (iv_style.isInView) { + showBgTextConfig(holder.layoutPosition) + } else { + false + } + } + } + } - override fun selectFile(path: String) { - requireContext().putPrefString(PreferKey.readBookFont, path) - postEvent(EventBus.UP_CONFIG, true) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt new file mode 100644 index 000000000..6ee1d5bda --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt @@ -0,0 +1,164 @@ +package io.legado.app.ui.book.read.config + +import android.annotation.SuppressLint +import android.content.Context +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 androidx.appcompat.widget.Toolbar +import androidx.lifecycle.LiveData +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.constant.PreferKey +import io.legado.app.data.entities.HttpTTS +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.cancelButton +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.okButton +import io.legado.app.lib.theme.primaryColor +import io.legado.app.service.help.ReadAloud +import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_http_tts_edit.view.* +import kotlinx.android.synthetic.main.dialog_recycler_view.* +import kotlinx.android.synthetic.main.item_http_tts.view.* +import org.jetbrains.anko.sdk27.listeners.onClick + +class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.9).toInt()) + } + + lateinit var adapter: Adapter + lateinit var viewModel: SpeakEngineViewModel + private var httpTTSData: LiveData>? = null + var engineId = App.INSTANCE.getPrefLong(PreferKey.speakEngine) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + viewModel = getViewModel(SpeakEngineViewModel::class.java) + return inflater.inflate(R.layout.dialog_recycler_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + initView() + initMenu() + initData() + } + + private fun initView() { + tool_bar.setBackgroundColor(primaryColor) + tool_bar.setTitle(R.string.speak_engine) + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + adapter = Adapter(requireContext()) + recycler_view.adapter = adapter + tv_footer_left.setText(R.string.local_tts) + tv_footer_left.visible() + tv_footer_left.onClick { + removePref(PreferKey.speakEngine) + dismiss() + } + tv_ok.visible() + tv_ok.onClick { + putPrefLong(PreferKey.speakEngine, engineId) + dismiss() + } + tv_cancel.visible() + tv_cancel.onClick { + dismiss() + } + } + + private fun initMenu() { + tool_bar.inflateMenu(R.menu.speak_engine) + tool_bar.menu.applyTint(requireContext()) + tool_bar.setOnMenuItemClickListener(this) + } + + private fun initData() { + httpTTSData?.removeObservers(this) + httpTTSData = App.db.httpTTSDao().observeAll() + httpTTSData?.observe(this, { + adapter.setItems(it) + }) + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.menu_add -> editHttpTTS() + R.id.menu_default -> viewModel.importDefault() + } + return true + } + + @SuppressLint("InflateParams") + private fun editHttpTTS(v: HttpTTS? = null) { + val httpTTS = v?.copy() ?: HttpTTS() + requireContext().alert(titleResource = R.string.speak_engine) { + var rootView: View? = null + customView { + LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_http_tts_edit, null).apply { + rootView = this + tv_name.setText(httpTTS.name) + tv_url.setText(httpTTS.url) + } + } + cancelButton() + okButton { + rootView?.apply { + httpTTS.name = tv_name.text.toString() + httpTTS.url = tv_url.text.toString() + App.db.httpTTSDao().insert(httpTTS) + ReadAloud.upReadAloudClass() + } + } + }.show().applyTint() + } + + inner class Adapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_http_tts) { + + override fun convert(holder: ItemViewHolder, item: HttpTTS, payloads: MutableList) { + holder.itemView.apply { + cb_name.text = item.name + cb_name.isChecked = item.id == engineId + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + cb_name.onClick { + getItem(holder.layoutPosition)?.let { httpTTS -> + engineId = httpTTS.id + notifyItemRangeChanged(0, getActualItemCount()) + } + } + iv_edit.onClick { + editHttpTTS(getItem(holder.layoutPosition)) + } + iv_menu_delete.onClick { + getItem(holder.layoutPosition)?.let { httpTTS -> + App.db.httpTTSDao().delete(httpTTS) + } + } + } + } + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt new file mode 100644 index 000000000..490eb6efd --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt @@ -0,0 +1,34 @@ +package io.legado.app.ui.book.read.config + +import android.app.Application +import io.legado.app.App +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.TxtTocRule +import io.legado.app.help.DefaultValueHelp +import io.legado.app.help.http.HttpHelper +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray + +class SpeakEngineViewModel(application: Application) : BaseViewModel(application) { + + fun importDefault() { + execute { + DefaultValueHelp.initHttpTTS() + } + } + + fun importOnLine(url: String, finally: (msg: String) -> Unit) { + execute { + HttpHelper.simpleGetAsync(url)?.let { json -> + GSON.fromJsonArray(json)?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + } + } + }.onSuccess { + finally("导入成功") + }.onError { + finally("导入失败") + } + } + +} \ No newline at end of file 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..fbff59f3e --- /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.ReadBookConfig +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.theme.accentColor +import io.legado.app.ui.widget.text.StrokeTextView +import io.legado.app.utils.applyTint +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().applyTint() + } + + 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 new file mode 100644 index 000000000..583afb4d4 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt @@ -0,0 +1,255 @@ +package io.legado.app.ui.book.read.config + +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.constant.EventBus +import io.legado.app.help.ReadTipConfig +import io.legado.app.lib.dialogs.selector +import io.legado.app.utils.postEvent +import kotlinx.android.synthetic.main.dialog_tip_config.* +import org.jetbrains.anko.sdk27.listeners.onCheckedChange +import org.jetbrains.anko.sdk27.listeners.onClick + +class TipConfigDialog : BaseDialogFragment() { + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.let { + it.windowManager?.defaultDisplay?.getMetrics(dm) + } + dialog?.window + ?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.dialog_tip_config, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + initView() + initEvent() + } + + private fun initView() { + tv_header_left.text = ReadTipConfig.tipHeaderLeftStr + tv_header_middle.text = ReadTipConfig.tipHeaderMiddleStr + tv_header_right.text = ReadTipConfig.tipHeaderRightStr + tv_footer_left.text = ReadTipConfig.tipFooterLeftStr + tv_footer_middle.text = ReadTipConfig.tipFooterMiddleStr + tv_footer_right.text = ReadTipConfig.tipFooterRightStr + sw_hide_header.isChecked = ReadTipConfig.hideHeader + sw_hide_footer.isChecked = ReadTipConfig.hideFooter + } + + private fun initEvent() { + tv_header_left.onClick { + selector(items = ReadTipConfig.tipArray.toList()) { _, i -> + ReadTipConfig.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.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.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.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.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.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) + } + } + sw_hide_header.onCheckedChange { buttonView, isChecked -> + if (buttonView?.isPressed == true) { + ReadTipConfig.hideHeader = isChecked + postEvent(EventBus.UP_CONFIG, true) + } + } + sw_hide_footer.onCheckedChange { buttonView, isChecked -> + if (buttonView?.isPressed == true) { + ReadTipConfig.hideFooter = isChecked + postEvent(EventBus.UP_CONFIG, true) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt index d4a6711bc..c0bed1512 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 @@ -11,7 +11,6 @@ import android.view.ViewGroup import androidx.appcompat.widget.Toolbar import androidx.fragment.app.FragmentManager import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -21,13 +20,14 @@ import io.legado.app.R import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme import io.legado.app.data.entities.TxtTocRule -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.cancelButton import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.okButton +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* @@ -66,10 +66,11 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) durRegex = arguments?.getString("tocRegex") tool_bar.setTitle(R.string.txt_toc_regex) tool_bar.inflateMenu(R.menu.txt_toc_regex) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) initView() initData() @@ -102,7 +103,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { private fun initData() { tocRegexLiveData?.removeObservers(viewLifecycleOwner) tocRegexLiveData = App.db.txtTocRule().observeAll() - tocRegexLiveData?.observe(viewLifecycleOwner, Observer { tocRules -> + tocRegexLiveData?.observe(viewLifecycleOwner, { tocRules -> initSelectedName(tocRules) adapter.setItems(tocRules) }) @@ -189,7 +190,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() @@ -203,6 +204,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { override fun convert(holder: ItemViewHolder, item: TxtTocRule, payloads: MutableList) { holder.itemView.apply { if (payloads.isEmpty()) { + setBackgroundColor(context.backgroundColor) rb_regex_name.text = item.name rb_regex_name.isChecked = item.name == selectedName swt_enabled.isChecked = item.enable 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 deleted file mode 100644 index 0d6a0009e..000000000 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt +++ /dev/null @@ -1,299 +0,0 @@ -package io.legado.app.ui.book.read.page - -import android.graphics.Typeface -import android.text.Layout -import android.text.StaticLayout -import android.text.TextPaint -import android.text.TextUtils -import io.legado.app.App -import io.legado.app.constant.PreferKey -import io.legado.app.data.entities.BookChapter -import io.legado.app.help.AppConfig -import io.legado.app.help.ReadBookConfig -import io.legado.app.ui.book.read.page.entities.TextChapter -import io.legado.app.ui.book.read.page.entities.TextLine -import io.legado.app.ui.book.read.page.entities.TextPage -import io.legado.app.utils.* - - -@Suppress("DEPRECATION") -object ChapterProvider { - var viewWidth = 0 - var viewHeight = 0 - var paddingLeft = 0 - var paddingTop = 0 - var visibleWidth = 0 - var visibleHeight = 0 - var visibleRight = 0 - var visibleBottom = 0 - private var lineSpacingExtra = 0 - private var paragraphSpacing = 0 - private var titleTopSpacing = 0 - private var titleBottomSpacing = 0 - var typeface: Typeface = Typeface.SANS_SERIF - var titlePaint = TextPaint() - var contentPaint = TextPaint() - - init { - upStyle() - } - - /** - * 获取拆分完的章节数据 - */ - fun getTextChapter( - bookChapter: BookChapter, - content: String, - 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()) { - val isTitle = index == 0 - if (isTitle && ReadBookConfig.titleMode == 2) { - continue - } - durY = - setTypeText(text, durY, textPages, pageLines, pageLengths, stringBuilder, isTitle) - } - textPages.last().height = durY + 20.dp - textPages.last().text = stringBuilder.toString() - if (pageLines.size < textPages.size) { - pageLines.add(textPages.last().textLines.size) - } - if (pageLengths.size < textPages.size) { - pageLengths.add(textPages.last().text.length) - } - for ((index, item) in textPages.withIndex()) { - item.index = index - item.pageSize = textPages.size - item.chapterIndex = bookChapter.index - item.chapterSize = chapterSize - item.title = bookChapter.title - item.upLinesPosition() - } - return TextChapter( - bookChapter.index, - bookChapter.title, - bookChapter.url, - textPages, - pageLines, - pageLengths, - chapterSize - ) - } - - /** - * 排版文字 - */ - private fun setTypeText( - text: String, - y: Float, - textPages: ArrayList, - pageLines: ArrayList, - pageLengths: ArrayList, - stringBuilder: StringBuilder, - isTitle: Boolean - ): Float { - var durY = if (isTitle) y + titleTopSpacing else y - val textPaint = if (isTitle) titlePaint else contentPaint - val layout = StaticLayout( - text, textPaint, visibleWidth, - Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true - ) - for (lineIndex in 0 until layout.lineCount) { - val textLine = TextLine(isTitle = isTitle) - val words = - text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) - textLine.text = words - val desiredWidth = layout.getLineWidth(lineIndex) - var isLastLine = false - if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) { - //第一行 - addCharsToLineFirst(textLine, words, textPaint, desiredWidth) - } else if (lineIndex == layout.lineCount - 1) { - //最后一行 - isLastLine = true - val x = if (isTitle && ReadBookConfig.titleMode == 1) - (visibleWidth - layout.getLineWidth(lineIndex)) / 2 - else 0f - addCharsToLineLast(textLine, words, textPaint, x) - } else { - //中间行 - addCharsToLineMiddle(textLine, words, textPaint, desiredWidth, 0f) - } - if (durY + textPaint.textHeight > visibleHeight) { - //当前页面结束,设置各种值 - textPages.last().text = stringBuilder.toString() - pageLines.add(textPages.last().textLines.size) - pageLengths.add(textPages.last().text.length) - textPages.last().height = durY - //新建页面 - textPages.add(TextPage()) - stringBuilder.clear() - durY = 0f - } - stringBuilder.append(words) - if (isLastLine) stringBuilder.append("\n") - textPages.last().textLines.add(textLine) - textLine.upTopBottom(durY, textPaint) - durY += textPaint.textHeight * lineSpacingExtra / 10f - textPages.last().height = durY - } - if (isTitle) durY += titleBottomSpacing - durY += textPaint.textHeight * paragraphSpacing / 10f - return durY - } - - /** - * 有缩进,两端对齐 - */ - private fun addCharsToLineFirst( - textLine: TextLine, - words: String, - textPaint: TextPaint, - desiredWidth: Float - ) { - var x = 0f - val bodyIndent = ReadBookConfig.bodyIndent - val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length - bodyIndent.toStringArray().forEach { - val x1 = x + icw - textLine.addTextChar( - charData = it, - start = paddingLeft + x, - end = paddingLeft + x1 - ) - x = x1 - } - val words1 = words.replaceFirst(bodyIndent, "") - addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x) - } - - /** - * 无缩进,两端对齐 - */ - private fun addCharsToLineMiddle( - textLine: TextLine, - words: String, - textPaint: TextPaint, - desiredWidth: Float, - startX: Float - ) { - 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) - textLine.addTextChar( - charData = char, - start = paddingLeft + x, - end = paddingLeft + x1 - ) - x = x1 - } - exceed(textLine, words) - } - - /** - * 最后一行,自然排列 - */ - private fun addCharsToLineLast( - textLine: TextLine, - words: String, - textPaint: TextPaint, - startX: Float - ) { - textLine.text = "$words\n" - var x = startX - words.toStringArray().forEach { - val cw = StaticLayout.getDesiredWidth(it, textPaint) - val x1 = x + cw - textLine.addTextChar( - charData = it, - start = paddingLeft + x, - end = paddingLeft + x1 - ) - x = x1 - } - exceed(textLine, words) - } - - /** - * 超出边界处理 - */ - private fun exceed(textLine: TextLine, words: String) { - val endX = textLine.textChars.last().end - if (endX > visibleRight) { - val cc = (endX - visibleRight) / words.length - for (i in 0..words.lastIndex) { - textLine.getTextCharReverseAt(i).let { - val py = cc * (words.length - i) - it.start = it.start - py - it.end = it.end - py - } - } - } - } - - /** - * 更新样式 - */ - fun upStyle() { - typeface = try { - val fontPath = App.INSTANCE.getPrefString(PreferKey.readBookFont) - if (!TextUtils.isEmpty(fontPath)) { - Typeface.createFromFile(fontPath) - } else { - when (AppConfig.systemTypefaces) { - 1 -> Typeface.SERIF - 2 -> Typeface.MONOSPACE - else -> Typeface.SANS_SERIF - } - } - } catch (e: Exception) { - App.INSTANCE.removePref(PreferKey.readBookFont) - Typeface.SANS_SERIF - } - //标题 - titlePaint.isAntiAlias = true - titlePaint.color = ReadBookConfig.durConfig.textColor() - titlePaint.letterSpacing = ReadBookConfig.letterSpacing - titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) - titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat() - //正文 - contentPaint.isAntiAlias = true - contentPaint.color = ReadBookConfig.durConfig.textColor() - contentPaint.letterSpacing = ReadBookConfig.letterSpacing - val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL - contentPaint.typeface = Typeface.create(typeface, style) - contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() - //间距 - lineSpacingExtra = ReadBookConfig.lineSpacingExtra - paragraphSpacing = ReadBookConfig.paragraphSpacing - titleTopSpacing = ReadBookConfig.titleTopSpacing.dp - titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp - upSize() - } - - /** - * 更新View尺寸 - */ - fun upSize() { - paddingLeft = ReadBookConfig.paddingLeft.dp - paddingTop = ReadBookConfig.paddingTop.dp - visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp - visibleHeight = viewHeight - paddingTop - ReadBookConfig.paddingBottom.dp - visibleRight = paddingLeft + visibleWidth - visibleBottom = paddingTop + visibleHeight - } - - val TextPaint.textHeight: Float - get() = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 53f03462f..295ccb0ef 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 @@ -5,16 +5,23 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.util.AttributeSet +import android.util.Log import android.view.View import io.legado.app.R import io.legado.app.constant.PreferKey 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.entities.TextChar +import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.book.read.page.provider.ImageProvider +import io.legado.app.ui.widget.dialog.PhotoDialog import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefBoolean +import kotlinx.coroutines.CoroutineScope class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { @@ -59,9 +66,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - ChapterProvider.viewWidth = w - ChapterProvider.viewHeight = h - ChapterProvider.upSize() + ChapterProvider.upViewSize(w, h) upVisibleRect() textPage.format() } @@ -78,18 +83,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private fun drawPage(canvas: Canvas) { var relativeOffset = relativeOffset(0) textPage.textLines.forEach { textLine -> - val lineTop = textLine.lineTop + relativeOffset - val lineBase = textLine.lineBase + relativeOffset - val lineBottom = textLine.lineBottom + relativeOffset - drawChars( - canvas, - textLine.textChars, - lineTop, - lineBase, - lineBottom, - textLine.isTitle, - textLine.isReadAloud - ) + draw(canvas, textLine, relativeOffset) } if (!ReadBookConfig.isScroll) return //滚动翻页 @@ -97,37 +91,38 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at val nextPage = relativePage(1) relativeOffset = relativeOffset(1) nextPage.textLines.forEach { textLine -> - val lineTop = textLine.lineTop + relativeOffset - val lineBase = textLine.lineBase + relativeOffset - val lineBottom = textLine.lineBottom + relativeOffset + draw(canvas, textLine, relativeOffset) + } + if (!pageFactory.hasNextPlus()) return + relativeOffset = relativeOffset(2) + if (relativeOffset < ChapterProvider.visibleHeight) { + relativePage(2).textLines.forEach { textLine -> + draw(canvas, textLine, relativeOffset) + } + } + } + + private fun draw( + canvas: Canvas, + textLine: TextLine, + relativeOffset: Float, + ) { + val lineTop = textLine.lineTop + relativeOffset + val lineBase = textLine.lineBase + relativeOffset + val lineBottom = textLine.lineBottom + relativeOffset + if (textLine.isImage) { + drawImage(canvas, textLine, lineTop, lineBottom) + } else { drawChars( canvas, textLine.textChars, lineTop, lineBase, lineBottom, - textLine.isTitle, - textLine.isReadAloud + isTitle = textLine.isTitle, + isReadAloud = textLine.isReadAloud ) } - if (!pageFactory.hasNextPlus()) return - relativeOffset = relativeOffset(2) - if (relativeOffset < ChapterProvider.visibleHeight) { - relativePage(2).textLines.forEach { textLine -> - val lineTop = textLine.lineTop + relativeOffset - val lineBase = textLine.lineBase + relativeOffset - val lineBottom = textLine.lineBottom + relativeOffset - drawChars( - canvas, - textLine.textChars, - lineTop, - lineBase, - lineBottom, - textLine.isTitle, - textLine.isReadAloud - ) - } - } } /** @@ -140,11 +135,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at lineBase: Float, lineBottom: Float, isTitle: Boolean, - isReadAloud: Boolean + isReadAloud: Boolean, ) { 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) { @@ -153,6 +148,26 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } } + /** + * 绘制图片 + */ + private fun drawImage( + canvas: Canvas, + textLine: TextLine, + lineTop: Float, + lineBottom: Float, + ) { + textLine.textChars.forEach { textChar -> + ReadBook.book?.let { book -> + val rectF = RectF(textChar.start, lineTop, textChar.end, lineBottom) + ImageProvider.getImage(book, textPage.chapterIndex, textChar.charData, true) + ?.let { + canvas.drawBitmap(it, null, rectF, null) + } + } + } + } + /** * 滚动事件 */ @@ -189,97 +204,44 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } /** - * 选择初始文字 + * 选择文字 */ fun selectText( x: Float, y: Float, - select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit + select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit, ) { if (!selectAble) return if (!visibleRect.contains(x, y)) return - var relativeOffset = relativeOffset(0) - for ((lineIndex, textLine) in textPage.textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - textChar.selected = true - invalidate() - selectStart[0] = 0 - selectStart[1] = lineIndex - selectStart[2] = charIndex - selectEnd[0] = 0 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - select(0, lineIndex, charIndex) - return - } - } - return - } - } - if (!ReadBookConfig.isScroll) return - //滚动翻页 - relativeOffset = relativeOffset(1) - if (relativeOffset >= ChapterProvider.visibleHeight) return - val nextPage = relativePage(1) - for ((lineIndex, textLine) in nextPage.textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - textChar.selected = true - invalidate() - selectStart[0] = 1 - selectStart[1] = lineIndex - selectStart[2] = charIndex - selectEnd[0] = 1 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - select(1, lineIndex, charIndex) - return - } - } - return + var relativeOffset: Float + for (relativePos in 0..2) { + relativeOffset = relativeOffset(relativePos) + if (relativePos > 0) { + //滚动翻页 + if (!ReadBookConfig.isScroll) return + if (relativeOffset >= ChapterProvider.visibleHeight) return } - } - relativeOffset = relativeOffset(2) - if (relativeOffset >= ChapterProvider.visibleHeight) return - for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - textChar.selected = true - invalidate() - selectStart[0] = 2 - selectStart[1] = lineIndex - selectStart[2] = charIndex - selectEnd[0] = 2 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - select(2, lineIndex, charIndex) - return + val page = relativePage(relativePos) + for ((lineIndex, textLine) in page.textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (textChar.isImage) { + activity?.supportFragmentManager?.let { + PhotoDialog.show(it, page.chapterIndex, textChar.charData) + } + } else { + textChar.selected = true + invalidate() + select(relativePos, lineIndex, charIndex) + } + return + } } + return } - return } + } } @@ -288,83 +250,37 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ fun selectStartMove(x: Float, y: Float) { if (!visibleRect.contains(x, y)) return - var relativeOffset = relativeOffset(0) - for ((lineIndex, textLine) in textPage.textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectStart[0] != 0 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { - if (selectToInt(0, lineIndex, charIndex) > selectToInt(selectEnd)) { - return - } - selectStart[0] = 0 - selectStart[1] = lineIndex - selectStart[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectChars() - } - return - } - } - return + var relativeOffset: Float + for (relativePos in 0..2) { + relativeOffset = relativeOffset(relativePos) + if (relativePos > 0) { + //滚动翻页 + if (!ReadBookConfig.isScroll) return + if (relativeOffset >= ChapterProvider.visibleHeight) return } - } - if (!ReadBookConfig.isScroll) return - //滚动翻页 - relativeOffset = relativeOffset(1) - if (relativeOffset >= ChapterProvider.visibleHeight) return - for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectStart[0] != 1 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { - if (selectToInt(1, lineIndex, charIndex) > selectToInt(selectEnd)) { - return - } - selectStart[0] = 1 - selectStart[1] = lineIndex - selectStart[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectChars() - } - return - } - } - return - } - } - relativeOffset = relativeOffset(2) - if (relativeOffset >= ChapterProvider.visibleHeight) return - for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectStart[0] != 2 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { - if (selectToInt(2, lineIndex, charIndex) > selectToInt(selectEnd)) { - return + for ((lineIndex, textLine) in relativePage(relativePos).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + if (selectStart[0] != relativePos || selectStart[1] != lineIndex || selectStart[2] != charIndex) { + if (selectToInt(relativePos, lineIndex, charIndex) > selectToInt(selectEnd)) { + return + } + selectStart[0] = relativePos + selectStart[1] = lineIndex + selectStart[2] = charIndex + upSelectedStart( + textChar.start, + textLine.lineBottom + relativeOffset, + textLine.lineTop + relativeOffset + ) + upSelectChars() } - selectStart[0] = 2 - selectStart[1] = lineIndex - selectStart[2] = charIndex - upSelectedStart( - textChar.start, - textLine.lineBottom + relativeOffset, - textLine.lineTop + relativeOffset - ) - upSelectChars() + return } - return } + return } - return } } } @@ -374,71 +290,36 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ fun selectEndMove(x: Float, y: Float) { if (!visibleRect.contains(x, y)) return - var relativeOffset = relativeOffset(0) - for ((lineIndex, textLine) in textPage.textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectEnd[0] != 0 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { - if (selectToInt(0, lineIndex, charIndex) < selectToInt(selectStart)) { - return - } - selectEnd[0] = 0 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - upSelectChars() - } - return - } - } - return - } - } - if (!ReadBookConfig.isScroll) return - //滚动翻页 - relativeOffset = relativeOffset(1) - if (relativeOffset >= ChapterProvider.visibleHeight) return - for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectEnd[0] != 1 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { - if (selectToInt(1, lineIndex, charIndex) < selectToInt(selectStart)) { - return - } - selectEnd[0] = 1 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - upSelectChars() - } - return - } - } - return + var relativeOffset: Float + for (relativePos in 0..2) { + relativeOffset = relativeOffset(relativePos) + if (relativePos > 0) { + //滚动翻页 + if (!ReadBookConfig.isScroll) return + if (relativeOffset >= ChapterProvider.visibleHeight) return } - } - relativeOffset = relativeOffset(2) - if (relativeOffset >= ChapterProvider.visibleHeight) return - for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { - if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { - for ((charIndex, textChar) in textLine.textChars.withIndex()) { - if (x > textChar.start && x < textChar.end) { - if (selectEnd[0] != 2 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { - if (selectToInt(2, lineIndex, charIndex) < selectToInt(selectStart)) { - return + Log.e("y", "$y") + for ((lineIndex, textLine) in relativePage(relativePos).textLines.withIndex()) { + if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { + Log.e("line", "$relativePos $lineIndex") + for ((charIndex, textChar) in textLine.textChars.withIndex()) { + if (x > textChar.start && x < textChar.end) { + Log.e("char", "$relativePos $lineIndex $charIndex") + if (selectEnd[0] != relativePos || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { + if (selectToInt(relativePos, lineIndex, charIndex) < selectToInt(selectStart)) { + return + } + selectEnd[0] = relativePos + selectEnd[1] = lineIndex + selectEnd[2] = charIndex + upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) + upSelectChars() } - selectEnd[0] = 2 - selectEnd[1] = lineIndex - selectEnd[2] = charIndex - upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) - upSelectChars() + return } - return } + return } - return } } } @@ -455,7 +336,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at upSelectedStart( textChar.start, textLine.lineBottom + relativeOffset(relativePage), - textLine.lineTop + textLine.lineTop + relativeOffset(relativePage) ) upSelectChars() } @@ -585,11 +466,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } private fun selectToInt(page: Int, line: Int, char: Int): Int { - return page * 1000000 + line * 100000 + char + return page * 10000000 + line * 100000 + char } private fun selectToInt(select: Array): Int { - return select[0] * 1000000 + select[1] * 100000 + select[2] + return select[0] * 10000000 + select[1] * 100000 + select[2] } private fun relativeOffset(relativePos: Int): Float { @@ -614,5 +495,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at fun onCancelSelect() val headerHeight: Int val pageFactory: TextPageFactory + val scope: CoroutineScope } } 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 1cc006512..e89ee5ae8 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 @@ -3,30 +3,44 @@ package io.legado.app.ui.book.read.page import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable -import android.view.MotionEvent import android.widget.FrameLayout -import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap +import androidx.core.view.isGone +import androidx.core.view.isInvisible import io.legado.app.R +import io.legado.app.base.BaseActivity import io.legado.app.constant.AppConst.timeFormat -import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig +import io.legado.app.help.ReadTipConfig import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.widget.BatteryView import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_book_page.view.* +import org.jetbrains.anko.topPadding import java.util.* class ContentView(context: Context) : FrameLayout(context) { private var battery = 100 + private var tvTitle: BatteryView? = null + private var tvTime: BatteryView? = null + private var tvBattery: BatteryView? = null + private var tvPage: BatteryView? = null + private var tvTotalProgress: BatteryView? = null + private var tvPageAndTotal: BatteryView? = null + + val headerHeight: Int + get() = if (ReadBookConfig.hideStatusBar) { + if (ll_header.isGone) 0 else ll_header.height + } else context.statusBarHeight init { //设置背景防止切换背景时文字重叠 setBackgroundColor(context.getCompatColor(R.color.background)) inflate(context, R.layout.view_book_page, this) - + upTipStyle() upStyle() - upTime() content_text_view.upView = { setProgress(it) } @@ -34,28 +48,29 @@ class ContentView(context: Context) : FrameLayout(context) { fun upStyle() { ReadBookConfig.apply { - tv_top_left.typeface = ChapterProvider.typeface - tv_top_right.typeface = ChapterProvider.typeface - tv_bottom_left.typeface = ChapterProvider.typeface - tv_bottom_right.typeface = ChapterProvider.typeface - battery_view.typeface = ChapterProvider.typeface - //显示状态栏时隐藏header - if (hideStatusBar) { - ll_header.layoutParams = ll_header.layoutParams.apply { - height = context.statusBarHeight + headerPaddingTop.dp + headerPaddingBottom.dp - } - ll_header.setPadding( - headerPaddingLeft.dp, - headerPaddingTop.dp, - headerPaddingRight.dp, - headerPaddingBottom.dp - ) - ll_header.visible() - page_panel.setPadding(0, 0, 0, 0) - } else { - ll_header.gone() - page_panel.setPadding(0, context.statusBarHeight, 0, 0) - } + 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 + 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) + upStatusBar() + ll_header.setPadding( + headerPaddingLeft.dp, + headerPaddingTop.dp, + headerPaddingRight.dp, + headerPaddingBottom.dp + ) ll_footer.setPadding( footerPaddingLeft.dp, footerPaddingTop.dp, @@ -65,47 +80,124 @@ class ContentView(context: Context) : FrameLayout(context) { vw_top_divider.visible(showHeaderLine) vw_bottom_divider.visible(showFooterLine) content_text_view.upVisibleRect() - durConfig.textColor().let { - tv_top_left.setTextColor(it) - tv_top_right.setTextColor(it) - tv_bottom_left.setTextColor(it) - tv_bottom_right.setTextColor(it) - battery_view.setColor(it) - } - if (hideStatusBar) { - tv_bottom_left.text = timeFormat.format(Date(System.currentTimeMillis())) - battery_view.visible() - battery_view.setBattery(battery) - } else { - battery_view.gone() - } } + upTime() + upBattery(battery) } - val headerHeight: Int - get() { - return if (ReadBookConfig.hideStatusBar) { - ll_header.height - } else { - context.statusBarHeight - } + /** + * 显示状态栏时隐藏header + */ + fun upStatusBar() { + vw_status_bar.topPadding = context.statusBarHeight + vw_status_bar.isGone = + ReadBookConfig.hideStatusBar || (activity as? BaseActivity)?.isInMultiWindow == true + } + + fun upTipStyle() { + ReadTipConfig.apply { + 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 + } + tvTitle = when (ReadTipConfig.chapterTitle) { + ReadTipConfig.tipHeaderLeft -> tv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> tv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvTitle?.apply { + isBattery = false + textSize = 12f + } + tvTime = when (ReadTipConfig.time) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvTime?.apply { + isBattery = false + textSize = 12f } + tvBattery = when (ReadTipConfig.battery) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvBattery?.apply { + isBattery = true + textSize = 10f + } + tvPage = when (ReadTipConfig.page) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvPage?.apply { + isBattery = false + textSize = 12f + } + tvTotalProgress = when (ReadTipConfig.totalProgress) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvTotalProgress?.apply { + isBattery = false + textSize = 12f + } + tvPageAndTotal = when (ReadTipConfig.pageAndTotal) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvPageAndTotal?.apply { + isBattery = false + textSize = 12f + } + } fun setBg(bg: Drawable?) { page_panel.background = bg } fun upTime() { - if (ReadBookConfig.hideStatusBar) { - tv_bottom_right.text = timeFormat.format(Date(System.currentTimeMillis())) - } + tvTime?.text = timeFormat.format(Date(System.currentTimeMillis())) } fun upBattery(battery: Int) { this.battery = battery - if (ReadBookConfig.hideStatusBar) { - battery_view.setBattery(battery) - } + tvBattery?.setBattery(battery) } fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) { @@ -121,19 +213,11 @@ class ContentView(context: Context) : FrameLayout(context) { @SuppressLint("SetTextI18n") fun setProgress(textPage: TextPage) = textPage.apply { - val title = when (AppConfig.chineseConverterType) { - 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) - 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) - else -> textPage.title - } - if (ReadBookConfig.hideStatusBar) { - tv_top_left.text = title - tv_top_right.text = readProgress - tv_bottom_left.text = "${index.plus(1)}/$pageSize" - } else { - tv_bottom_left.text = title - tv_bottom_right.text = "${index.plus(1)}/$pageSize $readProgress" - } + val title = textPage.title + tvTitle?.text = title + tvPage?.text = "${index.plus(1)}/$pageSize" + tvTotalProgress?.text = readProgress + tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress" } fun onScroll(offset: Float) { @@ -145,11 +229,10 @@ class ContentView(context: Context) : FrameLayout(context) { } fun selectText( - e: MotionEvent, - select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit + x: Float, y: Float, + select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit, ) { - val y = e.y - headerHeight - return content_text_view.selectText(e.x, y, select) + return content_text_view.selectText(x, y - headerHeight, select) } fun selectStartMove(x: Float, y: Float) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index dd19fdc0e..3d4d6e266 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,24 @@ 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.graphics.RectF import android.util.AttributeSet import android.view.MotionEvent +import android.view.ViewConfiguration import android.widget.FrameLayout +import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.theme.accentColor import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.delegate.* import io.legado.app.ui.book.read.page.entities.TextChapter +import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.activity +import io.legado.app.utils.screenshot +import kotlinx.android.synthetic.main.activity_book_read.view.* +import kotlin.math.abs class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs), @@ -23,6 +33,46 @@ class PageView(context: Context, attrs: AttributeSet) : var prevPage: ContentView = ContentView(context) var curPage: ContentView = ContentView(context) var nextPage: ContentView = ContentView(context) + val defaultAnimationSpeed = 300 + private var pressDown = false + private var isMove = false + + //起始点 + var startX: Float = 0f + var startY: Float = 0f + + //上一个触碰点 + var lastX: Float = 0f + var lastY: Float = 0f + + //触碰点 + var touchX: Float = 0f + var touchY: Float = 0f + + //是否停止动画动作 + var isAbortAnim = false + + //长按 + private var longPressed = false + private val longPressTimeout = 600L + private val longPressRunnable = Runnable { + longPressed = true + onLongPress() + } + var isTextSelected = false + private var pressOnTextSelected = false + private var firstRelativePage = 0 + private var firstLineIndex: Int = 0 + private var firstCharIndex: Int = 0 + + val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop } + private val centerRectF = RectF(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f) + private val autoPageRect by lazy { Rect() } + private val autoPagePint by lazy { + Paint().apply { + color = context.accentColor + } + } init { addView(nextPage) @@ -35,6 +85,7 @@ class PageView(context: Context, attrs: AttributeSet) : override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) + centerRectF.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f) prevPage.x = -w.toFloat() pageDelegate?.setViewSize(w, h) if (oldw != 0 && oldh != 0) { @@ -44,8 +95,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() { @@ -56,13 +121,171 @@ class PageView(context: Context, attrs: AttributeSet) : return true } + /** + * 触摸事件 + */ @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { - pageDelegate?.onTouch(event) callBack.screenOffTimerStart() + when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (isTextSelected) { + curPage.cancelSelect() + isTextSelected = false + pressOnTextSelected = true + } else { + pressOnTextSelected = false + } + longPressed = false + postDelayed(longPressRunnable, longPressTimeout) + pressDown = true + isMove = false + pageDelegate?.onTouch(event) + pageDelegate?.onDown() + setStartPoint(event.x, event.y) + } + MotionEvent.ACTION_MOVE -> { + pressDown = true + if (!isMove) { + isMove = + abs(startX - event.x) > slopSquare || abs(startY - event.y) > slopSquare + } + if (isMove) { + longPressed = false + removeCallbacks(longPressRunnable) + if (isTextSelected) { + selectText(event.x, event.y) + } else { + pageDelegate?.onTouch(event) + } + } + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + removeCallbacks(longPressRunnable) + if (!pressDown) return true + if (!isMove) { + if (!longPressed && !pressOnTextSelected) { + onSingleTapUp() + return true + } + } + if (isTextSelected) { + callBack.showTextActionMenu() + } else if (isMove) { + pageDelegate?.onTouch(event) + } + pressOnTextSelected = false + } + } return true } + fun upStatusBar() { + curPage.upStatusBar() + prevPage.upStatusBar() + nextPage.upStatusBar() + } + + /** + * 保存开始位置 + */ + fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { + startX = x + startY = y + lastX = x + lastY = y + touchX = x + touchY = y + + if (invalidate) { + invalidate() + } + } + + /** + * 保存当前位置 + */ + fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { + lastX = touchX + lastY = touchY + touchX = x + touchY = y + if (invalidate) { + invalidate() + } + pageDelegate?.onScroll() + } + + /** + * 长按选择 + */ + private fun onLongPress() { + curPage.selectText(startX, startY) { relativePage, lineIndex, charIndex -> + isTextSelected = true + firstRelativePage = relativePage + firstLineIndex = lineIndex + firstCharIndex = charIndex + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + } + } + + /** + * 单击 + */ + private fun onSingleTapUp(): Boolean { + if (isTextSelected) { + isTextSelected = false + return true + } + if (centerRectF.contains(startX, startY)) { + if (!isAbortAnim) { + callBack.clickCenter() + } + } else if (ReadBookConfig.clickTurnPage) { + if (startX > width / 2 || AppConfig.clickAllNext) { + pageDelegate?.nextPageByAnim(defaultAnimationSpeed) + } else { + pageDelegate?.prevPageByAnim(defaultAnimationSpeed) + } + } + return true + } + + /** + * 选择文本 + */ + private fun selectText(x: Float, y: Float) { + curPage.selectText(x, y) { relativePage, lineIndex, charIndex -> + when { + relativePage > firstRelativePage -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + relativePage < firstRelativePage -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + lineIndex > firstLineIndex -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + lineIndex < firstLineIndex -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + charIndex > firstCharIndex -> { + curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) + } + else -> { + curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) + curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) + } + } + } + } + fun onDestroy() { pageDelegate?.onDestroy() curPage.cancelSelect() @@ -94,9 +317,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) @@ -110,6 +334,12 @@ class PageView(context: Context, attrs: AttributeSet) : callBack.screenOffTimerStart() } + fun upTipStyle() { + curPage.upTipStyle() + prevPage.upTipStyle() + nextPage.upTipStyle() + } + fun upStyle() { ChapterProvider.upStyle() curPage.upStyle() @@ -140,18 +370,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 @@ -163,6 +393,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/CoverPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt index 05437cd70..278b8fffc 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt @@ -57,7 +57,7 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { } } - override fun onAnimStart() { + override fun onAnimStart(animationSpeed: Int) { val distanceX: Float when (mDirection) { Direction.NEXT -> distanceX = @@ -77,7 +77,7 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { viewWidth - (touchX - startX) } } - startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) + startScroll(touchX.toInt(), 0, distanceX.toInt(), 0, animationSpeed) } } 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 8fe4fb699..a224edd5b 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 @@ -37,17 +37,15 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie override fun onTouch(event: MotionEvent) { when (event.action) { MotionEvent.ACTION_DOWN -> { - abort() + abortAnim() } MotionEvent.ACTION_MOVE -> { - if (isTextSelected) { - selectText(event) - } else { - onScroll(event) - } + onScroll(event) + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + onAnimStart(pageView.defaultAnimationSpeed) } } - super.onTouch(event) } private fun onScroll(event: MotionEvent) { @@ -73,7 +71,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie val deltaX = (focusX - startX).toInt() val deltaY = (focusY - startY).toInt() val distance = deltaX * deltaX + deltaY * deltaY - isMoved = distance > slopSquare + isMoved = distance > pageView.slopSquare if (isMoved) { if (sumX - startX > 0) { //如果上一页不存在 @@ -96,24 +94,40 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie isCancel = if (mDirection == Direction.NEXT) sumX > lastX else sumX < lastX isRunning = true //设置触摸点 - setTouchPoint(sumX, sumY) + pageView.setTouchPoint(sumX, sumY) + } + } + + override fun abortAnim() { + isStarted = false + isMoved = false + isRunning = false + if (!scroller.isFinished) { + pageView.isAbortAnim = true + scroller.abortAnimation() + if (!isCancel) { + pageView.fillPage(mDirection) + pageView.invalidate() + } + } else { + pageView.isAbortAnim = false } } - override fun nextPageByAnim() { - super.nextPageByAnim() + override fun nextPageByAnim(animationSpeed: Int) { + abortAnim() if (!hasNext()) return setDirection(Direction.NEXT) - setTouchPoint(viewWidth.toFloat(), 0f) - onAnimStart() + pageView.setTouchPoint(viewWidth.toFloat(), 0f) + onAnimStart(animationSpeed) } - override fun prevPageByAnim() { - super.prevPageByAnim() + override fun prevPageByAnim(animationSpeed: Int) { + abortAnim() if (!hasPrev()) return setDirection(Direction.PREV) - setTouchPoint(0f, 0f) - onAnimStart() + pageView.setTouchPoint(0f, 0f) + onAnimStart(animationSpeed) } override fun onDestroy() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt index cf18cb1db..7414df401 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt @@ -1,14 +1,24 @@ package io.legado.app.ui.book.read.page.delegate +import android.graphics.Canvas import io.legado.app.ui.book.read.page.PageView class NoAnimPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { - override fun onAnimStart() { + override fun onAnimStart(animationSpeed: Int) { if (!isCancel) { pageView.fillPage(mDirection) } stopScroll() } + override fun onDraw(canvas: Canvas) { + // nothing + } + + override fun onAnimStop() { + // nothing + } + + } \ No newline at end of file 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 d83f60edb..3a2dabc1d 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 @@ -2,37 +2,31 @@ package io.legado.app.ui.book.read.page.delegate import android.content.Context import android.graphics.Canvas -import android.graphics.RectF -import android.view.GestureDetector import android.view.MotionEvent -import android.view.ViewConfiguration import android.view.animation.DecelerateInterpolator import android.widget.Scroller import androidx.annotation.CallSuper import com.google.android.material.snackbar.Snackbar -import io.legado.app.help.AppConfig -import io.legado.app.help.ReadBookConfig +import io.legado.app.R import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.PageView import kotlin.math.abs -abstract class PageDelegate(protected val pageView: PageView) : - GestureDetector.SimpleOnGestureListener() { - private val centerRectF = RectF( - pageView.width * 0.33f, pageView.height * 0.33f, - pageView.width * 0.66f, pageView.height * 0.66f - ) +abstract class PageDelegate(protected val pageView: PageView) { + protected val context: Context = pageView.context //起始点 - protected var startX: Float = 0f - protected var startY: Float = 0f + protected val startX: Float get() = pageView.startX + protected val startY: Float get() = pageView.startY + //上一个触碰点 - protected var lastX: Float = 0f - protected var lastY: Float = 0f + protected val lastX: Float get() = pageView.lastX + protected val lastY: Float get() = pageView.lastY + //触碰点 - protected var touchX: Float = 0f - protected var touchY: Float = 0f + protected val touchX: Float get() = pageView.touchX + protected val touchY: Float get() = pageView.touchY protected val nextPage: ContentView get() = pageView.nextPage protected val curPage: ContentView get() = pageView.curPage @@ -41,19 +35,10 @@ abstract class PageDelegate(protected val pageView: PageView) : protected var viewWidth: Int = pageView.width protected var viewHeight: Int = pageView.height - private val scroller: Scroller by lazy { + protected val scroller: Scroller by lazy { Scroller(pageView.context, DecelerateInterpolator()) } - protected val slopSquare by lazy { - val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop - scaledTouchSlop * scaledTouchSlop - } - - private val detector: GestureDetector by lazy { - GestureDetector(pageView.context, this) - } - private val snackBar: Snackbar by lazy { Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT) } @@ -65,44 +50,14 @@ abstract class PageDelegate(protected val pageView: PageView) : var mDirection = Direction.NONE var isCancel = false var isRunning = false - private var isStarted = false - var isTextSelected = false - private var selectedOnDown = false + var isStarted = false - private var firstRelativePage = 0 - private var firstLineIndex: Int = 0 - private var firstCharIndex: Int = 0 + private var selectedOnDown = false init { curPage.resetPageOffset() } - open fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { - startX = x - startY = y - lastX = x - lastY = y - touchX = x - touchY = y - - if (invalidate) { - pageView.invalidate() - } - } - - open fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { - lastX = touchX - lastY = touchY - touchX = x - touchY = y - - if (invalidate) { - pageView.invalidate() - } - - onScroll() - } - open fun fling( startX: Int, startY: Int, velocityX: Int, velocityY: Int, minX: Int, maxX: Int, minY: Int, maxY: Int @@ -113,14 +68,13 @@ abstract class PageDelegate(protected val pageView: PageView) : pageView.invalidate() } - protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int) { - scroller.startScroll( - startX, - startY, - dx, - dy, - if (dx != 0) (abs(dx) * 0.3).toInt() else (abs(dy) * 0.3).toInt() - ) + protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, animationSpeed: Int) { + val duration = if (dx != 0) { + (animationSpeed * abs(dx)) / viewWidth + } else { + (animationSpeed * abs(dy)) / viewHeight + } + scroller.startScroll(startX, startY, dx, dy, duration) isRunning = true isStarted = true pageView.invalidate() @@ -138,49 +92,36 @@ abstract class PageDelegate(protected val pageView: PageView) : open fun setViewSize(width: Int, height: Int) { viewWidth = width viewHeight = height - pageView.invalidate() - centerRectF.set( - width * 0.33f, height * 0.33f, - width * 0.66f, height * 0.66f - ) } fun scroll() { if (scroller.computeScrollOffset()) { - setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) + pageView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) } else if (isStarted) { onAnimStop() stopScroll() } } - fun abort() { - if (!scroller.isFinished) { - scroller.abortAnimation() - } - } + open fun onScroll() = Unit - open fun onAnimStart() {}//scroller start + abstract fun abortAnim() - open fun onDraw(canvas: Canvas) {}//绘制 + abstract fun onAnimStart(animationSpeed: Int) //scroller start - open fun onAnimStop() {}//scroller finish + abstract fun onDraw(canvas: Canvas) //绘制 - open fun onScroll() {}//移动contentView, slidePage + abstract fun onAnimStop() //scroller finish - open fun nextPageByAnim() { - abort() - } + abstract fun nextPageByAnim(animationSpeed: Int) - open fun prevPageByAnim() { - abort() - } + abstract fun prevPageByAnim(animationSpeed: Int) open fun keyTurnPage(direction: Direction) { if (isRunning) return when (direction) { - Direction.NEXT -> nextPageByAnim() - Direction.PREV -> prevPageByAnim() + Direction.NEXT -> nextPageByAnim(100) + Direction.PREV -> prevPageByAnim(100) else -> return } } @@ -193,37 +134,12 @@ abstract class PageDelegate(protected val pageView: PageView) : /** * 触摸事件处理 */ - @CallSuper - open fun onTouch(event: MotionEvent) { - if (isStarted) return - if (!detector.onTouchEvent(event)) { - //GestureDetector.onFling小幅移动不会触发,所以要自己判断 - when (event.action) { - MotionEvent.ACTION_UP, - MotionEvent.ACTION_CANCEL -> { - if (isTextSelected) { - pageView.callBack.showTextActionMenu() - } - if (isMoved) { - if (selectedOnDown) { - selectedOnDown = false - } - if (!noNext) onAnimStart() - } - } - } - } - } + abstract fun onTouch(event: MotionEvent) /** * 按下 */ - override fun onDown(e: MotionEvent): Boolean { - if (isTextSelected) { - curPage.cancelSelect() - isTextSelected = false - selectedOnDown = true - } + fun onDown() { //是否移动 isMoved = false //是否存在下一章 @@ -234,81 +150,6 @@ abstract class PageDelegate(protected val pageView: PageView) : isCancel = false //是下一章还是前一章 setDirection(Direction.NONE) - //设置起始位置的触摸点 - setStartPoint(e.x, e.y) - return true - } - - /** - * 单击 - */ - override fun onSingleTapUp(e: MotionEvent): Boolean { - if (selectedOnDown) { - selectedOnDown = false - return true - } - if (isMoved) { - if (!noNext) onAnimStart() - return true - } - val x = e.x - val y = e.y - if (centerRectF.contains(x, y)) { - pageView.callBack.clickCenter() - setTouchPoint(x, y) - } else if (ReadBookConfig.clickTurnPage) { - if (x > viewWidth / 2 || - AppConfig.clickAllNext - ) { - nextPageByAnim() - } else { - prevPageByAnim() - } - } - return true - } - - /** - * 长按选择 - */ - override fun onLongPress(e: MotionEvent) { - curPage.selectText(e) { relativePage, lineIndex, charIndex -> - isTextSelected = true - firstRelativePage = relativePage - firstLineIndex = lineIndex - firstCharIndex = charIndex - } - } - - protected fun selectText(event: MotionEvent) { - curPage.selectText(event) { relativePage, lineIndex, charIndex -> - when { - relativePage > firstRelativePage -> { - curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) - } - relativePage < firstRelativePage -> { - curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) - } - lineIndex > firstLineIndex -> { - curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) - } - lineIndex < firstLineIndex -> { - curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) - } - charIndex > firstCharIndex -> { - curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex) - } - else -> { - curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex) - curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex) - } - } - } } /** @@ -318,7 +159,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() } } @@ -332,7 +173,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..30078de89 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 @@ -1,9 +1,10 @@ package io.legado.app.ui.book.read.page.delegate +import android.graphics.Canvas import android.view.MotionEvent import android.view.VelocityTracker -import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.PageView +import io.legado.app.ui.book.read.page.provider.ChapterProvider class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { @@ -12,7 +13,7 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { //速度追踪器 private val mVelocity: VelocityTracker = VelocityTracker.obtain() - override fun onAnimStart() { + override fun onAnimStart(animationSpeed: Int) { //惯性滚动 fling( 0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(), @@ -20,26 +21,31 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { ) } - override fun onScroll() { - curPage.onScroll(touchY - lastY) + override fun onAnimStop() { + // nothing } override fun onTouch(event: MotionEvent) { when (event.action) { MotionEvent.ACTION_DOWN -> { - setStartPoint(event.x, event.y) - abort() + abortAnim() mVelocity.clear() } MotionEvent.ACTION_MOVE -> { - if (isTextSelected) { - selectText(event) - } else { - onScroll(event) - } + onScroll(event) + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + onAnimStart(pageView.defaultAnimationSpeed) } } - super.onTouch(event) + } + + override fun onScroll() { + curPage.onScroll(touchY - lastY) + } + + override fun onDraw(canvas: Canvas) { + // nothing } private fun onScroll(event: MotionEvent) { @@ -61,12 +67,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { val div = if (pointerUp) count - 1 else count val focusX = sumX / div val focusY = sumY / div - setTouchPoint(sumX, sumY) + pageView.setTouchPoint(sumX, sumY) if (!isMoved) { val deltaX = (focusX - startX).toInt() val deltaY = (focusY - startY).toInt() val distance = deltaX * deltaX + deltaY * deltaY - isMoved = distance > slopSquare + isMoved = distance > pageView.slopSquare } if (isMoved) { isRunning = true @@ -78,13 +84,31 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { mVelocity.recycle() } - override fun nextPageByAnim() { - super.nextPageByAnim() - startScroll(0, 0, 0, -ChapterProvider.visibleHeight) + override fun abortAnim() { + isStarted = false + isMoved = false + isRunning = false + if (!scroller.isFinished) { + pageView.isAbortAnim = true + scroller.abortAnimation() + } else { + pageView.isAbortAnim = false + } } - override fun prevPageByAnim() { - super.prevPageByAnim() - startScroll(0, 0, 0, ChapterProvider.visibleHeight) + override fun nextPageByAnim(animationSpeed: Int) { + if (pageView.isAbortAnim) { + return + } + pageView.setStartPoint(0f, 0f, false) + startScroll(0, 0, 0, -ChapterProvider.visibleHeight, animationSpeed) + } + + override fun prevPageByAnim(animationSpeed: Int) { + if (pageView.isAbortAnim) { + return + } + pageView.setStartPoint(0f, 0f, false) + startScroll(0, 0, 0, ChapterProvider.visibleHeight, animationSpeed) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index 616a41614..5d0e97ec6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -3,6 +3,7 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.* import android.graphics.drawable.GradientDrawable import android.os.Build +import android.view.MotionEvent import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.PageView import kotlin.math.* @@ -114,26 +115,26 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() } - override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { - super.setStartPoint(x, y, false) - calcCornerXY(x, y) - } - - override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { - super.setTouchPoint(x, y, false) - //触摸y中间位置吧y变成屏幕高度 - if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) - || mDirection == Direction.PREV - ) { - touchY = viewHeight.toFloat() - } + override fun onTouch(event: MotionEvent) { + super.onTouch(event) + when (event.action) { + MotionEvent.ACTION_DOWN -> { + calcCornerXY(event.x, event.y) + } + MotionEvent.ACTION_MOVE -> { + if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) + || mDirection == Direction.PREV + ) { + pageView.touchY = viewHeight.toFloat() + } - if (startY > viewHeight / 3 && startY < viewHeight / 2 - && mDirection == Direction.NEXT - ) { - touchY = 1f + if (startY > viewHeight / 3 && startY < viewHeight / 2 + && mDirection == Direction.NEXT + ) { + pageView.touchY = 1f + } + } } - pageView.invalidate() } override fun setDirection(direction: Direction) { @@ -154,7 +155,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi } } - override fun onAnimStart() { + override fun onAnimStart(animationSpeed: Int) { var dx: Float val dy: Float // dy 垂直方向滑动的距离,负值会使滚动向上滚动 @@ -184,7 +185,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi (1 - touchY) // 防止mTouchY最终变为0 } } - startScroll(touchX.toInt(), touchY.toInt(), dx.toInt(), dy.toInt()) + startScroll(touchX.toInt(), touchY.toInt(), dx.toInt(), dy.toInt(), animationSpeed) } override fun onAnimStop() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt index 197b65c39..9c3413f6a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt @@ -8,7 +8,7 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { private val bitmapMatrix = Matrix() - override fun onAnimStart() { + override fun onAnimStart(animationSpeed: Int) { val distanceX: Float when (mDirection) { Direction.NEXT -> distanceX = @@ -28,7 +28,7 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { viewWidth - (touchX - startX) } } - startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) + startScroll(touchX.toInt(), 0, distanceX.toInt(), 0, animationSpeed) } override fun onDraw(canvas: Canvas) { 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/read/page/entities/TextChar.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt index 72798a9ea..6a0e9b0f4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt @@ -4,5 +4,6 @@ data class TextChar( val charData: String, var start: Float, var end: Float, - var selected: Boolean = false + var selected: Boolean = false, + var isImage: Boolean = false ) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index c8a9c9ca0..9457bff37 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -1,8 +1,8 @@ package io.legado.app.ui.book.read.page.entities import android.text.TextPaint -import io.legado.app.ui.book.read.page.ChapterProvider -import io.legado.app.ui.book.read.page.ChapterProvider.textHeight +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.book.read.page.provider.ChapterProvider.textHeight data class TextLine( var text: String = "", @@ -11,6 +11,7 @@ data class TextLine( var lineBase: Float = 0f, var lineBottom: Float = 0f, val isTitle: Boolean = false, + val isImage: Boolean = false, var isReadAloud: Boolean = false ) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 9613947c7..beb9e3673 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -4,7 +4,8 @@ import android.text.Layout import android.text.StaticLayout import io.legado.app.App import io.legado.app.R -import io.legado.app.ui.book.read.page.ChapterProvider +import io.legado.app.help.ReadBookConfig +import io.legado.app.ui.book.read.page.provider.ChapterProvider import java.text.DecimalFormat data class TextPage( @@ -19,8 +20,10 @@ data class TextPage( ) { fun upLinesPosition() = ChapterProvider.apply { + if (!ReadBookConfig.textBottomJustify) return@apply if (textLines.size <= 1) return@apply - if (visibleHeight - height > with(textLines.last()) { lineBottom - lineTop }) return@apply + if (textLines.last().isImage) return@apply + if (visibleHeight - height >= with(textLines.last()) { lineBottom - lineTop }) return@apply val surplus = (visibleBottom - textLines.last().lineBottom) if (surplus == 0f) return@apply height += surplus @@ -44,12 +47,11 @@ data class TextPage( if (y < 0) y = 0f for (lineIndex in 0 until layout.lineCount) { val textLine = TextLine() - textLine.lineTop = (ChapterProvider.paddingTop + y - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))) - textLine.lineBase = (ChapterProvider.paddingTop + y - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))) + textLine.lineTop = ChapterProvider.paddingTop + y + layout.getLineTop(lineIndex) + textLine.lineBase = + ChapterProvider.paddingTop + y + layout.getLineBaseline(lineIndex) textLine.lineBottom = - textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent + ChapterProvider.paddingTop + y + layout.getLineBottom(lineIndex) var x = ChapterProvider.paddingLeft + (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 textLine.text = diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt new file mode 100644 index 000000000..a8cf1482d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -0,0 +1,473 @@ +package io.legado.app.ui.book.read.page.provider + +import android.graphics.Typeface +import android.net.Uri +import android.os.Build +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint +import io.legado.app.App +import io.legado.app.constant.AppPattern +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.ReadBookConfig +import io.legado.app.ui.book.read.page.entities.TextChapter +import io.legado.app.ui.book.read.page.entities.TextChar +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.utils.* +import java.util.* + + +@Suppress("DEPRECATION") +object ChapterProvider { + private var viewWidth = 0 + private var viewHeight = 0 + var paddingLeft = 0 + var paddingTop = 0 + var visibleWidth = 0 + var visibleHeight = 0 + var visibleRight = 0 + var visibleBottom = 0 + private var lineSpacingExtra = 0 + private var paragraphSpacing = 0 + private var titleTopSpacing = 0 + private var titleBottomSpacing = 0 + var typeface: Typeface = Typeface.SANS_SERIF + lateinit var titlePaint: TextPaint + lateinit var contentPaint: TextPaint + + init { + upStyle() + } + + /** + * 获取拆分完的章节数据 + */ + fun getTextChapter( + book: Book, + bookChapter: BookChapter, + contents: List, + chapterSize: Int, + imageStyle: String?, + ): TextChapter { + val textPages = arrayListOf() + val pageLines = arrayListOf() + val pageLengths = arrayListOf() + val stringBuilder = StringBuilder() + var durY = 0f + textPages.add(TextPage()) + contents.forEachIndexed { index, text -> + val matcher = AppPattern.imgPattern.matcher(text) + if (matcher.find()) { + var src = matcher.group(1) + if (!book.isEpub()) { + src = NetworkUtils.getAbsoluteURL(bookChapter.url, src) + } + src?.let { + durY = + setTypeImage( + book, bookChapter, src, durY, textPages, imageStyle + ) + } + } else { + val isTitle = index == 0 + if (!(isTitle && ReadBookConfig.titleMode == 2)) { + durY = + setTypeText( + text, durY, textPages, pageLines, + pageLengths, stringBuilder, isTitle + ) + } + } + } + textPages.last().height = durY + 20.dp + textPages.last().text = stringBuilder.toString() + if (pageLines.size < textPages.size) { + pageLines.add(textPages.last().textLines.size) + } + if (pageLengths.size < textPages.size) { + pageLengths.add(textPages.last().text.length) + } + textPages.forEachIndexed { index, item -> + item.index = index + item.pageSize = textPages.size + item.chapterIndex = bookChapter.index + item.chapterSize = chapterSize + item.title = bookChapter.title + item.upLinesPosition() + } + + return TextChapter( + bookChapter.index, + bookChapter.title, + bookChapter.url, + textPages, + pageLines, + pageLengths, + chapterSize + ) + } + + private fun setTypeImage( + book: Book, + chapter: BookChapter, + src: String, + y: Float, + textPages: ArrayList, + imageStyle: String?, + ): Float { + var durY = y + ImageProvider.getImage(book, chapter.index, src)?.let { + if (durY > visibleHeight) { + textPages.last().height = durY + textPages.add(TextPage()) + durY = 0f + } + var height = it.height + var width = it.width + when (imageStyle?.toUpperCase(Locale.ROOT)) { + "FULL" -> { + width = visibleWidth + height = it.height * visibleWidth / it.width + } + else -> { + if (it.width > visibleWidth) { + height = it.height * visibleWidth / it.width + width = visibleWidth + } + if (height > visibleHeight) { + width = width * visibleHeight / height + height = visibleHeight + } + if (durY + height > visibleHeight) { + textPages.last().height = durY + textPages.add(TextPage()) + durY = 0f + } + } + } + val textLine = TextLine(isImage = true) + textLine.lineTop = durY + durY += height + textLine.lineBottom = durY + val (start, end) = if (visibleWidth > width) { + val adjustWidth = (visibleWidth - width) / 2f + Pair( + paddingLeft.toFloat() + adjustWidth, + paddingLeft.toFloat() + adjustWidth + width + ) + } else { + Pair(paddingLeft.toFloat(), (paddingLeft + width).toFloat()) + } + textLine.textChars.add( + TextChar( + charData = src, + start = start, + end = end, + isImage = true + ) + ) + textPages.last().textLines.add(textLine) + } + return durY + paragraphSpacing / 10f + } + + /** + * 排版文字 + */ + private fun setTypeText( + text: String, + y: Float, + textPages: ArrayList, + pageLines: ArrayList, + pageLengths: ArrayList, + stringBuilder: StringBuilder, + isTitle: Boolean, + ): Float { + var durY = if (isTitle) y + titleTopSpacing else y + val textPaint = if (isTitle) titlePaint else contentPaint + val layout = StaticLayout( + text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true + ) + for (lineIndex in 0 until layout.lineCount) { + val textLine = TextLine(isTitle = isTitle) + val words = + text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) + val desiredWidth = layout.getLineWidth(lineIndex) + var isLastLine = false + if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) { + //第一行 + textLine.text = words + addCharsToLineFirst( + textLine, + words.toStringArray(), + textPaint, + desiredWidth + ) + } else if (lineIndex == layout.lineCount - 1) { + //最后一行 + textLine.text = "$words\n" + isLastLine = true + val x = if (isTitle && ReadBookConfig.titleMode == 1) + (visibleWidth - layout.getLineWidth(lineIndex)) / 2 + else 0f + addCharsToLineLast( + textLine, + words.toStringArray(), + textPaint, + x + ) + } else { + //中间行 + textLine.text = words + addCharsToLineMiddle( + textLine, + words.toStringArray(), + textPaint, + desiredWidth, + 0f + ) + } + if (durY + textPaint.textHeight > visibleHeight) { + //当前页面结束,设置各种值 + textPages.last().text = stringBuilder.toString() + pageLines.add(textPages.last().textLines.size) + pageLengths.add(textPages.last().text.length) + textPages.last().height = durY + //新建页面 + textPages.add(TextPage()) + stringBuilder.clear() + durY = 0f + } + stringBuilder.append(words) + if (isLastLine) stringBuilder.append("\n") + textPages.last().textLines.add(textLine) + textLine.upTopBottom(durY, textPaint) + durY += textPaint.textHeight * lineSpacingExtra / 10f + textPages.last().height = durY + } + if (isTitle) durY += titleBottomSpacing + durY += textPaint.textHeight * paragraphSpacing / 10f + return durY + } + + /** + * 有缩进,两端对齐 + */ + private fun addCharsToLineFirst( + textLine: TextLine, + words: Array, + textPaint: TextPaint, + 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 { + val x1 = x + icw + textLine.addTextChar( + charData = it, + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + val words1 = words.copyOfRange(bodyIndent.length, words.size) + addCharsToLineMiddle( + textLine, + words1, + textPaint, + desiredWidth, + x + ) + } + + /** + * 无缩进,两端对齐 + */ + private fun addCharsToLineMiddle( + textLine: TextLine, + words: Array, + textPaint: TextPaint, + desiredWidth: Float, + startX: Float, + ) { + if (!ReadBookConfig.textFullJustify) { + addCharsToLineLast( + textLine, + words, + textPaint, + startX + ) + return + } + val gapCount: Int = words.lastIndex + val d = (visibleWidth - desiredWidth) / gapCount + var x = startX + words.forEachIndexed { index, s -> + val cw = StaticLayout.getDesiredWidth(s, textPaint) + val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw) + textLine.addTextChar( + charData = s, + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + exceed( + textLine, + words + ) + } + + /** + * 最后一行,自然排列 + */ + private fun addCharsToLineLast( + textLine: TextLine, + words: Array, + textPaint: TextPaint, + startX: Float, + ) { + var x = startX + words.forEach { + val cw = StaticLayout.getDesiredWidth(it, textPaint) + val x1 = x + cw + textLine.addTextChar( + charData = it, + start = paddingLeft + x, + end = paddingLeft + x1 + ) + x = x1 + } + exceed( + textLine, + words + ) + } + + /** + * 超出边界处理 + */ + private fun exceed(textLine: TextLine, words: Array) { + val endX = textLine.textChars.last().end + if (endX > visibleRight) { + val cc = (endX - visibleRight) / words.size + for (i in 0..words.lastIndex) { + textLine.getTextCharReverseAt(i).let { + val py = cc * (words.size - i) + it.start = it.start - py + it.end = it.end - py + } + } + } + } + + /** + * 更新样式 + */ + fun upStyle() { + typeface = try { + val fontPath = ReadBookConfig.textFont + when { + fontPath.isContentPath() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> { + val fd = App.INSTANCE.contentResolver + .openFileDescriptor(Uri.parse(fontPath), "r")!! + .fileDescriptor + Typeface.Builder(fd).build() + } + fontPath.isContentPath() -> { + Typeface.createFromFile(RealPathUtil.getPath(App.INSTANCE, Uri.parse(fontPath))) + } + fontPath.isNotEmpty() -> Typeface.createFromFile(fontPath) + else -> when (AppConfig.systemTypefaces) { + 1 -> Typeface.SERIF + 2 -> Typeface.MONOSPACE + else -> Typeface.SANS_SERIF + } + } + } catch (e: Exception) { + ReadBookConfig.textFont = "" + ReadBookConfig.save() + 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.textColor + titlePaint.letterSpacing = ReadBookConfig.letterSpacing + titlePaint.typeface = titleFont + titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat() + titlePaint.isAntiAlias = true + //正文 + contentPaint = TextPaint() + contentPaint.color = ReadBookConfig.textColor + contentPaint.letterSpacing = ReadBookConfig.letterSpacing + contentPaint.typeface = textFont + contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() + contentPaint.isAntiAlias = true + //间距 + lineSpacingExtra = ReadBookConfig.lineSpacingExtra + paragraphSpacing = ReadBookConfig.paragraphSpacing + titleTopSpacing = ReadBookConfig.titleTopSpacing.dp + titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp + upVisibleSize() + } + + /** + * 更新View尺寸 + */ + fun upViewSize(width: Int, height: Int) { + if (width > 0 && height > 0) { + viewWidth = width + viewHeight = height + upVisibleSize() + } + } + + /** + * 更新绘制尺寸 + */ + private fun upVisibleSize() { + if (viewWidth > 0 && viewHeight > 0) { + paddingLeft = ReadBookConfig.paddingLeft.dp + paddingTop = ReadBookConfig.paddingTop.dp + visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp + visibleHeight = viewHeight - paddingTop - ReadBookConfig.paddingBottom.dp + visibleRight = paddingLeft + visibleWidth + visibleBottom = paddingTop + visibleHeight + } + } + + val TextPaint.textHeight: Float + get() = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt new file mode 100644 index 000000000..c269a7e55 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt @@ -0,0 +1,84 @@ +package io.legado.app.ui.book.read.page.provider + +import android.graphics.Bitmap +import io.legado.app.data.entities.Book +import io.legado.app.help.BookHelp +import io.legado.app.model.localBook.EPUBFile +import io.legado.app.utils.BitmapUtils +import io.legado.app.utils.FileUtils +import kotlinx.coroutines.runBlocking +import java.io.FileOutputStream +import java.util.concurrent.ConcurrentHashMap + +object ImageProvider { + + private val cache = ConcurrentHashMap>() + + @Synchronized + fun getCache(chapterIndex: Int, src: String): Bitmap? { + return cache[chapterIndex]?.get(src) + } + + @Synchronized + fun setCache(chapterIndex: Int, src: String, bitmap: Bitmap) { + var indexCache = cache[chapterIndex] + if (indexCache == null) { + indexCache = ConcurrentHashMap() + cache[chapterIndex] = indexCache + } + indexCache[src] = bitmap + } + + fun getImage(book: Book, chapterIndex: Int, src: String, onUi: Boolean = false): Bitmap? { + getCache(chapterIndex, src)?.let { + return it + } + val vFile = BookHelp.getImage(book, src) + if (!vFile.exists()) { + if (book.isEpub()) { + EPUBFile.getImage(book, src)?.use { input -> + val newFile = FileUtils.createFileIfNotExist(vFile.absolutePath) + FileOutputStream(newFile).use { output -> + input.copyTo(output) + } + } + } else if (!onUi) { + runBlocking { + BookHelp.saveImage(book, src) + } + } + } + return try { + val bitmap = BitmapUtils.decodeBitmap( + vFile.absolutePath, + ChapterProvider.visibleWidth, + ChapterProvider.visibleHeight + ) + setCache(chapterIndex, src, bitmap) + bitmap + } catch (e: Exception) { + null + } + } + + fun clearAllCache() { + cache.forEach { indexCache -> + indexCache.value.forEach { + it.value.recycle() + } + } + cache.clear() + } + + fun clearOut(chapterIndex: Int) { + cache.forEach { indexCache -> + if (indexCache.key !in chapterIndex - 1..chapterIndex + 1) { + indexCache.value.forEach { + it.value.recycle() + } + cache.remove(indexCache.key) + } + } + } + +} 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..2870818c2 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 @@ -7,20 +7,18 @@ import android.view.View.GONE 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 import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.AppPattern import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchKeyword -import io.legado.app.lib.theme.ATH -import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.lib.theme.* import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.widget.recycler.LoadMoreView @@ -52,9 +50,9 @@ 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?) { + ll_history.setBackgroundColor(backgroundColor) initRecyclerView() initSearchView() initOtherView() @@ -165,30 +163,34 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se } private fun initOtherView() { - tv_clear_history.onClick { viewModel.clearHistory() } + fb_stop.backgroundTintList = + Selector.colorBuild() + .setDefaultColor(accentColor) + .setPressedColor(ColorUtils.darkenColor(accentColor)) + .create() fb_stop.onClick { viewModel.stop() refresh_progress_bar.isAutoLoading = false } + tv_clear_history.onClick { viewModel.clearHistory() } } private fun initLiveData() { - App.db.bookSourceDao().liveGroupEnabled().observe(this, Observer { + App.db.bookSourceDao().liveGroupEnabled().observe(this, { groups.clear() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } upGroupMenu() }) - viewModel.searchBookLiveData.observe(this, Observer { - upSearchItems(it, false) + viewModel.searchBookLiveData.observe(this, { + upSearchItems(it) }) - viewModel.isSearchLiveData.observe(this, Observer { + viewModel.isSearchLiveData.observe(this, { if (it) { startSearch() } else { searchFinally() - upSearchItems(viewModel.searchBooks, true) } }) } @@ -252,7 +254,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se rv_bookshelf_search.gone() } else { bookData = App.db.bookDao().liveDataSearch(key) - bookData?.observe(this, Observer { + bookData?.observe(this, { if (it.isEmpty()) { tv_book_show.gone() rv_bookshelf_search.gone() @@ -270,7 +272,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se } else { App.db.searchKeywordDao().liveDataSearch(key) } - historyData?.observe(this, Observer { + historyData?.observe(this, { historyKeyAdapter.setItems(it) if (it.isEmpty()) { tv_clear_history.invisible() @@ -284,14 +286,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 +313,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 +326,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..179b2cd9b 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,43 +1,63 @@ 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 import io.legado.app.constant.PreferKey import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchKeyword -import io.legado.app.model.SearchBookModel +import io.legado.app.model.webBook.SearchBookModel 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() } /** * 开始搜索 */ fun search(key: String) { - if (searchKey != key && key.isNotEmpty()) { + if ((searchKey == key) || key.isNotEmpty()) { searchBookModel.cancelSearch() searchBooks.clear() searchBookLiveData.postValue(searchBooks) searchID = System.currentTimeMillis() searchKey = key } + if (searchKey.isEmpty()) { + return + } 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) + isLoading = true } override fun onSearchSuccess(searchBooks: ArrayList) { @@ -94,7 +114,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) && item.author == searchBook.author ) { hasSame = true - searchBook.addOrigin(item.bookUrl) + searchBook.addOrigin(item.origin) break } } @@ -151,7 +171,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/searchContent/SearchListActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt new file mode 100644 index 000000000..ec1cbc0c7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt @@ -0,0 +1,93 @@ +package io.legado.app.ui.book.searchContent + +import android.os.Bundle +import android.view.Menu +import androidx.appcompat.widget.SearchView +import androidx.core.view.isGone +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.utils.getViewModel +import io.legado.app.utils.gone +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.activity_chapter_list.* +import kotlinx.android.synthetic.main.view_tab_layout.* + + +class SearchListActivity : VMBaseActivity(R.layout.activity_search_list) { + // todo: 完善搜索界面UI + override val viewModel: SearchListViewModel + get() = getViewModel(SearchListViewModel::class.java) + + private var searchView: SearchView? = null + + override fun onActivityCreated(savedInstanceState: Bundle?) { + tab_layout.isTabIndicatorFullWidth = false + tab_layout.setSelectedTabIndicatorColor(accentColor) + intent.getStringExtra("bookUrl")?.let { + viewModel.initBook(it) { + view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) + tab_layout.setupWithViewPager(view_pager) + } + } + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.search_view, menu) + val search = menu.findItem(R.id.menu_search) + searchView = search.actionView as SearchView + ATH.setTint(searchView!!, primaryTextColor) + searchView?.maxWidth = resources.displayMetrics.widthPixels + searchView?.onActionViewCollapsed() + searchView?.setOnCloseListener { + tab_layout.visible() + //to do clean + false + } + searchView?.setOnSearchClickListener { tab_layout.gone() } + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + if (viewModel.lastQuery != query){ + viewModel.startContentSearch(query) + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + + return false + } + }) + return super.onCompatCreateOptionsMenu(menu) + } + + private inner class TabFragmentPageAdapter(fm: FragmentManager) : + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + override fun getItem(position: Int): Fragment { + return SearchListFragment() + } + + override fun getCount(): Int { + return 1 + } + + override fun getPageTitle(position: Int): CharSequence? { + return "Search" + } + + } + + override fun onBackPressed() { + if (tab_layout.isGone) { + searchView?.onActionViewCollapsed() + tab_layout.visible() + } else { + super.onBackPressed() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt new file mode 100644 index 000000000..d2c98b955 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt @@ -0,0 +1,51 @@ +package io.legado.app.ui.book.searchContent + +import android.content.Context +import android.os.Build +import android.text.Html +import android.util.Log +import android.view.View +import androidx.annotation.RequiresApi +import androidx.core.text.HtmlCompat +import io.legado.app.R +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.data.entities.BookChapter +import io.legado.app.help.BookHelp +import io.legado.app.lib.theme.accentColor +import io.legado.app.utils.getCompatColor +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.item_bookmark.view.* +import kotlinx.android.synthetic.main.item_search_list.view.* +import org.jetbrains.anko.sdk27.listeners.onClick + +class SearchListAdapter(context: Context, val callback: Callback) : + SimpleRecyclerAdapter(context, R.layout.item_search_list) { + + val cacheFileNames = hashSetOf() + + override fun convert(holder: ItemViewHolder, item: SearchResult, payloads: MutableList) { + with(holder.itemView) { + val isDur = callback.durChapterIndex() == item.chapterIndex + if (payloads.isEmpty()) { + tv_search_result.text = item.parseText(item.presentText) + if (isDur){ + tv_search_result.paint.isFakeBoldText = true + } + } + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.onClick { + getItem(holder.layoutPosition)?.let { + callback.openSearchResult(it) + } + } + } + + interface Callback { + fun openSearchResult(searchResult: SearchResult) + fun durChapterIndex(): Int + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt new file mode 100644 index 000000000..38ac013df --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt @@ -0,0 +1,239 @@ +package io.legado.app.ui.book.searchContent + +import android.annotation.SuppressLint +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.lifecycle.LiveData +import com.hankcs.hanlp.HanLP +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.VMBaseFragment +import io.legado.app.constant.EventBus +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig +import io.legado.app.help.BookHelp +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.service.help.ReadBook +import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.widget.recycler.UpLinearLayoutManager +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.observeEvent +import kotlinx.android.synthetic.main.fragment_search_list.* +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import org.jetbrains.anko.sdk27.listeners.onClick +import java.util.regex.Pattern + +class SearchListFragment : VMBaseFragment(R.layout.fragment_search_list), + SearchListAdapter.Callback, + SearchListViewModel.SearchListCallBack{ + override val viewModel: SearchListViewModel + get() = getViewModelOfActivity(SearchListViewModel::class.java) + + lateinit var adapter: SearchListAdapter + private lateinit var mLayoutManager: UpLinearLayoutManager + private var searchResultCounts = 0 + private var durChapterIndex = 0 + private var searchResultList: MutableList = mutableListOf() + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + viewModel.searchCallBack = this + val bbg = bottomBackground + val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) + ll_search_base_info.setBackgroundColor(bbg) + tv_current_search_info.setTextColor(btc) + iv_search_content_top.setColorFilter(btc) + iv_search_content_bottom.setColorFilter(btc) + initRecyclerView() + initView() + initBook() + } + + private fun initRecyclerView() { + adapter = SearchListAdapter(requireContext(), this) + mLayoutManager = UpLinearLayoutManager(requireContext()) + recycler_view.layoutManager = mLayoutManager + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + recycler_view.adapter = adapter + } + + private fun initView() { + iv_search_content_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } + iv_search_content_bottom.onClick { + if (adapter.itemCount > 0) { + mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) + } + } + } + + @SuppressLint("SetTextI18n") + private fun initBook() { + launch { + + tv_current_search_info.text = "搜索结果:$searchResultCounts" + viewModel.book?.let { + initCacheFileNames(it) + durChapterIndex = it.durChapterIndex + } + } + } + + private fun initCacheFileNames(book: Book) { + launch(IO) { + adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) + withContext(Main) { + adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true) + } + } + } + + override fun observeLiveBus() { + observeEvent(EventBus.SAVE_CONTENT) { chapter -> + viewModel.book?.bookUrl?.let { bookUrl -> + if (chapter.bookUrl == bookUrl) { + adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) + adapter.notifyItemChanged(chapter.index, true) + } + } + } + } + + @SuppressLint("SetTextI18n") + override fun startContentSearch(newText: String) { + // 按章节搜索内容 + if (!newText.isBlank()) { + adapter.clearItems() + searchResultList.clear() + searchResultCounts = 0 + viewModel.lastQuery = newText + var searchResults = listOf() + launch(Main){ + App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map{ chapter -> + val job = async(IO){ + if (isLocalBook || adapter.cacheFileNames.contains(BookHelp.formatChapterName(chapter))) { + searchResults = searchChapter(newText, chapter) + } + } + job.await() + if (searchResults.isNotEmpty()){ + searchResultList.addAll(searchResults) + tv_current_search_info.text = "搜索结果:$searchResultCounts" + adapter.addItems(searchResults) + searchResults = listOf() + } + } + } + } + } + + private suspend fun searchChapter(query: String, chapter: BookChapter?): List { + val searchResults: MutableList = mutableListOf() + var positions : List = listOf() + var replaceContents: List? = null + var totalContents = "" + if (chapter != null){ + viewModel.book?.let { bookSource -> + val bookContent = BookHelp.getContent(bookSource, chapter) + if (bookContent != null){ + //搜索替换后的正文 + val job = async(IO) { + chapter.title = when (AppConfig.chineseConverterType) { + 1 -> HanLP.convertToSimplifiedChinese(chapter.title) + 2 -> HanLP.convertToTraditionalChinese(chapter.title) + else -> chapter.title + } + replaceContents = BookHelp.disposeContent( + chapter.title, + bookSource.name, + bookSource.bookUrl, + bookContent, + bookSource.useReplaceRule + ) + } + job.await() + while (replaceContents == null){ + delay(100L) + } + totalContents = replaceContents!!.joinToString("") + positions = searchPosition(totalContents, query) + var count = 1 + positions.map{ + val construct = constructText(totalContents, it, query) + val result = SearchResult(index = searchResultCounts, + indexWithinChapter = count, + text = construct[1] as String, + chapterTitle = chapter.title, + query = query, + chapterIndex = chapter.index, + newPosition = construct[0] as Int, + contentPosition = it + ) + count += 1 + searchResultCounts += 1 + searchResults.add(result) + } + } + } + } + return searchResults + } + + private fun searchPosition(content: String, pattern: String): List { + val position : MutableList = mutableListOf() + var index = content.indexOf(pattern) + while(index >= 0){ + position.add(index) + index = content.indexOf(pattern, index + 1); + } + return position + } + + private fun constructText(content: String, position: Int, query: String): Array{ + // 构建关键词周边文字,在搜索结果里显示 + // todo: 判断段落,只在关键词所在段落内分割 + // todo: 利用标点符号分割完整的句 + // todo: length和设置结合,自由调整周边文字长度 + val length = 20 + var po1 = position - length + var po2 = position + query.length + length + if (po1 <0) { + po1 = 0 + } + if (po2 > content.length){ + po2 = content.length + } + val newPosition = position - po1 + val newText = content.substring(po1, po2) + return arrayOf(newPosition, newText) + } + + val isLocalBook: Boolean + get() = viewModel.book?.isLocalBook() == true + + override fun openSearchResult(searchResult: SearchResult) { + + val searchData = Intent() + searchData.putExtra("index", searchResult.chapterIndex) + searchData.putExtra("contentPosition", searchResult.contentPosition) + searchData.putExtra("query", searchResult.query) + searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter) + activity?.setResult(RESULT_OK, searchData) + activity?.finish() + + + } + + override fun durChapterIndex(): Int { + return durChapterIndex + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt new file mode 100644 index 000000000..060d74067 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt @@ -0,0 +1,33 @@ +package io.legado.app.ui.book.searchContent + + +import android.app.Application +import io.legado.app.App +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.Book + +class SearchListViewModel(application: Application) : BaseViewModel(application) { + var bookUrl: String = "" + var book: Book? = null + var searchCallBack: SearchListCallBack? = null + var lastQuery: String = "" + + fun initBook(bookUrl: String, success: () -> Unit) { + this.bookUrl = bookUrl + execute { + book = App.db.bookDao().getBook(bookUrl) + }.onSuccess { + success.invoke() + } + } + + fun startContentSearch(newText: String) { + searchCallBack?.startContentSearch(newText) + } + + + interface SearchListCallBack { + fun startContentSearch(newText: String) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt new file mode 100644 index 000000000..3a629839b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt @@ -0,0 +1,43 @@ +package io.legado.app.ui.book.searchContent + +import android.text.Spanned +import androidx.core.text.HtmlCompat +import io.legado.app.App +import io.legado.app.R +import io.legado.app.utils.getCompatColor +import io.legado.app.utils.hexString + +data class SearchResult( + var index: Int = 0, + var indexWithinChapter: Int = 0, + var text: String = "", + var chapterTitle: String = "", + val query: String, + var pageSize: Int = 0, + var chapterIndex: Int = 0, + var pageIndex: Int = 0, + var newPosition: Int = 0, + var contentPosition: Int =0 +) { + val presentText: String + get(){ + return colorPresentText(newPosition, query, text) + + "($chapterTitle)" + } + + fun colorPresentText(position: Int, center: String, targetText: String): String { + val sub1 = text.substring(0, position) + val sub2 = text.substring(position + center.length, targetText.length) + val textColor = App.INSTANCE.getCompatColor(R.color.primaryText).hexString + return "$sub1" + + "$center" + + "$sub2" + } + + fun parseText(targetText: String): Spanned { + return HtmlCompat.fromHtml(targetText, HtmlCompat.FROM_HTML_MODE_LEGACY) + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt index b17ec5e9f..c7755401d 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt @@ -4,7 +4,7 @@ import android.app.Application import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.model.Debug -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook class BookSourceDebugModel(application: Application) : BaseViewModel(application), Debug.Callback { 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..bf1eed674 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 @@ -1,9 +1,6 @@ package io.legado.app.ui.book.source.edit import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.graphics.Rect import android.net.Uri @@ -23,18 +20,14 @@ import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.rule.* import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.backgroundColor import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.login.SourceLogin +import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.GSON -import io.legado.app.utils.applyTint -import io.legado.app.utils.getViewModel -import io.legado.app.utils.shareWithQr +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_source_edit.* -import org.jetbrains.anko.displayMetrics -import org.jetbrains.anko.share -import org.jetbrains.anko.startActivity -import org.jetbrains.anko.toast +import org.jetbrains.anko.* import kotlin.math.abs class BookSourceEditActivity : @@ -43,6 +36,7 @@ class BookSourceEditActivity : override val viewModel: BookSourceEditViewModel get() = getViewModel(BookSourceEditViewModel::class.java) + private val qrRequestCode = 101 private val adapter = BookSourceEditAdapter() private val sourceEntities: ArrayList = ArrayList() private val searchEntities: ArrayList = ArrayList() @@ -69,6 +63,9 @@ class BookSourceEditActivity : override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_save -> getSource().let { source -> + if (!source.equal(viewModel.bookSource ?: BookSource())){ + source.lastUpdateTime = System.currentTimeMillis() + } if (checkSource(source)) { viewModel.save(source) { setResult(Activity.RESULT_OK); finish() } } @@ -80,17 +77,11 @@ class BookSourceEditActivity : } } } - R.id.menu_copy_source -> getSource().let { source -> - GSON.toJson(source)?.let { sourceStr -> - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr)) - } - } + R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource())) R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } - R.id.menu_share_str -> GSON.toJson(getSource())?.let { share(it) } - R.id.menu_share_qr -> GSON.toJson(getSource())?.let { sourceStr -> - shareWithQr(getString(R.string.share_book_source), sourceStr) - } + R.id.menu_qr_code_camera -> startActivityForResult(qrRequestCode) + R.id.menu_share_str -> share(GSON.toJson(getSource())) + R.id.menu_share_qr -> shareWithQr(getString(R.string.share_book_source), GSON.toJson(getSource())) R.id.menu_rule_summary -> { try { val intent = Intent(Intent.ACTION_VIEW) @@ -122,6 +113,7 @@ class BookSourceEditActivity : window.decorView.viewTreeObserver.addOnGlobalLayoutListener(KeyboardOnGlobalChangeListener()) recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.adapter = adapter + tab_layout.setBackgroundColor(backgroundColor) tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabReselected(tab: TabLayout.Tab?) { @@ -181,9 +173,11 @@ class BookSourceEditActivity : add(EditEntity("bookSourceUrl", source?.bookSourceUrl, R.string.source_url)) add(EditEntity("bookSourceName", source?.bookSourceName, R.string.source_name)) add(EditEntity("bookSourceGroup", source?.bookSourceGroup, R.string.source_group)) + add(EditEntity("bookSourceComment", source?.bookSourceComment, R.string.comment)) add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern)) add(EditEntity("header", source?.header, R.string.source_http_header)) + } //搜索 val sr = source?.getSearchRule() @@ -233,6 +227,8 @@ class BookSourceEditActivity : add(EditEntity("nextContentUrl", cr?.nextContentUrl, R.string.rule_next_content)) add(EditEntity("webJs", cr?.webJs, R.string.rule_web_js)) add(EditEntity("sourceRegex", cr?.sourceRegex, R.string.rule_source_regex)) + add(EditEntity("replaceRegex", cr?.replaceRegex, R.string.rule_replace_regex)) + add(EditEntity("imageStyle", cr?.imageStyle, R.string.rule_image_style)) } //发现 val er = source?.getExploreRule() @@ -249,6 +245,7 @@ class BookSourceEditActivity : add(EditEntity("coverUrl", er?.coverUrl, R.string.rule_cover_url)) add(EditEntity("bookUrl", er?.bookUrl, R.string.r_book_url)) } + tab_layout.selectTab(tab_layout.getTabAt(0)) setEditEntities(0) } @@ -270,6 +267,7 @@ class BookSourceEditActivity : "loginUrl" -> source.loginUrl = it.value "bookUrlPattern" -> source.bookUrlPattern = it.value "header" -> source.header = it.value + "bookSourceComment" -> source.bookSourceComment = it.value ?: "" } } searchEntities.forEach { @@ -332,13 +330,15 @@ class BookSourceEditActivity : "nextContentUrl" -> contentRule.nextContentUrl = it.value "webJs" -> contentRule.webJs = it.value "sourceRegex" -> contentRule.sourceRegex = it.value + "replaceRegex" -> contentRule.replaceRegex = it.value + "imageStyle" -> contentRule.imageStyle = 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 @@ -378,6 +386,19 @@ class BookSourceEditActivity : mSoftKeyboardTool?.dismiss() } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + qrRequestCode -> if (resultCode == RESULT_OK) { + data?.getStringExtra("result")?.let { + viewModel.importSource(it) { source -> + upRecyclerView(source) + } + } + } + } + } + private inner class KeyboardOnGlobalChangeListener : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { val rect = Rect() diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt index 82b50e536..7e1f70089 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt @@ -1,13 +1,14 @@ package io.legado.app.ui.book.source.edit import android.app.Application -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.BookSource import io.legado.app.help.storage.OldRule +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonObject +import io.legado.app.utils.getClipText import kotlinx.coroutines.Dispatchers class BookSourceEditViewModel(application: Application) : BaseViewModel(application) { @@ -52,12 +53,8 @@ class BookSourceEditViewModel(application: Application) : BaseViewModel(applicat fun pasteSource(onSuccess: (source: BookSource) -> Unit) { execute(context = Dispatchers.Main) { var source: BookSource? = null - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? - clipboard?.primaryClip?.let { - if (it.itemCount > 0) { - val json = it.getItemAt(0).text.toString() - source = OldRule.jsonToBookSource(json) - } + context.getClipText()?.let { json -> + source = OldRule.jsonToBookSource(json) } source }.onError { @@ -71,4 +68,15 @@ class BookSourceEditViewModel(application: Application) : BaseViewModel(applicat } } } + + fun importSource(text: String, finally: (source: BookSource) -> Unit) { + execute { + val text1 = text.trim() + GSON.fromJsonObject(text1)?.let { + finally.invoke(it) + } + }.onError { + toast(it.localizedMessage ?: "Error") + } + } } \ No newline at end of file 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..07f959719 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 @@ -2,6 +2,7 @@ package io.legado.app.ui.book.source.manage import android.annotation.SuppressLint import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Intent import android.os.Bundle import android.view.Menu @@ -9,27 +10,31 @@ import android.view.MenuItem import android.view.SubMenu import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView +import androidx.core.content.FileProvider import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.snackbar.Snackbar import io.legado.app.App +import io.legado.app.BuildConfig import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.AppPattern import io.legado.app.data.entities.BookSource -import io.legado.app.help.ItemTouchCallback +import io.legado.app.help.IntentDataHelp import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor import io.legado.app.service.help.CheckSource +import io.legado.app.ui.association.ImportBookSourceActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* @@ -57,9 +62,10 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity private var bookSourceLiveDate: LiveData>? = null private var groups = linkedSetOf() private var groupMenu: SubMenu? = null + private var sort = 0 + private var sortAscending = 0 override fun onActivityCreated(savedInstanceState: Bundle?) { - initUriScheme() initRecyclerView() initSearchView() initLiveDataBookSource() @@ -74,6 +80,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) } @@ -82,16 +90,57 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity when (item.itemId) { R.id.menu_add_book_source -> startActivity() R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) + R.id.menu_share_source -> { + try { + val json = GSON.toJson(adapter.getSelection()) + val intent = Intent(Intent.ACTION_SEND) + val file = FileUtils.createFileWithReplace("$filesDir/shareBookSource.json") + file.writeText(json) + val fileUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileProvider", file) + intent.type = "text/*" + intent.putExtra(Intent.EXTRA_STREAM, fileUri) + intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + startActivity(Intent.createChooser(intent, getString(R.string.share_selected_source))) + } catch (e: ActivityNotFoundException) { + e.printStackTrace() + } + } R.id.menu_group_manage -> GroupManageDialog().show(supportFragmentManager, "groupManage") R.id.menu_import_source_local -> FilePicker - .selectFile( - this, - importRequestCode, - type = "text/*", - allowExtensions = arrayOf("txt", "json") - ) + .selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) R.id.menu_import_source_onLine -> showImportDialog() + R.id.menu_sort_manual -> { + item.isChecked = true + sortCheck(0) + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_auto -> { + item.isChecked = true + sortCheck(1) + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_pin_yin -> { + item.isChecked = true + sortCheck(2) + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_url -> { + item.isChecked = true + sortCheck(3) + initLiveDataBookSource(search_view.query?.toString()) + } + R.id.menu_sort_time -> { + item.isChecked = true + sortCheck(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) @@ -99,28 +148,6 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity return super.onCompatOptionsItemSelected(item) } - private fun initUriScheme() { - intent.data?.let { - 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)){ - viewModel.importSource(url) { msg -> - title_bar.snackbar(msg) - } - } - else{ - viewModel.importSourceFromFilePath(url){msg -> - title_bar.snackbar(msg)} - } - } - else -> { - toast("格式不对") - } - } - } - } - private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) @@ -130,6 +157,13 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity val itemTouchCallback = ItemTouchCallback() itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.isCanDrag = true + val dragSelectTouchHelper: DragSelectTouchHelper = + DragSelectTouchHelper(adapter.initDragSelectTouchHelperCallback()).setSlideArea(16, 50) + dragSelectTouchHelper.attachToRecyclerView(recycler_view) + // When this page is opened, it is in selection mode + dragSelectTouchHelper.activeSlideSelect() + + // Note: need judge selection first, so add ItemTouchHelper after it. ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) } @@ -143,24 +177,58 @@ 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, { data -> + val sourceList = when (sortAscending % 2){ + 0 -> when (sort) { + 1 -> data.sortedBy { it.weight } + 2 -> data.sortedBy { it.bookSourceName } + 3 -> data.sortedBy { it.bookSourceUrl } + 4 -> data.sortedByDescending { it.lastUpdateTime} + else -> data + } + else -> when (sort) { + 1 -> data.sortedByDescending { it.weight } + 2 -> data.sortedByDescending { it.bookSourceName } + 3 -> data.sortedByDescending { it.bookSourceUrl } + 4 -> data.sortedBy { it.lastUpdateTime} + else -> data.reversed() + } + } val diffResult = DiffUtil - .calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), it)) - adapter.setItems(it, diffResult) + .calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), sourceList)) + adapter.setItems(sourceList, diffResult) upCountView() }) } + private fun sortCheck (sortId: Int){ + if (sort == sortId){ + sortAscending +=1 + } + else{ + sortAscending = 0 + sort = sortId + } + } private fun initLiveDataGroup() { - App.db.bookSourceDao().liveGroup().observe(this, Observer { + App.db.bookSourceDao().liveGroup().observe(this, { groups.clear() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } upGroupMenu() }) @@ -201,12 +269,80 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection()) R.id.menu_disable_explore -> viewModel.disableSelectExplore(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()) + R.id.menu_add_group -> selectionAddToGroups() + R.id.menu_remove_group -> selectionRemoveFromGroups() R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode) - R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection()) } 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() + } + + @SuppressLint("InflateParams") + private fun selectionAddToGroups() { + alert(titleResource = R.string.add_group) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + edit_view.setHint(R.string.group_name) + } + } + okButton { + editText?.text?.toString()?.let { + if (it.isNotEmpty()) { + viewModel.selectionAddToGroups(adapter.getSelection(), it) + } + } + } + noButton { } + }.show().applyTint() + } + + @SuppressLint("InflateParams") + private fun selectionRemoveFromGroups() { + alert(titleResource = R.string.remove_group) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + edit_view.setHint(R.string.group_name) + } + } + okButton { + editText?.text?.toString()?.let { + if (it.isNotEmpty()) { + viewModel.selectionRemoveFromGroups(adapter.getSelection(), it) + } + } + } + noButton { } + }.show().applyTint() + } + private fun upGroupMenu() { groupMenu?.removeGroup(R.id.source_group) groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) @@ -241,10 +377,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity cacheUrls.add(0, it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + startActivity(Pair("source", it)) } } cancelButton() @@ -286,6 +419,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( @@ -293,10 +430,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity File(currentPath) ) importRequestCode -> { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSourceFromFilePath(currentPath) { msg -> - title_bar.snackbar(msg) - } + startActivity(Pair("filePath", currentPath)) } } } @@ -306,24 +440,18 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity when (requestCode) { qrRequestCode -> if (resultCode == RESULT_OK) { data?.getStringExtra("result")?.let { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + startActivity("source" to it) } } importRequestCode -> if (resultCode == Activity.RESULT_OK) { data?.data?.let { uri -> try { uri.readText(this)?.let { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) - .show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + val dataKey = IntentDataHelp.putData(it) + startActivity("dataKey" to dataKey) } } catch (e: Exception) { - toast(e.localizedMessage ?: "ERROR") + toast("readTextError:${e.localizedMessage}") } } } 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..a5177f443 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 @@ -12,8 +12,9 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.BookSource -import io.legado.app.help.ItemTouchCallback.OnItemTouchCallbackListener import io.legado.app.lib.theme.backgroundColor +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback.OnItemTouchCallbackListener import io.legado.app.utils.invisible import io.legado.app.utils.visible import kotlinx.android.synthetic.main.item_book_source.view.* @@ -46,14 +47,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 +139,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)) @@ -192,11 +194,38 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : } } + fun initDragSelectTouchHelperCallback(): DragSelectTouchHelper.Callback { + return object : DragSelectTouchHelper.AdvanceCallback(Mode.ToggleAndReverse) { + override fun currentSelectedId(): MutableSet { + return selected + } + + override fun getItemId(position: Int): BookSource { + return getItem(position)!! + } + + override fun updateSelectState(position: Int, isSelected: Boolean): Boolean { + getItem(position)?.let { + if (isSelected) { + selected.add(it) + } else { + selected.remove(it) + } + notifyItemChanged(position, bundleOf(Pair("selected", null))) + callBack.upCountView() + return true + } + return false + } + } + } + interface CallBack { fun del(bookSource: BookSource) 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 c84497536..e291ac625 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 @@ -3,25 +3,36 @@ package io.legado.app.ui.book.source.manage import android.app.Application import android.text.TextUtils import androidx.documentfile.provider.DocumentFile -import com.jayway.jsonpath.JsonPath import io.legado.app.App import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppPattern import io.legado.app.data.entities.BookSource -import io.legado.app.help.http.HttpHelper -import io.legado.app.help.storage.OldRule -import io.legado.app.help.storage.Restore.jsonPath -import io.legado.app.utils.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.jetbrains.anko.toast +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.splitNotBlank +import io.legado.app.utils.writeText +import org.jetbrains.anko.longToast 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) } } @@ -43,7 +54,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelection(sources: LinkedHashSet) { + fun enableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -53,7 +64,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelection(sources: LinkedHashSet) { + fun disableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -63,7 +74,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelectExplore(sources: LinkedHashSet) { + fun enableSelectExplore(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -73,7 +84,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelectExplore(sources: LinkedHashSet) { + fun disableSelectExplore(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -83,34 +94,72 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun delSelection(sources: LinkedHashSet) { + fun selectionAddToGroups(sources: List, groups: String) { + execute { + val list = arrayListOf() + sources.forEach { source -> + val newGroupList = arrayListOf() + source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.forEach { + newGroupList.add(it) + } + groups.splitNotBlank(",", ";", ",").forEach { + newGroupList.add(it) + } + val lh = LinkedHashSet(newGroupList) + val newGroup = ArrayList(lh).joinToString(separator = ",") + list.add(source.copy(bookSourceGroup = newGroup)) + } + App.db.bookSourceDao().update(*list.toTypedArray()) + } + } + + fun selectionRemoveFromGroups(sources: List, groups: String) { + execute { + val list = arrayListOf() + sources.forEach { source -> + val newGroupList = arrayListOf() + source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.forEach { + newGroupList.add(it) + } + groups.splitNotBlank(",", ";", ",").forEach { + newGroupList.remove(it) + } + val lh = LinkedHashSet(newGroupList) + val newGroup = ArrayList(lh).joinToString(separator = ",") + list.add(source.copy(bookSourceGroup = newGroup)) + } + App.db.bookSourceDao().update(*list.toTypedArray()) + } + } + + 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") .writeText(json) }.onSuccess { - context.toast("成功导出至\n${file.absolutePath}") + context.longToast("成功导出至\n${file.absolutePath}") }.onError { - context.toast("导出失败\n${it.localizedMessage}") + context.longToast("导出失败\n${it.localizedMessage}") } } - fun exportSelection(sources: LinkedHashSet, doc: DocumentFile) { + fun exportSelection(sources: List, doc: DocumentFile) { execute { val json = GSON.toJson(sources) doc.findFile("exportBookSource.json")?.delete() doc.createFile("", "exportBookSource.json") ?.writeText(context, json) }.onSuccess { - context.toast("成功导出至\n${doc.uri.path}") + context.longToast("成功导出至\n${doc.uri.path}") }.onError { - context.toast("导出失败\n${it.localizedMessage}") + context.longToast("导出失败\n${it.localizedMessage}") } } @@ -144,89 +193,11 @@ 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()) } } } - fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) { - execute { - val file = File(path) - if (file.exists()) { - importSource(file.readText(), finally) - } else { - withContext(Dispatchers.Main) { - finally("打开文件出错") - } - } - }.onError { - finally(it.localizedMessage ?: "打开文件出错") - } - } - - fun importSource(text: String, finally: (msg: String) -> Unit) { - execute { - val text1 = text.trim() - when { - text1.isJsonObject() -> { - val json = JsonPath.parse(text1) - val urls = json.read>("$.sourceUrls") - var count = 0 - if (!urls.isNullOrEmpty()) { - urls.forEach { - count += importSourceUrl(it) - } - } else { - OldRule.jsonToBookSource(text1)?.let { - App.db.bookSourceDao().insert(it) - count = 1 - } - } - "导入${count}条" - } - text1.isJsonArray() -> { - val bookSources = mutableListOf() - val items: List> = jsonPath.parse(text1).read("$") - for (item in items) { - val jsonItem = jsonPath.parse(item) - OldRule.jsonToBookSource(jsonItem.jsonString())?.let { - bookSources.add(it) - } - } - App.db.bookSourceDao().insert(*bookSources.toTypedArray()) - "导入${bookSources.size}条" - } - text1.isAbsUrl() -> { - val count = importSourceUrl(text1) - "导入${count}条" - } - else -> "格式不对" - } - }.onError { - finally(it.localizedMessage ?: "") - }.onSuccess { - finally(it) - } - } - - private fun importSourceUrl(url: String): Int { - HttpHelper.simpleGet(url)?.let { body -> - val bookSources = mutableListOf() - val items: List> = jsonPath.parse(body).read("$") - for (item in items) { - val jsonItem = jsonPath.parse(item) - OldRule.jsonToBookSource(jsonItem.jsonString())?.let { source -> - bookSources.add(source) - } - } - 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/book/source/manage/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt index d4a466948..5c7136691 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt @@ -11,17 +11,18 @@ import android.view.ViewGroup import android.widget.EditText import androidx.appcompat.widget.Toolbar import androidx.fragment.app.DialogFragment -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme +import io.legado.app.constant.AppPattern import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModelOfActivity @@ -54,22 +55,24 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initData() - } - - private fun initData() { + view.setBackgroundColor(backgroundColor) + tool_bar.setBackgroundColor(primaryColor) tool_bar.title = getString(R.string.group_manage) tool_bar.inflateMenu(R.menu.group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter - App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { + initData() + } + + private fun initData() { + App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, { val groups = linkedSetOf() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } adapter.setItems(groups.toList()) }) @@ -89,7 +92,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view.apply { - hint = "分组名称" + setHint(R.string.group_name) } } } @@ -111,7 +114,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view.apply { - hint = "分组名称" + setHint(R.string.group_name) setText(group) } } @@ -128,6 +131,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList) { with(holder.itemView) { + setBackgroundColor(context.backgroundColor) tv_group.text = item } } diff --git a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt index 9df96e3f2..56d753eaf 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt @@ -8,15 +8,18 @@ import android.view.View import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import io.legado.app.R +import io.legado.app.base.BasePreferenceFragment import io.legado.app.constant.PreferKey +import io.legado.app.help.storage.Restore +import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.ui.filechooser.FileChooserDialog +import io.legado.app.utils.applyTint import io.legado.app.utils.getPrefString -class BackupConfigFragment : PreferenceFragmentCompat(), +class BackupConfigFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener, FileChooserDialog.CallBack { @@ -44,6 +47,8 @@ class BackupConfigFragment : PreferenceFragmentCompat(), upPreferenceSummary(PreferKey.webDavAccount, getPrefString(PreferKey.webDavAccount)) upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword)) upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath)) + findPreference("web_dav_restore") + ?.onLongClick = { BackupRestoreUi.restoreByFolder(this) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -104,6 +109,7 @@ class BackupConfigFragment : PreferenceFragmentCompat(), override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this) + PreferKey.restoreIgnore -> restoreIgnore() "web_dav_backup" -> BackupRestoreUi.backup(this) "web_dav_restore" -> BackupRestoreUi.restore(this) "import_old" -> BackupRestoreUi.importOldData(this) @@ -111,6 +117,22 @@ class BackupConfigFragment : PreferenceFragmentCompat(), return super.onPreferenceTreeClick(preference) } + + private fun restoreIgnore() { + val checkedItems = BooleanArray(Restore.ignoreKeys.size) { + Restore.ignoreConfig[Restore.ignoreKeys[it]] ?: false + } + alert(R.string.restore_ignore) { + multiChoiceItems(Restore.ignoreTitle, checkedItems) { _, which, isChecked -> + Restore.ignoreConfig[Restore.ignoreKeys[which]] = isChecked + } + }.show() + .applyTint() + .setOnDismissListener { + Restore.saveIgnoreConfig() + } + } + override fun onFilePicked(requestCode: Int, currentPath: String) { BackupRestoreUi.onFilePicked(requestCode, currentPath) } 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..e62030482 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 @@ -19,6 +19,7 @@ import io.legado.app.help.storage.WebDavHelp import io.legado.app.ui.filechooser.FilePicker import io.legado.app.utils.getPrefString import io.legado.app.utils.isContentPath +import io.legado.app.utils.longToast import io.legado.app.utils.toast import kotlinx.coroutines.Dispatchers.Main import org.jetbrains.anko.toast @@ -77,30 +78,32 @@ object BackupRestoreUi { fun restore(fragment: Fragment) { Coroutine.async(context = Main) { - if (!WebDavHelp.showRestoreDialog(fragment.requireContext()) { - fragment.toast(R.string.restore_success) - }) { - val backupPath = fragment.getPrefString(PreferKey.backupPath) - if (backupPath?.isNotEmpty() == true) { - if (backupPath.isContentPath()) { - val uri = Uri.parse(backupPath) - val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri) - if (doc?.canWrite() == true) { - Restore.restore(fragment.requireContext(), backupPath) - fragment.toast(R.string.restore_success) - } else { - selectBackupFolder(fragment, restoreSelectRequestCode) - } + WebDavHelp.showRestoreDialog(fragment.requireContext()) + }.onError { + fragment.longToast("WebDavError:${it.localizedMessage}\n将从本地备份恢复。") + val backupPath = fragment.getPrefString(PreferKey.backupPath) + if (backupPath?.isNotEmpty() == true) { + if (backupPath.isContentPath()) { + val uri = Uri.parse(backupPath) + val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri) + if (doc?.canWrite() == true) { + Restore.restore(fragment.requireContext(), backupPath) } else { - restoreUsePermission(fragment, backupPath) + selectBackupFolder(fragment, restoreSelectRequestCode) } } else { - selectBackupFolder(fragment, restoreSelectRequestCode) + restoreUsePermission(fragment, backupPath) } + } else { + selectBackupFolder(fragment, restoreSelectRequestCode) } } } + fun restoreByFolder(fragment: Fragment) { + selectBackupFolder(fragment, restoreSelectRequestCode) + } + private fun restoreUsePermission(fragment: Fragment, path: String) { PermissionsCompat.Builder(fragment) .addPermissions(*Permissions.Group.STORAGE) @@ -110,8 +113,6 @@ object BackupRestoreUi { AppConfig.backupPath = path Restore.restoreDatabase(path) Restore.restoreConfig(path) - }.onSuccess { - fragment.toast(R.string.restore_success) } } .request() @@ -135,8 +136,6 @@ object BackupRestoreUi { AppConfig.backupPath = currentPath Coroutine.async { Restore.restore(App.INSTANCE, currentPath) - }.onSuccess { - App.INSTANCE.toast(R.string.restore_success) } } selectFolderRequestCode -> { @@ -175,8 +174,6 @@ object BackupRestoreUi { AppConfig.backupPath = uri.toString() Coroutine.async { Restore.restore(App.INSTANCE, uri.toString()) - }.onSuccess { - App.INSTANCE.toast(R.string.restore_success) } } } @@ -190,10 +187,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/FileAssociationActivity.kt b/app/src/main/java/io/legado/app/ui/config/FileAssociationActivity.kt deleted file mode 100644 index 80390c639..000000000 --- a/app/src/main/java/io/legado/app/ui/config/FileAssociationActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.legado.app.ui.config - -import android.content.Intent -import android.os.Bundle -import io.legado.app.R -import io.legado.app.base.VMBaseActivity -import io.legado.app.ui.main.MainActivity -import io.legado.app.utils.getViewModel - - -class FileAssociationActivity : - VMBaseActivity(R.layout.activity_file_association) { - override val viewModel: FileAssociationViewModel - get() = getViewModel(FileAssociationViewModel::class.java) - - override fun onActivityCreated(savedInstanceState: Bundle?) { - intent.data?.let { data -> - val newIntent = viewModel.dispatchIndent(data) - if (newIntent != null) { - this.startActivityForResult(newIntent, 100) - } else { - gotoMainActivity() - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - //返回后直接跳转到主页面 - gotoMainActivity() - } - - private fun gotoMainActivity() { - val mIntent = Intent() - mIntent.setClass(this, MainActivity::class.java) - startActivity(mIntent) - } -} 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 deleted file mode 100644 index 80db1af37..000000000 --- a/app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt +++ /dev/null @@ -1,69 +0,0 @@ -package io.legado.app.ui.config - -import android.app.Application -import android.content.Intent -import android.net.Uri -import android.text.TextUtils -import androidx.documentfile.provider.DocumentFile -import io.legado.app.base.BaseViewModel -import io.legado.app.model.localBook.LocalBook -import io.legado.app.utils.isJsonArray -import io.legado.app.utils.isJsonObject -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() - } else { - null - } - } 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" - } - } - } - if (TextUtils.isEmpty(scheme)) { - execute { - LocalBook.importFile(uri.path.toString()) - toast("添加本地文件成功${uri.path}") - } - return null - } - } else { - toast("文件不存在") - return null - } - - url = "yuedu://${scheme}/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 - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index a010428fd..1230bb2b5 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -5,31 +5,39 @@ import android.content.ComponentName import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle import android.view.View +import androidx.documentfile.provider.DocumentFile import androidx.preference.ListPreference import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BasePreferenceFragment import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat +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.dialogs.selector import io.legado.app.lib.theme.ATH import io.legado.app.receiver.SharedReceiverActivity import io.legado.app.service.WebService -import io.legado.app.ui.filechooser.FileChooserDialog -import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.utils.* +import java.io.File -class OtherConfigFragment : PreferenceFragmentCompat(), - FileChooserDialog.CallBack, +class OtherConfigFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { - private val requestCodeDownloadPath = 25324 + private val requestCodeCover = 231 + private val packageManager = App.INSTANCE.packageManager private val componentName = ComponentName( App.INSTANCE, @@ -40,9 +48,9 @@ class OtherConfigFragment : PreferenceFragmentCompat(), override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { putPrefBoolean(PreferKey.processText, isProcessTextEnabled()) addPreferencesFromResource(R.xml.pref_config_other) - upPreferenceSummary(getString(R.string.pk_download_path), BookHelp.downloadPath) upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString()) upPreferenceSummary(PreferKey.webPort, webPort.toString()) + upPreferenceSummary(PreferKey.defaultCover, getPrefString(PreferKey.defaultCover)) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -74,10 +82,17 @@ class OtherConfigFragment : PreferenceFragmentCompat(), .show { putPrefInt(PreferKey.webPort, it) } - getString(R.string.pk_download_path) -> selectDownloadPath() - PreferKey.cleanCache -> { - BookHelp.clearCache() - toast(R.string.clear_cache_success) + PreferKey.cleanCache -> clearCache() + PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) { + selectDefaultCover() + } else { + selector(items = arrayListOf("删除图片", "选择图片")) { _, i -> + if (i == 0) { + removePref(PreferKey.defaultCover) + } else { + selectDefaultCover() + } + } } } return super.onPreferenceTreeClick(preference) @@ -85,12 +100,10 @@ class OtherConfigFragment : PreferenceFragmentCompat(), override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { when (key) { - getString(R.string.pk_download_path) -> { - upPreferenceSummary(key, BookHelp.downloadPath) + PreferKey.threadCount -> { + upPreferenceSummary(key, AppConfig.threadCount.toString()) + postEvent(PreferKey.threadCount, "") } - PreferKey.threadCount -> upPreferenceSummary( - key, AppConfig.threadCount.toString() - ) PreferKey.webPort -> { upPreferenceSummary(key, webPort.toString()) if (WebService.isRun) { @@ -102,7 +115,17 @@ class OtherConfigFragment : PreferenceFragmentCompat(), PreferKey.processText -> sharedPreferences?.let { setProcessTextEnable(it.getBoolean(key, true)) } - PreferKey.showRss -> postEvent(EventBus.SHOW_RSS, "unused") + PreferKey.showRss -> postEvent(EventBus.SHOW_RSS, "") + PreferKey.defaultCover -> upPreferenceSummary( + key, + getPrefString(PreferKey.defaultCover) + ) + PreferKey.replaceEnableDefault -> AppConfig.replaceEnableDefault = + App.INSTANCE.getPrefBoolean(PreferKey.replaceEnableDefault, true) + PreferKey.language -> { + LanguageUtils.setConfigurationOld(App.INSTANCE) + postEvent(EventBus.RECREATE, "") + } } } @@ -121,6 +144,25 @@ class OtherConfigFragment : PreferenceFragmentCompat(), } } + private fun clearCache() { + requireContext().alert(titleResource = R.string.clear_cache, + messageResource = R.string.sure_del) { + okButton { + BookHelp.clearCache() + FileUtils.deleteFile(requireActivity().cacheDir.absolutePath) + toast(R.string.clear_cache_success) + } + noButton() + }.show().applyTint() + } + + private fun selectDefaultCover() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" + startActivityForResult(intent, requestCodeCover) + } + private fun isProcessTextEnabled(): Boolean { return packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED } @@ -139,34 +181,52 @@ class OtherConfigFragment : PreferenceFragmentCompat(), } } - private fun selectDownloadPath() { - FilePicker.selectFolder(this, requestCodeDownloadPath) { - removePref(getString(R.string.pk_download_path)) - } - } - - private fun putDownloadPath(path: String) { - putPrefString(getString(R.string.pk_download_path), path) - } - - override fun onFilePicked(requestCode: Int, currentPath: String) { - if (requestCode == requestCodeDownloadPath) { - putDownloadPath(currentPath) + private fun setCoverFromUri(uri: Uri) { + if (uri.toString().isContentPath()) { + val doc = DocumentFile.fromSingleUri(requireContext(), uri) + doc?.name?.let { + var file = requireContext().externalFilesDir + file = FileUtils.createFileIfNotExist(file, "covers", it) + kotlin.runCatching { + DocumentUtils.readBytes(requireContext(), doc.uri) + }.getOrNull()?.let { byteArray -> + file.writeBytes(byteArray) + putPrefString(PreferKey.defaultCover, file.absolutePath) + CoverImageView.upDefaultCover() + } ?: toast("获取文件出错") + } + } else { + PermissionsCompat.Builder(this) + .addPermissions( + Permissions.READ_EXTERNAL_STORAGE, + Permissions.WRITE_EXTERNAL_STORAGE + ) + .rationale(R.string.bg_image_per) + .onGranted { + RealPathUtil.getPath(requireContext(), uri)?.let { path -> + val imgFile = File(path) + if (imgFile.exists()) { + var file = requireContext().externalFilesDir + file = FileUtils.createFileIfNotExist(file, "covers", imgFile.name) + file.writeBytes(imgFile.readBytes()) + putPrefString(PreferKey.defaultCover, file.absolutePath) + CoverImageView.upDefaultCover() + } + } + } + .request() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { - requestCodeDownloadPath -> if (resultCode == RESULT_OK) { + requestCodeCover -> if (resultCode == RESULT_OK) { data?.data?.let { uri -> - requireContext().contentResolver.takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - putDownloadPath(uri.toString()) + setCoverFromUri(uri) } } } } + } \ 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..4b76ebc3e 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,30 +1,38 @@ 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 import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BasePreferenceFragment import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.LauncherIconHelp +import io.legado.app.help.ThemeConfig import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton -import io.legado.app.lib.dialogs.yesButton +import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.ATH -import io.legado.app.lib.theme.ColorUtils import io.legado.app.ui.widget.number.NumberPickerDialog +import io.legado.app.ui.widget.prefs.ColorPreference import io.legado.app.ui.widget.prefs.IconListPreference +import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* -class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { - - val items = arrayListOf("极简", "曜夜", "经典", "黑白", "A屏黑") +@Suppress("SameParameterValue") +class ThemeConfigFragment : BasePreferenceFragment(), + SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_theme) @@ -33,12 +41,69 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar preferenceScreen.removePreference(it) } } - upPreferenceSummary("barElevation", AppConfig.elevation.toString()) + upPreferenceSummary(PreferKey.barElevation, AppConfig.elevation.toString()) + findPreference(PreferKey.cBackground)?.let { + it.onSaveColor = { color -> + if (!ColorUtils.isColorLight(color)) { + toast(R.string.day_background_too_dark) + true + } else { + false + } + } + } + findPreference(PreferKey.cNBackground)?.let { + it.onSaveColor = { color -> + if (ColorUtils.isColorLight(color)) { + toast(R.string.night_background_too_light) + true + } else { + false + } + } + } + findPreference(PreferKey.cAccent)?.let { + it.onSaveColor = { color -> + val background = + getPrefInt(PreferKey.cBackground, getCompatColor(R.color.md_grey_100)) + val textColor = getCompatColor(R.color.primaryText) + when { + ColorUtils.getColorDifference(color, background) <= 60 -> { + toast(R.string.accent_background_diff) + true + } + ColorUtils.getColorDifference(color, textColor) <= 60 -> { + toast(R.string.accent_text_diff) + true + } + else -> false + } + } + } + findPreference(PreferKey.cNAccent)?.let { + it.onSaveColor = { color -> + val background = + getPrefInt(PreferKey.cNBackground, getCompatColor(R.color.md_grey_900)) + val textColor = getCompatColor(R.color.primaryText) + when { + ColorUtils.getColorDifference(color, background) <= 60 -> { + toast(R.string.accent_background_diff) + true + } + ColorUtils.getColorDifference(color, textColor) <= 60 -> { + toast(R.string.accent_text_diff) + true + } + else -> false + } + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ATH.applyEdgeEffectColor(listView) + setHasOptionsMenu(true) } override fun onResume() { @@ -51,142 +116,88 @@ 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" -> { - if (backgroundIsDark(sharedPreferences)) { - alert { - title = "白天背景太暗" - message = "将会恢复默认背景?" - yesButton { - putPrefInt( - "colorBackground", - getCompatColor(R.color.md_grey_100) - ) - upTheme(false) - } - - noButton { - upTheme(false) - } - }.show().applyTint() - } else { - upTheme(false) - } + PreferKey.transparentStatusBar -> recreateActivities() + PreferKey.cPrimary, + PreferKey.cAccent, + PreferKey.cBackground, + PreferKey.cBBackground -> { + upTheme(false) } - "colorPrimaryNight", - "colorAccentNight", - "colorBackgroundNight", - "colorBottomBackgroundNight" -> { - if (backgroundIsLight(sharedPreferences)) { - alert { - title = "夜间背景太亮" - message = "将会恢复默认背景?" - yesButton { - putPrefInt( - "colorBackgroundNight", - getCompatColor(R.color.md_grey_800) - ) - upTheme(true) - } - - noButton { - upTheme(true) - } - }.show().applyTint() - } else { - upTheme(true) - } + PreferKey.cNPrimary, + PreferKey.cNAccent, + PreferKey.cNBackground, + PreferKey.cNBBackground -> { + upTheme(true) } } } + @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()) + when (val key = preference?.key) { + 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 { AppConfig.elevation = it recreateActivities() } + "themeList" -> ThemeListDialog().show(childFragmentManager, "themeList") + "saveDayTheme", "saveNightTheme" -> saveThemeAlert(key) } return super.onPreferenceTreeClick(preference) } - private fun backgroundIsDark(sharedPreferences: SharedPreferences): Boolean { - return !ColorUtils.isColorLight( - sharedPreferences.getInt( - "colorBackground", - getCompatColor(R.color.md_grey_100) - ) - ) - } - - private fun backgroundIsLight(sharedPreferences: SharedPreferences): Boolean { - return ColorUtils.isColorLight( - sharedPreferences.getInt( - "colorBackgroundNight", - getCompatColor(R.color.md_grey_800) - ) - ) + @SuppressLint("InflateParams") + private fun saveThemeAlert(key: String) { + alert("主题名称") { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + } + } + okButton { + editText?.text?.toString()?.let { themeName -> + when (key) { + "saveDayTheme" -> { + ThemeConfig.saveDayTheme(requireContext(), themeName) + } + "saveNightTheme" -> { + ThemeConfig.saveNightTheme(requireContext(), themeName) + } + } + } + } + noButton { } + }.show().applyTint() } private fun upTheme(isNightTheme: Boolean) { @@ -205,7 +216,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/config/ThemeListDialog.kt b/app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt new file mode 100644 index 000000000..d7000b127 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt @@ -0,0 +1,127 @@ +package io.legado.app.ui.config + +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 androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.help.ThemeConfig +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.primaryColor +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.GSON +import io.legado.app.utils.applyTint +import io.legado.app.utils.getClipText +import io.legado.app.utils.toast +import kotlinx.android.synthetic.main.dialog_recycler_view.* +import kotlinx.android.synthetic.main.item_theme_config.view.* +import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.share + +class ThemeListDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { + + private lateinit var adapter: Adapter + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.9).toInt()) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_recycler_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) + tool_bar.setTitle(R.string.theme_list) + initView() + initMenu() + initData() + } + + private fun initView() { + adapter = Adapter() + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + recycler_view.adapter = adapter + } + + private fun initMenu() { + tool_bar.setOnMenuItemClickListener(this) + tool_bar.inflateMenu(R.menu.theme_list) + tool_bar.menu.applyTint(requireContext()) + } + + fun initData() { + adapter.setItems(ThemeConfig.configList) + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.menu_import -> { + requireContext().getClipText()?.let { + if (ThemeConfig.addConfig(it)) { + initData() + } else { + toast("格式不对,添加失败") + } + } + } + } + return true + } + + fun delete(index: Int) { + alert(R.string.delete, R.string.sure_del) { + okButton { + ThemeConfig.delConfig(index) + initData() + } + noButton() + }.show().applyTint() + } + + fun share(index: Int) { + val json = GSON.toJson(ThemeConfig.configList[index]) + requireContext().share(json, "主题分享") + } + + inner class Adapter : SimpleRecyclerAdapter(requireContext(), R.layout.item_theme_config) { + + override fun convert(holder: ItemViewHolder, item: ThemeConfig.Config, payloads: MutableList) { + holder.itemView.apply { + tv_name.text = item.themeName + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.apply { + onClick { + ThemeConfig.applyConfig(context, ThemeConfig.configList[holder.layoutPosition]) + } + iv_share.onClick { + share(holder.layoutPosition) + } + iv_delete.onClick { + delete(holder.layoutPosition) + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt index f1e6a7bc9..d7e6b05d6 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt @@ -12,14 +12,11 @@ import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R -import io.legado.app.constant.Theme +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.filechooser.adapter.FileAdapter import io.legado.app.ui.filechooser.adapter.PathAdapter import io.legado.app.ui.widget.recycler.VerticalDivider -import io.legado.app.utils.FileUtils -import io.legado.app.utils.applyTint -import io.legado.app.utils.gone -import io.legado.app.utils.visible +import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_file_chooser.* @@ -62,7 +59,7 @@ class FileChooserDialog : DialogFragment(), } override var allowExtensions: Array? = null - override val isOnlyListDir: Boolean + override val isSelectDir: Boolean get() = mode == DIRECTORY override var isShowHomeDir: Boolean = false override var isShowUpDir: Boolean = true @@ -93,6 +90,7 @@ class FileChooserDialog : DialogFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + tool_bar.setBackgroundColor(primaryColor) view.setBackgroundResource(R.color.background_card) arguments?.let { requestCode = it.getInt("requestCode") @@ -108,7 +106,7 @@ class FileChooserDialog : DialogFragment(), menus = it.getStringArray("menus") } tool_bar.title = title ?: let { - if (isOnlyListDir) { + if (isSelectDir) { getString(R.string.folder_chooser) } else { getString(R.string.file_chooser) @@ -121,7 +119,7 @@ class FileChooserDialog : DialogFragment(), private fun initMenu() { tool_bar.inflateMenu(R.menu.file_chooser) - if (isOnlyListDir) { + if (isSelectDir) { tool_bar.menu.findItem(R.id.menu_ok).isVisible = true } menus?.let { @@ -129,7 +127,7 @@ class FileChooserDialog : DialogFragment(), tool_bar.menu.add(menuTitle) } } - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) } @@ -168,10 +166,16 @@ class FileChooserDialog : DialogFragment(), refreshCurrentDirPath(fileItem.path) } else { fileItem?.path?.let { path -> - if (mode != DIRECTORY) { + if (mode == DIRECTORY) { + toast("这是文件夹选择,不能选择文件,点击右上角的确定选择文件夹") + } else if (allowExtensions == null || + allowExtensions?.contains(FileUtils.getExtension(path)) == true + ) { (parentFragment as? CallBack)?.onFilePicked(requestCode, path) (activity as? CallBack)?.onFilePicked(requestCode, path) dismiss() + } else { + toast("不能打开此文件") } } } diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt index 717ad8f11..07ed5c6f1 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt @@ -8,6 +8,7 @@ import io.legado.app.base.BaseActivity import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat import io.legado.app.lib.dialogs.alert +import io.legado.app.utils.applyTint import io.legado.app.utils.toast import org.jetbrains.anko.toast @@ -18,18 +19,18 @@ object FilePicker { activity: AppCompatActivity, requestCode: Int, title: String = activity.getString(R.string.select_folder), - default: (() -> Unit)? = null + otherActions: List? = null, + otherFun: ((action: String) -> Unit)? = null ) { activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() - default ?: let { - selectList.removeAt(0) + otherActions?.let { + selectList.addAll(otherActions) } items(selectList) { _, index -> - when (if (default == null) index + 1 else index) { - 0 -> default?.invoke() - 1 -> { + when (index) { + 0 -> { try { val intent = createSelectDirIntent() activity.startActivityForResult(intent, requestCode) @@ -38,35 +39,36 @@ object FilePicker { activity.toast(e.localizedMessage ?: "ERROR") } } - 2 -> checkPermissions(activity) { + 1 -> checkPermissions(activity) { FileChooserDialog.show( activity.supportFragmentManager, requestCode, mode = FileChooserDialog.DIRECTORY ) } + else -> otherFun?.invoke(selectList[index]) } } - }.show() + }.show().applyTint() } fun selectFolder( fragment: Fragment, requestCode: Int, title: String = fragment.getString(R.string.select_folder), - default: (() -> Unit)? = null + otherActions: List? = null, + otherFun: ((action: String) -> Unit)? = null ) { fragment.requireContext() .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() - default ?: let { - selectList.removeAt(0) + otherActions?.let { + selectList.addAll(otherActions) } items(selectList) { _, index -> - when (if (default == null) index + 1 else index) { - 0 -> default?.invoke() - 1 -> { + when (index) { + 0 -> { try { val intent = createSelectDirIntent() fragment.startActivityForResult(intent, requestCode) @@ -75,46 +77,49 @@ object FilePicker { fragment.toast(e.localizedMessage ?: "ERROR") } } - 2 -> checkPermissions(fragment) { + 1 -> checkPermissions(fragment) { FileChooserDialog.show( fragment.childFragmentManager, requestCode, mode = FileChooserDialog.DIRECTORY ) } + else -> otherFun?.invoke(selectList[index]) } } - }.show() + }.show().applyTint() } fun selectFile( activity: BaseActivity, requestCode: Int, title: String = activity.getString(R.string.select_file), - type: String, - allowExtensions: Array?, - default: (() -> Unit)? = null + allowExtensions: Array, + otherActions: List? = null, + otherFun: ((action: String) -> Unit)? = null ) { activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() - default ?: let { - selectList.removeAt(0) + otherActions?.let { + selectList.addAll(otherActions) } items(selectList) { _, index -> - when (if (default == null) index + 1 else index) { - 0 -> default?.invoke() - 1 -> { + when (index) { + 0 -> { try { val intent = createSelectFileIntent() - intent.type = type//设置类型 + intent.putExtra( + Intent.EXTRA_MIME_TYPES, + typesOfExtensions(allowExtensions) + ) activity.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { e.printStackTrace() activity.toast(e.localizedMessage ?: "ERROR") } } - 2 -> checkPermissions(activity) { + 1 -> checkPermissions(activity) { FileChooserDialog.show( activity.supportFragmentManager, requestCode, @@ -122,40 +127,43 @@ object FilePicker { allowExtensions = allowExtensions ) } + else -> otherFun?.invoke(selectList[index]) } } - }.show() + }.show().applyTint() } fun selectFile( fragment: Fragment, requestCode: Int, title: String = fragment.getString(R.string.select_file), - type: String, allowExtensions: Array, - default: (() -> Unit)? = null + otherActions: List? = null, + otherFun: ((action: String) -> Unit)? = null ) { fragment.requireContext() .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() - default ?: let { - selectList.removeAt(0) + otherActions?.let { + selectList.addAll(otherActions) } items(selectList) { _, index -> - when (if (default == null) index + 1 else index) { - 0 -> default?.invoke() - 1 -> { + when (index) { + 0 -> { try { val intent = createSelectFileIntent() - intent.type = type//设置类型 + intent.putExtra( + Intent.EXTRA_MIME_TYPES, + typesOfExtensions(allowExtensions) + ) fragment.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { e.printStackTrace() fragment.toast(e.localizedMessage ?: "ERROR") } } - 2 -> checkPermissions(fragment) { + 1 -> checkPermissions(fragment) { FileChooserDialog.show( fragment.childFragmentManager, requestCode, @@ -163,15 +171,17 @@ object FilePicker { allowExtensions = allowExtensions ) } + else -> otherFun?.invoke(selectList[index]) } } - }.show() + }.show().applyTint() } private fun createSelectFileIntent(): Intent { val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = "*/*" return intent } @@ -200,4 +210,15 @@ object FilePicker { } .request() } + + private fun typesOfExtensions(allowExtensions: Array): Array { + val types = hashSetOf() + allowExtensions.forEach { + when (it) { + "txt", "xml" -> types.add("text/*") + else -> types.add("application/$it") + } + } + return types.toTypedArray() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt index 42e34dfb1..4458b692e 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt @@ -2,14 +2,16 @@ package io.legado.app.ui.filechooser.adapter import android.content.Context -import android.graphics.drawable.Drawable import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.help.AppConfig +import io.legado.app.lib.theme.getPrimaryDisabledTextColor +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.ui.filechooser.entity.FileItem import io.legado.app.ui.filechooser.utils.ConvertUtils import io.legado.app.ui.filechooser.utils.FilePickerIcon -import io.legado.app.ui.filechooser.utils.FileUtils +import io.legado.app.utils.FileUtils import kotlinx.android.synthetic.main.item_path_filepicker.view.* import org.jetbrains.anko.sdk27.listeners.onClick import java.io.File @@ -21,27 +23,17 @@ class FileAdapter(context: Context, val callBack: CallBack) : private var rootPath: String? = null var currentPath: String? = null private set - private var homeIcon: Drawable? = null - private var upIcon: Drawable? = null - private var folderIcon: Drawable? = null - private var fileIcon: Drawable? = null + private val homeIcon = ConvertUtils.toDrawable(FilePickerIcon.getHOME()) + private val upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUPDIR()) + private val folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFOLDER()) + private val fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFILE()) + private val primaryTextColor = context.getPrimaryTextColor(!AppConfig.isNightTheme) + private val disabledTextColor = context.getPrimaryDisabledTextColor(!AppConfig.isNightTheme) fun loadData(path: String?) { if (path == null) { return } - if (homeIcon == null) { - homeIcon = ConvertUtils.toDrawable(FilePickerIcon.getHOME()) - } - if (upIcon == null) { - upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUPDIR()) - } - if (folderIcon == null) { - folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFOLDER()) - } - if (fileIcon == null) { - fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFILE()) - } val data = ArrayList() if (rootPath == null) { rootPath = path @@ -68,19 +60,7 @@ class FileAdapter(context: Context, val callBack: CallBack) : data.add(fileParent) } currentPath?.let { currentPath -> - val files: Array? = callBack.allowExtensions?.let { allowExtensions -> - if (callBack.isOnlyListDir) { - FileUtils.listDirs(currentPath, allowExtensions) - } else { - FileUtils.listDirsAndFiles(currentPath, allowExtensions) - } - } ?: let { - if (callBack.isOnlyListDir) { - FileUtils.listDirs(currentPath) - } else { - FileUtils.listDirsAndFiles(currentPath) - } - } + val files: Array? = FileUtils.listDirsAndFiles(currentPath) if (files != null) { for (file in files) { if (file == null || (!callBack.isShowHideDir && file.name.startsWith("."))) { @@ -110,6 +90,21 @@ class FileAdapter(context: Context, val callBack: CallBack) : holder.itemView.apply { image_view.setImageDrawable(item.icon) text_view.text = item.name + if (item.isDirectory) { + text_view.setTextColor(primaryTextColor) + } else { + if (callBack.isSelectDir) { + text_view.setTextColor(disabledTextColor) + } else { + callBack.allowExtensions?.let { + if (it.contains(FileUtils.getExtension(item.path))) { + text_view.setTextColor(primaryTextColor) + } else { + text_view.setTextColor(disabledTextColor) + } + } ?: text_view.setTextColor(primaryTextColor) + } + } } } @@ -121,12 +116,15 @@ class FileAdapter(context: Context, val callBack: CallBack) : interface CallBack { fun onFileClick(position: Int) + //允许的扩展名 var allowExtensions: Array? + /** - * 是否仅仅读取目录 + * 是否选取目录 */ - val isOnlyListDir: Boolean + val isSelectDir: Boolean + /** * 是否显示返回主目录 */ diff --git a/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt b/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt index 25ad9d56b..17d2f15e7 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt @@ -5,7 +5,6 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import okhttp3.internal.and import java.io.BufferedReader import java.io.IOException import java.io.InputStream @@ -33,10 +32,10 @@ object ConvertUtils { fun toInt(bytes: ByteArray): Int { var result = 0 - var abyte: Byte + var byte: Byte for (i in bytes.indices) { - abyte = bytes[i] - result += abyte and 0xFF shl 8 * i + byte = bytes[i] + result += (byte.toInt() and 0xFF).shl(8 * i) } return result } diff --git a/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt b/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt deleted file mode 100644 index e7e2a4805..000000000 --- a/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt +++ /dev/null @@ -1,670 +0,0 @@ -package io.legado.app.ui.filechooser.utils - -import android.webkit.MimeTypeMap -import androidx.annotation.IntDef -import java.io.* -import java.nio.charset.Charset -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern - -/** - * 文件处理 - * - * - * - * @author 李玉江[QQ:1023694760] - * @since 2014-4-18 - */ -object FileUtils { - const val BY_NAME_ASC = 0 - const val BY_NAME_DESC = 1 - const val BY_TIME_ASC = 2 - const val BY_TIME_DESC = 3 - const val BY_SIZE_ASC = 4 - const val BY_SIZE_DESC = 5 - const val BY_EXTENSION_ASC = 6 - const val BY_EXTENSION_DESC = 7 - - @IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC]) - @kotlin.annotation.Retention(AnnotationRetention.SOURCE) - annotation class SortType - - /** - * 将目录分隔符统一为平台默认的分隔符,并为目录结尾添加分隔符 - */ - fun separator(path: String): String { - var path1 = path - val separator = File.separator - path1 = path1.replace("\\", separator) - if (!path1.endsWith(separator)) { - path1 += separator - } - return path1 - } - - fun closeSilently(c: Closeable?) { - if (c == null) { - return - } - try { - c.close() - } catch (ignored: IOException) { - } - - } - - /** - * 列出指定目录下的所有子目录 - */ - @JvmOverloads - fun listDirs( - startDirPath: String, - excludeDirs: Array? = null, @SortType sortType: Int = BY_NAME_ASC - ): Array { - var excludeDirs1 = excludeDirs - val dirList = ArrayList() - val startDir = File(startDirPath) - if (!startDir.isDirectory) { - return arrayOfNulls(0) - } - val dirs = startDir.listFiles(FileFilter { f -> - if (f == null) { - return@FileFilter false - } - f.isDirectory - }) - ?: return arrayOfNulls(0) - if (excludeDirs1 == null) { - excludeDirs1 = arrayOf() - } - for (dir in dirs) { - val file = dir.absoluteFile - if (!excludeDirs1.contentDeepToString().contains(file.name)) { - dirList.add(file) - } - } - when (sortType) { - BY_NAME_ASC -> Collections.sort(dirList, SortByName()) - BY_NAME_DESC -> { - Collections.sort(dirList, SortByName()) - dirList.reverse() - } - BY_TIME_ASC -> Collections.sort(dirList, SortByTime()) - BY_TIME_DESC -> { - Collections.sort(dirList, SortByTime()) - dirList.reverse() - } - BY_SIZE_ASC -> Collections.sort(dirList, SortBySize()) - BY_SIZE_DESC -> { - Collections.sort(dirList, SortBySize()) - dirList.reverse() - } - BY_EXTENSION_ASC -> Collections.sort(dirList, SortByExtension()) - BY_EXTENSION_DESC -> { - Collections.sort(dirList, SortByExtension()) - dirList.reverse() - } - } - return dirList.toTypedArray() - } - - /** - * 列出指定目录下的所有子目录及所有文件 - */ - @JvmOverloads - fun listDirsAndFiles( - startDirPath: String, - allowExtensions: Array? = null - ): Array? { - val dirs: Array? - val files: Array? = if (allowExtensions == null) { - listFiles(startDirPath) - } else { - listFiles(startDirPath, allowExtensions) - } - val dirsAndFiles: Array - dirs = listDirs(startDirPath) - if (files == null) { - return null - } - dirsAndFiles = arrayOfNulls(dirs.size + files.size) - System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.size) - System.arraycopy(files, 0, dirsAndFiles, dirs.size, files.size) - return dirsAndFiles - } - - /** - * 列出指定目录下的所有文件 - */ - @JvmOverloads - fun listFiles( - startDirPath: String, - filterPattern: Pattern? = null, @SortType sortType: Int = BY_NAME_ASC - ): Array { - val fileList = ArrayList() - val f = File(startDirPath) - if (!f.isDirectory) { - return arrayOfNulls(0) - } - val files = f.listFiles(FileFilter { file -> - if (file == null) { - return@FileFilter false - } - if (file.isDirectory) { - return@FileFilter false - } - - filterPattern?.matcher(file.name)?.find() ?: true - }) - ?: return arrayOfNulls(0) - for (file in files) { - fileList.add(file.absoluteFile) - } - when (sortType) { - BY_NAME_ASC -> Collections.sort(fileList, SortByName()) - BY_NAME_DESC -> { - Collections.sort(fileList, SortByName()) - fileList.reverse() - } - BY_TIME_ASC -> Collections.sort(fileList, SortByTime()) - BY_TIME_DESC -> { - Collections.sort(fileList, SortByTime()) - fileList.reverse() - } - BY_SIZE_ASC -> Collections.sort(fileList, SortBySize()) - BY_SIZE_DESC -> { - Collections.sort(fileList, SortBySize()) - fileList.reverse() - } - BY_EXTENSION_ASC -> Collections.sort(fileList, SortByExtension()) - BY_EXTENSION_DESC -> { - Collections.sort(fileList, SortByExtension()) - fileList.reverse() - } - } - return fileList.toTypedArray() - } - - /** - * 列出指定目录下的所有文件 - */ - fun listFiles(startDirPath: String, allowExtensions: Array?): Array? { - val file = File(startDirPath) - return file.listFiles { _, name -> - //返回当前目录所有以某些扩展名结尾的文件 - val extension = getExtension(name) - allowExtensions?.contentDeepToString()?.contains(extension) == true - || allowExtensions == null - } - } - - /** - * 列出指定目录下的所有文件 - */ - fun listFiles(startDirPath: String, allowExtension: String?): Array? { - return if (allowExtension == null) - listFiles(startDirPath, allowExtension = null) - else - listFiles(startDirPath, arrayOf(allowExtension)) - } - - /** - * 判断文件或目录是否存在 - */ - fun exist(path: String): Boolean { - val file = File(path) - return file.exists() - } - - /** - * 删除文件或目录 - */ - @JvmOverloads - fun delete(file: File, deleteRootDir: Boolean = false): Boolean { - var result = false - if (file.isFile) { - //是文件 - result = deleteResolveEBUSY(file) - } else { - //是目录 - val files = file.listFiles() ?: return false - if (files.isEmpty()) { - result = deleteRootDir && deleteResolveEBUSY(file) - } else { - for (f in files) { - delete(f, deleteRootDir) - result = deleteResolveEBUSY(f) - } - } - if (deleteRootDir) { - result = deleteResolveEBUSY(file) - } - } - return result - } - - /** - * bug: open failed: EBUSY (Device or resource busy) - * fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy - */ - private fun deleteResolveEBUSY(file: File): Boolean { - // Before you delete a Directory or File: rename it! - val to = File(file.absolutePath + System.currentTimeMillis()) - - file.renameTo(to) - return to.delete() - } - - /** - * 删除文件或目录 - */ - @JvmOverloads - fun delete(path: String, deleteRootDir: Boolean = false): Boolean { - val file = File(path) - - return if (file.exists()) { - delete(file, deleteRootDir) - } else false - } - - /** - * 复制文件为另一个文件,或复制某目录下的所有文件及目录到另一个目录下 - */ - fun copy(src: String, tar: String): Boolean { - val srcFile = File(src) - return srcFile.exists() && copy(srcFile, File(tar)) - } - - /** - * 复制文件或目录 - */ - fun copy(src: File, tar: File): Boolean { - try { - if (src.isFile) { - val `is` = FileInputStream(src) - val op = FileOutputStream(tar) - val bis = BufferedInputStream(`is`) - val bos = BufferedOutputStream(op) - val bt = ByteArray(1024 * 8) - while (true) { - val len = bis.read(bt) - if (len == -1) { - break - } else { - bos.write(bt, 0, len) - } - } - bis.close() - bos.close() - } else if (src.isDirectory) { - tar.mkdirs() - src.listFiles()?.forEach { file -> - copy(file.absoluteFile, File(tar.absoluteFile, file.name)) - } - } - return true - } catch (e: Exception) { - return false - } - - } - - /** - * 移动文件或目录 - */ - fun move(src: String, tar: String): Boolean { - return move(File(src), File(tar)) - } - - /** - * 移动文件或目录 - */ - fun move(src: File, tar: File): Boolean { - return rename(src, tar) - } - - /** - * 文件重命名 - */ - fun rename(oldPath: String, newPath: String): Boolean { - return rename(File(oldPath), File(newPath)) - } - - /** - * 文件重命名 - */ - fun rename(src: File, tar: File): Boolean { - return src.renameTo(tar) - } - - /** - * 读取文本文件, 失败将返回空串 - */ - @JvmOverloads - fun readText(filepath: String, charset: String = "utf-8"): String { - try { - val data = readBytes(filepath) - if (data != null) { - return String(data, Charset.forName(charset)).trim { it <= ' ' } - } - } catch (ignored: UnsupportedEncodingException) { - } - - return "" - } - - /** - * 读取文件内容, 失败将返回空串 - */ - fun readBytes(filepath: String): ByteArray? { - var fis: FileInputStream? = null - try { - fis = FileInputStream(filepath) - val baos = ByteArrayOutputStream() - val buffer = ByteArray(1024) - while (true) { - val len = fis.read(buffer, 0, buffer.size) - if (len == -1) { - break - } else { - baos.write(buffer, 0, len) - } - } - val data = baos.toByteArray() - baos.close() - return data - } catch (e: IOException) { - return null - } finally { - closeSilently(fis) - } - } - - /** - * 保存文本内容 - */ - @JvmOverloads - fun writeText(filepath: String, content: String, charset: String = "utf-8"): Boolean { - try { - writeBytes(filepath, content.toByteArray(charset(charset))) - return true - } catch (e: UnsupportedEncodingException) { - return false - } - - } - - /** - * 保存文件内容 - */ - fun writeBytes(filepath: String, data: ByteArray): Boolean { - val file = File(filepath) - var fos: FileOutputStream? = null - return try { - if (!file.exists()) { - file.parentFile?.mkdirs() - file.createNewFile() - } - fos = FileOutputStream(filepath) - fos.write(data) - true - } catch (e: IOException) { - false - } finally { - closeSilently(fos) - } - } - - /** - * 追加文本内容 - */ - fun appendText(path: String, content: String): Boolean { - val file = File(path) - var writer: FileWriter? = null - return try { - if (!file.exists()) { - - file.createNewFile() - } - writer = FileWriter(file, true) - writer.write(content) - true - } catch (e: IOException) { - false - } finally { - closeSilently(writer) - } - } - - /** - * 获取文件大小 - */ - fun getLength(path: String): Long { - val file = File(path) - return if (!file.isFile || !file.exists()) { - 0 - } else file.length() - } - - /** - * 获取文件或网址的名称(包括后缀) - */ - fun getName(pathOrUrl: String?): String { - if (pathOrUrl == null) { - return "" - } - val pos = pathOrUrl.lastIndexOf('/') - return if (0 <= pos) { - pathOrUrl.substring(pos + 1) - } else { - System.currentTimeMillis().toString() + "." + getExtension(pathOrUrl) - } - } - - /** - * 获取文件名(不包括扩展名) - */ - fun getNameExcludeExtension(path: String): String { - try { - var fileName = File(path).name - val lastIndexOf = fileName.lastIndexOf(".") - if (lastIndexOf != -1) { - fileName = fileName.substring(0, lastIndexOf) - } - return fileName - } catch (e: Exception) { - return "" - } - - } - - /** - * 获取格式化后的文件大小 - */ - fun getSize(path: String): String { - val fileSize = getLength(path) - return ConvertUtils.toFileSizeString(fileSize) - } - - /** - * 获取文件后缀,不包括“.” - */ - fun getExtension(pathOrUrl: String): String { - val dotPos = pathOrUrl.lastIndexOf('.') - return if (0 <= dotPos) { - pathOrUrl.substring(dotPos + 1) - } else { - "ext" - } - } - - /** - * 获取文件的MIME类型 - */ - fun getMimeType(pathOrUrl: String): String { - val ext = getExtension(pathOrUrl) - val map = MimeTypeMap.getSingleton() - return map.getMimeTypeFromExtension(ext) ?: "*/*" - } - - /** - * 获取格式化后的文件/目录创建或最后修改时间 - */ - @JvmOverloads - fun getDateTime(path: String, format: String = "yyyy年MM月dd日HH:mm"): String { - val file = File(path) - return getDateTime(file, format) - } - - /** - * 获取格式化后的文件/目录创建或最后修改时间 - */ - fun getDateTime(file: File, format: String): String { - val cal = Calendar.getInstance() - cal.timeInMillis = file.lastModified() - return SimpleDateFormat(format, Locale.PRC).format(cal.time) - } - - /** - * 比较两个文件的最后修改时间 - */ - fun compareLastModified(path1: String, path2: String): Int { - val stamp1 = File(path1).lastModified() - val stamp2 = File(path2).lastModified() - return if (stamp1 > stamp2) { - 1 - } else if (stamp1 < stamp2) { - -1 - } else { - 0 - } - } - - /** - * 创建多级别的目录 - */ - fun makeDirs(path: String): Boolean { - return makeDirs(File(path)) - } - - /** - * 创建多级别的目录 - */ - fun makeDirs(file: File): Boolean { - return file.mkdirs() - } - - class SortByExtension : Comparator { - - override fun compare(f1: File?, f2: File?): Int { - return if (f1 == null || f2 == null) { - if (f1 == null) { - -1 - } else { - 1 - } - } else { - if (f1.isDirectory && f2.isFile) { - -1 - } else if (f1.isFile && f2.isDirectory) { - 1 - } else { - f1.name.compareTo(f2.name, ignoreCase = true) - } - } - } - - } - - class SortByName : Comparator { - private var caseSensitive: Boolean = false - - constructor(caseSensitive: Boolean) { - this.caseSensitive = caseSensitive - } - - constructor() { - this.caseSensitive = false - } - - override fun compare(f1: File?, f2: File?): Int { - if (f1 == null || f2 == null) { - return if (f1 == null) { - -1 - } else { - 1 - } - } else { - return if (f1.isDirectory && f2.isFile) { - -1 - } else if (f1.isFile && f2.isDirectory) { - 1 - } else { - val s1 = f1.name - val s2 = f2.name - if (caseSensitive) { - s1.compareTo(s2) - } else { - s1.compareTo(s2, ignoreCase = true) - } - } - } - } - - } - - class SortBySize : Comparator { - - override fun compare(f1: File?, f2: File?): Int { - return if (f1 == null || f2 == null) { - if (f1 == null) { - -1 - } else { - 1 - } - } else { - if (f1.isDirectory && f2.isFile) { - -1 - } else if (f1.isFile && f2.isDirectory) { - 1 - } else { - if (f1.length() < f2.length()) { - -1 - } else { - 1 - } - } - } - } - - } - - class SortByTime : Comparator { - - override fun compare(f1: File?, f2: File?): Int { - return if (f1 == null || f2 == null) { - if (f1 == null) { - -1 - } else { - 1 - } - } else { - if (f1.isDirectory && f2.isFile) { - -1 - } else if (f1.isFile && f2.isDirectory) { - 1 - } else { - if (f1.lastModified() > f2.lastModified()) { - -1 - } else { - 1 - } - } - } - } - - } - -} - diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 0aa49ede7..d1cd2dea4 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 @@ -3,6 +3,7 @@ package io.legado.app.ui.main import android.os.Bundle import android.view.KeyEvent import android.view.MenuItem +import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter @@ -15,9 +16,10 @@ import io.legado.app.base.VMBaseActivity import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig +import io.legado.app.help.BookHelp +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 @@ -25,21 +27,19 @@ 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( - Pair(fragmentId[0], BookshelfFragment()), - Pair(fragmentId[1], ExploreFragment()), - Pair(fragmentId[2], RssFragment()), - Pair(fragmentId[3], MyFragment()) - ) + private val fragmentMap = hashMapOf() override fun onActivityCreated(savedInstanceState: Bundle?) { ATH.applyEdgeEffectColor(view_pager_main) @@ -48,6 +48,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 } @@ -57,9 +58,12 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), //自动更新书籍 if (AppConfig.autoRefreshBook) { view_pager_main.postDelayed({ - viewModel.upChapterList() + viewModel.upAllBookToc() }, 1000) } + view_pager_main.postDelayed({ + viewModel.postLoad() + }, 3000) } override fun onNavigationItemSelected(item: MenuItem): Boolean { @@ -72,12 +76,24 @@ 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) + if (getPrefInt(PreferKey.versionCode) != App.versionCode) { + putPrefInt(PreferKey.versionCode, App.versionCode) if (!BuildConfig.DEBUG) { val log = String(assets.open("updateLog.md").readBytes()) - TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000) + TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000, true) } } } @@ -103,19 +119,33 @@ 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 } } } return super.onKeyUp(keyCode, event) } + override fun onPause() { + super.onPause() + if (!BuildConfig.DEBUG) { + Backup.autoBack(this) + } + } + override fun onDestroy() { super.onDestroy() - ReadAloud.stop(this) + BookHelp.clearRemovedCache() } override fun observeLiveBus() { @@ -129,25 +159,56 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), view_pager_main.setCurrentItem(3, false) } } + observeEvent(PreferKey.threadCount) { + viewModel.upPool() + } } - private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : + private inner class TabFragmentPageAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + private fun getBookshelfFragment(): Fragment { + if (!fragmentMap.containsKey(0)) { + fragmentMap[0] = BookshelfFragment() + } + return fragmentMap.getValue(0) + } + + private fun getExploreFragment(): Fragment { + if (!fragmentMap.containsKey(1)) { + fragmentMap[1] = ExploreFragment() + } + return fragmentMap.getValue(1) + } + + private fun getRssFragment(): Fragment { + if (!fragmentMap.containsKey(2)) { + fragmentMap[2] = RssFragment() + } + return fragmentMap.getValue(2) + } + + private fun getMyFragment(): Fragment { + if (!fragmentMap.containsKey(3)) { + fragmentMap[3] = MyFragment() + } + return fragmentMap.getValue(3) + } + override fun getItemPosition(`object`: Any): Int { return POSITION_NONE } override fun getItem(position: Int): Fragment { return when (position) { - 0 -> fragmentMap.getValue(fragmentId[0]) - 1 -> fragmentMap.getValue(fragmentId[1]) + 0 -> getBookshelfFragment() + 1 -> getExploreFragment() 2 -> if (AppConfig.isShowRSS) { - fragmentMap.getValue(fragmentId[2]) + getRssFragment() } else { - fragmentMap.getValue(fragmentId[3]) + getMyFragment() } - else -> fragmentMap.getValue(fragmentId[3]) + else -> getMyFragment() } } @@ -155,6 +216,18 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), return if (AppConfig.isShowRSS) 4 else 3 } + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) as Fragment + val id = when (position) { + 2 -> if (AppConfig.isShowRSS) 2 else 3 + else -> position + } + if (!fragmentMap.containsKey(id)) { + fragmentMap[id] = fragment + } + return fragment + } + } } \ No newline at end of file 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..c9fc9066a 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,54 +3,115 @@ 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.AppConfig +import io.legado.app.help.DefaultValueHelp import io.legado.app.help.http.HttpHelper import io.legado.app.help.storage.Restore -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook +import io.legado.app.utils.FileUtils import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject import io.legado.app.utils.postEvent import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.delay +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArraySet +import java.util.concurrent.Executors class MainViewModel(application: Application) : BaseViewModel(application) { - val updateList = hashSetOf() + private var threadCount = AppConfig.threadCount + private var upTocPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + val updateList = CopyOnWriteArraySet() + private val bookMap = ConcurrentHashMap() - fun upChapterList() { + @Volatile + private var usePoolCount = 0 + + override fun onCleared() { + super.onCleared() + upTocPool.close() + } + + fun upPool() { + threadCount = AppConfig.threadCount + upTocPool.close() + upTocPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() + } + + fun upAllBookToc() { + execute { + upToc(App.db.bookDao().hasUpdateBooks) + } + } + + fun upToc(books: List) { execute { - App.db.bookDao().hasUpdateBooks.forEach { book -> - if (!updateList.contains(book.bookUrl)) { + books.filter { + it.origin != BookType.local && it.canUpdate + }.forEach { + bookMap[it.bookUrl] = it + } + for (i in 0 until threadCount) { + if (usePoolCount < threadCount) { + usePoolCount++ + updateToc() + } + } + } + } + + private fun updateToc() { + synchronized(this) { + bookMap.forEach { bookEntry -> + if (!updateList.contains(bookEntry.key)) { + val book = bookEntry.value + synchronized(this) { + updateList.add(book.bookUrl) + postEvent(EventBus.UP_BOOK, book.bookUrl) + } App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> - synchronized(this) { - updateList.add(book.bookUrl) - postEvent(EventBus.UP_BOOK, book.bookUrl) - } - WebBook(bookSource).getChapterList(book) + WebBook(bookSource).getChapterList(book, context = upTocPool) .timeout(300000) .onSuccess(IO) { - synchronized(this) { - updateList.remove(book.bookUrl) - postEvent(EventBus.UP_BOOK, book.bookUrl) - } App.db.bookDao().update(book) App.db.bookChapterDao().delByBook(book.bookUrl) App.db.bookChapterDao().insert(*it.toTypedArray()) } .onError { + it.printStackTrace() + } + .onFinally { synchronized(this) { + bookMap.remove(bookEntry.key) updateList.remove(book.bookUrl) postEvent(EventBus.UP_BOOK, book.bookUrl) + upNext() } - it.printStackTrace() } + } ?: synchronized(this) { + bookMap.remove(bookEntry.key) + updateList.remove(book.bookUrl) + postEvent(EventBus.UP_BOOK, book.bookUrl) + upNext() } + return } - delay(50) } } } + private fun upNext() { + if (bookMap.size > updateList.size) { + updateToc() + } else { + usePoolCount-- + } + } + fun initRss() { execute { val url = "https://gitee.com/alanskycn/yuedu/raw/master/JS/RSS/rssSource" @@ -67,4 +128,13 @@ class MainViewModel(application: Application) : BaseViewModel(application) { } } } + + fun postLoad() { + execute { + FileUtils.deleteFile(FileUtils.getPath(context.cacheDir, "Fonts")) + if (App.db.httpTTSDao().count == 0) { + DefaultValueHelp.initHttpTTS() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 55dca8d77..542f5a13f 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 @@ -2,16 +2,12 @@ package io.legado.app.ui.main.bookshelf import android.annotation.SuppressLint import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuItem -import android.view.View +import android.view.* import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import com.google.android.material.tabs.TabLayout import io.legado.app.App import io.legado.app.R @@ -27,10 +23,11 @@ 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.ui.book.arrange.ArrangeBookActivity -import io.legado.app.ui.book.download.DownloadActivity +import io.legado.app.ui.book.cache.CacheActivity 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.* @@ -39,6 +36,9 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.view_tab_layout.* import kotlinx.android.synthetic.main.view_title_bar.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jetbrains.anko.startActivity @@ -49,10 +49,13 @@ 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?) { setSupportToolbar(toolbar) @@ -68,21 +71,31 @@ 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.upToc(it) + } + } R.id.menu_bookshelf_layout -> configBookshelf() R.id.menu_group_manage -> GroupManageDialog() .show(childFragmentManager, "groupManageDialog") R.id.menu_add_local -> startActivity() R.id.menu_add_url -> addBookByUrl() R.id.menu_arrange_bookshelf -> startActivity( - Pair("groupId", selectedGroup.groupId), - Pair("groupName", selectedGroup.groupName) + Pair("groupId", selectedGroup?.groupId ?: 0), + Pair("groupName", selectedGroup?.groupName ?: 0) + ) + R.id.menu_download -> startActivity( + Pair("groupId", selectedGroup?.groupId ?: 0), + Pair("groupName", selectedGroup?.groupName ?: 0) ) - R.id.menu_download -> startActivity() } } - private val selectedGroup: BookGroup - get() = bookGroups[view_pager_bookshelf.currentItem] + private val selectedGroup: BookGroup? + get() = bookGroups.getOrNull(view_pager_bookshelf?.currentItem ?: 0) private fun initView() { ATH.applyEdgeEffectColor(view_pager_bookshelf) @@ -97,24 +110,51 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b private fun initBookGroupData() { bookGroupLiveData?.removeObservers(viewLifecycleOwner) bookGroupLiveData = App.db.bookGroupDao().liveDataAll() - bookGroupLiveData?.observe(viewLifecycleOwner, Observer { + bookGroupLiveData?.observe(viewLifecycleOwner, { viewModel.checkGroup(it) - synchronized(this) { - tab_layout.removeOnTabSelectedListener(this) - bookGroups.clear() - if (AppConfig.bookGroupAllShow) { - bookGroups.add(AppConst.bookGroupAll) + launch { + synchronized(this) { + tab_layout.removeOnTabSelectedListener(this@BookshelfFragment) } - if (AppConfig.bookGroupLocalShow) { - bookGroups.add(AppConst.bookGroupLocal) + var noGroupSize = 0 + withContext(IO) { + if (AppConfig.bookGroupNoneShow) { + noGroupSize = App.db.bookDao().noGroupSize + } } - if (AppConfig.bookGroupAudioShow) { - bookGroups.add(AppConst.bookGroupAudio) + synchronized(this@BookshelfFragment) { + bookGroups.clear() + if (AppConfig.bookGroupAllShow) { + bookGroups.add(AppConst.bookGroupAll) + } + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(AppConst.bookGroupLocal) + } + if (AppConfig.bookGroupAudioShow) { + bookGroups.add(AppConst.bookGroupAudio) + } + showGroupNone = if (noGroupSize > 0 && it.isNotEmpty()) { + bookGroups.add(AppConst.bookGroupNone) + true + } else { + false + } + bookGroups.addAll(it) + view_pager_bookshelf.adapter?.notifyDataSetChanged() + tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() + tab_layout.addOnTabSelectedListener(this@BookshelfFragment) } - bookGroups.addAll(it) - view_pager_bookshelf.adapter?.notifyDataSetChanged() - tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() - tab_layout.addOnTabSelectedListener(this) + } + }) + noGroupLiveData?.removeObservers(viewLifecycleOwner) + noGroupLiveData = App.db.bookDao().observeNoGroupSize() + noGroupLiveData?.observe(viewLifecycleOwner, { + if (it > 0 && !showGroupNone && AppConfig.bookGroupNoneShow) { + showGroupNone = true + upGroup() + } else if (it == 0 && showGroupNone) { + showGroupNone = false + upGroup() } }) } @@ -129,20 +169,36 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } override fun upGroup() { - synchronized(this) { - bookGroups.remove(AppConst.bookGroupAll) - bookGroups.remove(AppConst.bookGroupLocal) - bookGroups.remove(AppConst.bookGroupAudio) - if (AppConfig.bookGroupAudioShow) { - bookGroups.add(0, AppConst.bookGroupAudio) - } - if (AppConfig.bookGroupLocalShow) { - bookGroups.add(0, AppConst.bookGroupLocal) + launch { + var noGroupSize = 0 + withContext(IO) { + if (AppConfig.bookGroupNoneShow) { + noGroupSize = App.db.bookDao().noGroupSize + } } - if (AppConfig.bookGroupAllShow) { - bookGroups.add(0, AppConst.bookGroupAll) + synchronized(this@BookshelfFragment) { + bookGroups.remove(AppConst.bookGroupAll) + bookGroups.remove(AppConst.bookGroupLocal) + bookGroups.remove(AppConst.bookGroupAudio) + bookGroups.remove(AppConst.bookGroupNone) + showGroupNone = + if (noGroupSize > 0 && bookGroups.isNotEmpty()) { + bookGroups.add(0, AppConst.bookGroupNone) + true + } else { + false + } + if (AppConfig.bookGroupAudioShow) { + bookGroups.add(0, AppConst.bookGroupAudio) + } + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(0, AppConst.bookGroupLocal) + } + if (AppConfig.bookGroupAllShow) { + bookGroups.add(0, AppConst.bookGroupAll) + } + view_pager_bookshelf.adapter?.notifyDataSetChanged() } - view_pager_bookshelf.adapter?.notifyDataSetChanged() } } @@ -206,7 +262,11 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } } - private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : + fun gotoTop() { + fragmentMap[selectedGroup?.groupId]?.gotoTop() + } + + private inner class TabFragmentPageAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { override fun getPageTitle(position: Int): CharSequence? { @@ -231,5 +291,14 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b return bookGroups.size } + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) as BooksFragment + val group = bookGroups[position] + if (!fragmentMap.containsKey(group.groupId)) { + fragmentMap[group.groupId] = fragment + } + return fragment + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index dda342015..ae1287842 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -7,7 +7,7 @@ import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookSource -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import io.legado.app.utils.NetworkUtils import kotlinx.coroutines.Dispatchers.IO 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..7967b3843 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 { + bundle.keySet().forEach { 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..f0f15c27d 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,36 +26,33 @@ 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 { + tv_read.text = item.durChapterTitle + tv_last.text = item.latestChapterTitle + bundle.keySet().forEach { when (it) { "name" -> tv_name.text = item.name "author" -> tv_author.text = item.author - "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..8498b77a3 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,55 @@ 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", newItem.name) + } + if (oldItem.author != newItem.author) { + bundle.putString("author", newItem.author) + } + if (oldItem.durChapterTitle != newItem.durChapterTitle) { + bundle.putString("dur", newItem.durChapterTitle) + } + if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { + bundle.putString("last", newItem.latestChapterTitle) + } + if (oldItem.getDisplayCover() != newItem.getDisplayCover()) { + bundle.putString("cover", newItem.getDisplayCover()) + } + 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 + bundle.putBoolean("refresh", true) } + 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 d82480ca2..ebbe310e8 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 @@ -3,7 +3,6 @@ package io.legado.app.ui.main.bookshelf.books import android.os.Bundle import android.view.View import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager @@ -11,10 +10,12 @@ import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment +import io.legado.app.constant.AppConst import io.legado.app.constant.BookType import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey 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 @@ -44,14 +45,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) @@ -65,7 +66,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), refresh_layout.setColorSchemeColors(accentColor) refresh_layout.setOnRefreshListener { refresh_layout.isRefreshing = false - activityViewModel.upChapterList() + activityViewModel.upToc(booksAdapter.getItems()) } val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) if (bookshelfLayout == 0) { @@ -98,12 +99,13 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), private fun upRecyclerData() { bookshelfLiveData?.removeObservers(this) bookshelfLiveData = when (groupId) { - -1 -> App.db.bookDao().observeAll() - -2 -> App.db.bookDao().observeLocal() - -3 -> App.db.bookDao().observeAudio() + AppConst.bookGroupAll.groupId -> App.db.bookDao().observeAll() + AppConst.bookGroupLocal.groupId -> App.db.bookDao().observeLocal() + AppConst.bookGroupAudio.groupId -> App.db.bookDao().observeAudio() + AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } - bookshelfLiveData?.observe(this, Observer { list -> + bookshelfLiveData?.observe(this, { list -> val books = when (getPrefInt(PreferKey.bookshelfSort)) { 1 -> list.sortedByDescending { it.latestChapterTime } 2 -> list.sortedBy { it.name } @@ -116,6 +118,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 -> @@ -128,7 +142,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/explore/ExploreFragment.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt index c8f93fc64..6aa870ee7 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt @@ -7,13 +7,13 @@ import android.view.SubMenu import android.view.View 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 io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseFragment +import io.legado.app.constant.AppPattern import io.legado.app.data.entities.BookSource import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor @@ -93,10 +93,10 @@ class ExploreFragment : VMBaseFragment(R.layout.fragment_find_ private fun initGroupData() { liveGroup?.removeObservers(viewLifecycleOwner) liveGroup = App.db.bookSourceDao().liveGroupExplore() - liveGroup?.observe(viewLifecycleOwner, Observer { + liveGroup?.observe(viewLifecycleOwner, { groups.clear() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } upGroupsMenu() }) @@ -109,7 +109,7 @@ class ExploreFragment : VMBaseFragment(R.layout.fragment_find_ } else { App.db.bookSourceDao().liveExplore("%$key%") } - liveExplore?.observe(viewLifecycleOwner, Observer { + liveExplore?.observe(viewLifecycleOwner, { val diffResult = DiffUtil .calculateDiff(ExploreDiffCallBack(ArrayList(adapter.getItems()), it)) adapter.setItems(it) 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 f3087f5e7..e4bc131ba 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 @@ -7,17 +7,18 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment +import io.legado.app.base.BasePreferenceFragment import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey -import io.legado.app.help.channel +import io.legado.app.help.AppConfig import io.legado.app.lib.theme.ATH import io.legado.app.service.WebService import io.legado.app.ui.about.AboutActivity import io.legado.app.ui.about.DonateActivity +import io.legado.app.ui.about.ReadRecordActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.config.BackupRestoreUi import io.legado.app.ui.config.ConfigActivity @@ -65,7 +66,10 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. BackupRestoreUi.onActivityResult(requestCode, resultCode, data) } - class PreferenceFragment : PreferenceFragmentCompat(), + /** + * 配置 + */ + class PreferenceFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -85,7 +89,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. true } } - if (requireContext().channel == "google") { + if (AppConfig.isGooglePlay) { findPreference("aboutCategory") ?.removePreference(findPreference("donate")) } @@ -137,6 +141,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. "theme_setting" -> context?.startActivity( Pair("configType", ConfigViewModel.TYPE_THEME_CONFIG) ) + "readRecord" -> context?.startActivity() "donate" -> context?.startActivity() "about" -> context?.startActivity() } 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 b960041d8..82ccf867f 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 @@ -4,26 +4,30 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View -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.RssArticlesActivity +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) @@ -51,7 +55,7 @@ class RssFragment : BaseFragment(R.layout.fragment_rss), } private fun initData() { - App.db.rssSourceDao().liveEnabled().observe(viewLifecycleOwner, Observer { + App.db.rssSourceDao().liveEnabled().observe(viewLifecycleOwner, { if (it.isEmpty()) { getViewModelOfActivity(MainViewModel::class.java).initRss() } @@ -60,6 +64,18 @@ class RssFragment : BaseFragment(R.layout.fragment_rss), } override fun openRss(rssSource: RssSource) { - startActivity(Pair("url", rssSource.sourceUrl)) + 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/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt index df39b9a1d..bf9cb5409 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt @@ -11,17 +11,18 @@ import android.view.ViewGroup import android.widget.EditText import androidx.appcompat.widget.Toolbar import androidx.fragment.app.DialogFragment -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme +import io.legado.app.constant.AppPattern import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModelOfActivity @@ -54,22 +55,24 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + view.setBackgroundColor(backgroundColor) + tool_bar.setBackgroundColor(primaryColor) initData() } private fun initData() { tool_bar.title = getString(R.string.group_manage) tool_bar.inflateMenu(R.menu.group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter - App.db.replaceRuleDao().liveGroup().observe(viewLifecycleOwner, Observer { + App.db.replaceRuleDao().liveGroup().observe(viewLifecycleOwner, { val groups = linkedSetOf() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } adapter.setItems(groups.toList()) }) @@ -128,6 +131,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList) { with(holder.itemView) { + setBackgroundColor(context.backgroundColor) tv_group.text = item } } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 1f9057e35..1f65877f8 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -11,31 +11,34 @@ import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -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.AppPattern import io.legado.app.data.entities.ReplaceRule import io.legado.app.help.BookHelp -import io.legado.app.help.ItemTouchCallback +import io.legado.app.help.IntentDataHelp import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.ui.association.ImportReplaceRuleActivity import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_replace_rule.* import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.view_search.* +import org.jetbrains.anko.startActivity import org.jetbrains.anko.toast import java.io.File @@ -57,7 +60,6 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi private var dataInit = false override fun onActivityCreated(savedInstanceState: Bundle?) { - initUriScheme() initRecyclerView() initSearchView() initSelectActionView() @@ -76,29 +78,6 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi return super.onPrepareOptionsMenu(menu) } - private fun initUriScheme() { - intent.data?.let { - 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)){ - viewModel.importSource(url) { msg -> - title_bar.snackbar(msg) - } - } - else{ - viewModel.importSourceFromFilePath(url) { msg -> - title_bar.snackbar(msg) - } - } - } - else -> { - toast("格式不对") - } - } - } - } - private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) @@ -108,6 +87,13 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi val itemTouchCallback = ItemTouchCallback() itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.isCanDrag = true + val dragSelectTouchHelper: DragSelectTouchHelper = + DragSelectTouchHelper(adapter.initDragSelectTouchHelperCallback()).setSlideArea(16, 50) + dragSelectTouchHelper.attachToRecyclerView(recycler_view) + // When this page is opened, it is in selection mode + dragSelectTouchHelper.activeSlideSelect() + + // Note: need judge selection first, so add ItemTouchHelper after it. ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) } @@ -155,7 +141,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } else { App.db.replaceRuleDao().liveDataSearch(key) } - replaceRuleLiveData?.observe(this, Observer { + replaceRuleLiveData?.observe(this, { if (dataInit) { setResult(Activity.RESULT_OK) } @@ -168,10 +154,10 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } private fun observeGroupData() { - App.db.replaceRuleDao().liveGroup().observe(this, Observer { + App.db.replaceRuleDao().liveGroup().observe(this, { groups.clear() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } upGroupMenu() }) @@ -187,12 +173,10 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_local -> FilePicker - .selectFile( - this, - importRequestCode, - type = "text/*", - allowExtensions = arrayOf("txt", "json") - ) + .selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) + else -> if (item.groupId == R.id.replace_group) { + search_view.setQuery(item.title, true) + } } return super.onCompatOptionsItemSelected(item) } @@ -207,9 +191,9 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } private fun upGroupMenu() { - groupMenu?.removeGroup(R.id.source_group) + groupMenu?.removeGroup(R.id.replace_group) groups.map { - groupMenu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) + groupMenu?.add(R.id.replace_group, Menu.NONE, Menu.NONE, it) } } @@ -239,10 +223,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi cacheUrls.add(0, it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + startActivity("source" to it) } } cancelButton() @@ -261,10 +242,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { importRequestCode -> { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSource(File(currentPath).readText()) { msg -> - title_bar.snackbar(msg) - } + startActivity("filePath" to currentPath) } exportRequestCode -> viewModel.exportSelection( adapter.getSelection(), @@ -280,14 +258,11 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi data?.data?.let { uri -> try { uri.readText(this)?.let { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) - .show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + val dataKey = IntentDataHelp.putData(it) + startActivity("dataKey" to dataKey) } } catch (e: Exception) { - toast(e.localizedMessage ?: "ERROR") + toast("readTextError:${e.localizedMessage}") } } } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt index 4dd09980c..bcb29e87d 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt @@ -10,8 +10,9 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.ReplaceRule -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.theme.backgroundColor +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback import kotlinx.android.synthetic.main.item_replace_rule.view.* import org.jetbrains.anko.sdk27.listeners.onClick import java.util.* @@ -155,6 +156,32 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) : } } + fun initDragSelectTouchHelperCallback(): DragSelectTouchHelper.Callback { + return object : DragSelectTouchHelper.AdvanceCallback(Mode.ToggleAndReverse) { + override fun currentSelectedId(): MutableSet { + return selected + } + + override fun getItemId(position: Int): ReplaceRule { + return getItem(position)!! + } + + override fun updateSelectState(position: Int, isSelected: Boolean): Boolean { + getItem(position)?.let { + if (isSelected) { + selected.add(it) + } else { + selected.remove(it) + } + notifyItemChanged(position, bundleOf(Pair("selected", null))) + callBack.upCountView() + return true + } + return false + } + } + } + interface CallBack { fun update(vararg rule: ReplaceRule) fun delete(rule: ReplaceRule) diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt index ec75e78ed..afbbcdcf6 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt @@ -4,47 +4,16 @@ import android.app.Application import android.text.TextUtils import androidx.documentfile.provider.DocumentFile import io.legado.app.App -import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule -import io.legado.app.help.http.HttpHelper -import io.legado.app.help.storage.ImportOldData -import io.legado.app.utils.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.splitNotBlank +import io.legado.app.utils.writeText import org.jetbrains.anko.toast import java.io.File class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) { - fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) { - execute { - val file = File(path) - if (file.exists()) { - importSource(file.readText(), finally) - } else { - withContext(Dispatchers.Main) { - finally("打开文件出错") - } - } - }.onError { - finally(it.localizedMessage ?: "打开文件出错") - } - } - fun importSource(text: String, showMsg: (msg: String) -> Unit) { - execute { - if (text.isAbsUrl()) { - HttpHelper.simpleGet(text)?.let { - ImportOldData.importOldReplaceRule(it) - } - } else { - ImportOldData.importOldReplaceRule(text) - } - }.onError { - showMsg(it.localizedMessage ?: "ERROR") - }.onSuccess { - showMsg(context.getString(R.string.success)) - } - } fun update(vararg rule: ReplaceRule) { execute { 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..4bbfb44be 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,31 @@ 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.Theme +import io.legado.app.base.BaseDialogFragment +import io.legado.app.constant.AppConst import io.legado.app.data.entities.ReplaceRule +import io.legado.app.lib.theme.primaryColor +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 { +class ReplaceEditDialog : BaseDialogFragment(), + Toolbar.OnMenuItemClickListener, + KeyboardToolPop.CallBack { companion object { @@ -28,25 +33,26 @@ class ReplaceEditDialog : DialogFragment(), fragmentManager: FragmentManager, id: Long = -1, pattern: String? = null, - isRegex: Boolean = false + isRegex: Boolean = false, + scope: String? = null ) { val dialog = ReplaceEditDialog() val bundle = Bundle() bundle.putLong("id", id) bundle.putString("pattern", pattern) bundle.putBoolean("isRegex", isRegex) + bundle.putString("scope", scope) dialog.arguments = bundle - dialog.show(fragmentManager, "editReplace") + dialog.show(fragmentManager, this::class.simpleName) } } 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( @@ -58,23 +64,32 @@ class ReplaceEditDialog : DialogFragment(), return inflater.inflate(R.layout.dialog_replace_edit, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) + mSoftKeyboardTool = KeyboardToolPop(requireContext(), AppConst.keyboardToolChars, this) tool_bar.inflateMenu(R.menu.replace_edit) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) - viewModel.replaceRuleData.observe(viewLifecycleOwner, Observer { + viewModel.replaceRuleData.observe(viewLifecycleOwner, { upReplaceView(it) }) 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 +126,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/replacerule/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt index e4a0d443c..1308b64ec 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt @@ -20,12 +20,16 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application replaceRuleData.postValue(it) } } else { - bundle.getString("pattern")?.let { pattern -> - val isRegex = bundle.getBoolean("isRegex") - replaceRuleData.postValue( - ReplaceRule(name = pattern, pattern = pattern, isRegex = isRegex) - ) - } + val pattern = bundle.getString("pattern") ?: "" + val isRegex = bundle.getBoolean("isRegex") + val scope = bundle.getString("scope") + val rule = ReplaceRule( + name = pattern, + pattern = pattern, + isRegex = isRegex, + scope = scope + ) + replaceRuleData.postValue(rule) } } } 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..13f3e59cb 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,46 +19,51 @@ 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 } else { - tv_title.textColorResource = R.color.tv_text_default + tv_title.textColorResource = R.color.primaryText } } } @@ -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/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt similarity index 50% rename from app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt rename to app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt index 31ac9cdda..f9b684592 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt @@ -1,83 +1,75 @@ package io.legado.app.ui.rss.article import android.os.Bundle -import android.view.Menu -import android.view.MenuItem +import android.view.View import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R -import io.legado.app.base.VMBaseActivity +import io.legado.app.base.VMBaseFragment import io.legado.app.data.entities.RssArticle import io.legado.app.lib.theme.ATH import io.legado.app.ui.rss.read.ReadRssActivity -import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.getViewModel -import kotlinx.android.synthetic.main.activity_rss_artivles.* +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.startActivity +import kotlinx.android.synthetic.main.fragment_rss_articles.* import kotlinx.android.synthetic.main.view_load_more.view.* import kotlinx.android.synthetic.main.view_refresh_recycler.* -import org.jetbrains.anko.startActivity -import org.jetbrains.anko.startActivityForResult -class RssArticlesActivity : VMBaseActivity(R.layout.activity_rss_artivles), - RssArticlesViewModel.CallBack, +class RssArticlesFragment : VMBaseFragment(R.layout.fragment_rss_articles), RssArticlesAdapter.CallBack { + companion object { + fun create(sortName: String, sortUrl: String): RssArticlesFragment { + return RssArticlesFragment().apply { + val bundle = Bundle() + bundle.putString("sortName", sortName) + bundle.putString("sortUrl", sortUrl) + arguments = bundle + } + } + } + + private val activityViewModel: RssSortViewModel + get() = getViewModelOfActivity(RssSortViewModel::class.java) override val viewModel: RssArticlesViewModel get() = getViewModel(RssArticlesViewModel::class.java) - - override lateinit var adapter: RssArticlesAdapter - private val editSource = 12319 + lateinit var adapter: RssArticlesAdapter private lateinit var loadMoreView: LoadMoreView private var rssArticlesData: LiveData>? = null + override val isGridLayout: Boolean + get() = activityViewModel.isGridLayout - override fun onActivityCreated(savedInstanceState: Bundle?) { - viewModel.callBack = this - viewModel.titleLiveData.observe(this, Observer { - title_bar.title = it - }) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + viewModel.init(arguments) initView() - viewModel.initData(intent) { - initData() - refresh_recycler_view.startLoading() - } - } - - override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.rss_articles, menu) - return super.onCompatCreateOptionsMenu(menu) - } - - override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let { - startActivityForResult(editSource, Pair("data", it)) - } - R.id.menu_clear -> { - viewModel.url?.let { - refresh_progress_bar.isAutoLoading = true - viewModel.clearArticles() - } - } - } - return super.onCompatOptionsItemSelected(item) + refresh_recycler_view.startLoading() + initView() + initData() } private fun initView() { ATH.applyEdgeEffectColor(recycler_view) - recycler_view.layoutManager = LinearLayoutManager(this) - recycler_view.addItemDecoration(VerticalDivider(this)) - adapter = RssArticlesAdapter(this, this) + recycler_view.layoutManager = 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(this) + loadMoreView = LoadMoreView(requireContext()) adapter.addFooterView(loadMoreView) refresh_recycler_view.onRefreshStart = { - viewModel.url?.let { - viewModel.loadContent() + activityViewModel.rssSource?.let { + viewModel.loadContent(it) } } recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { @@ -91,10 +83,10 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi } private fun initData() { - viewModel.url?.let { + activityViewModel.url?.let { rssArticlesData?.removeObservers(this) - rssArticlesData = App.db.rssArticleDao().liveByOrigin(it) - rssArticlesData?.observe(this, Observer { list -> + rssArticlesData = App.db.rssArticleDao().liveByOriginSort(it, viewModel.sortName) + rssArticlesData?.observe(viewLifecycleOwner, { list -> adapter.setItems(list) }) } @@ -104,21 +96,25 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi if (viewModel.isLoading) return if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) { loadMoreView.rotate_loading.show() - viewModel.loadMore() + activityViewModel.rssSource?.let { + viewModel.loadMore(it) + } } } - override fun loadFinally(hasMore: Boolean) { - refresh_recycler_view.stopLoading() - if (hasMore) { - loadMoreView.startLoad() - } else { - loadMoreView.noMore() - } + override fun observeLiveBus() { + viewModel.loadFinally.observe(viewLifecycleOwner, { + refresh_recycler_view.stopLoading() + if (it) { + loadMoreView.startLoad() + } else { + loadMoreView.noMore() + } + }) } override fun readRss(rssArticle: RssArticle) { - viewModel.read(rssArticle) + activityViewModel.read(rssArticle) startActivity( Pair("title", rssArticle.title), Pair("origin", rssArticle.origin), diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt index 61a2b14be..44f125ef2 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt @@ -1,124 +1,97 @@ package io.legado.app.ui.rss.article import android.app.Application -import android.content.Intent +import android.os.Bundle import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssArticle -import io.legado.app.data.entities.RssReadRecord import io.legado.app.data.entities.RssSource -import io.legado.app.model.Rss -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main +import io.legado.app.model.rss.Rss +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext - class RssArticlesViewModel(application: Application) : BaseViewModel(application) { - var callBack: CallBack? = null - var url: String? = null - var rssSource: RssSource? = null - val titleLiveData = MutableLiveData() + val loadFinally = MutableLiveData() var isLoading = true var order = System.currentTimeMillis() private var nextPageUrl: String? = null + var sortName: String = "" + var sortUrl: String = "" + var page = 1 - fun initData(intent: Intent, finally: () -> Unit) { - execute { - url = intent.getStringExtra("url") - url?.let { url -> - rssSource = App.db.rssSourceDao().getByKey(url) - rssSource?.let { - titleLiveData.postValue(it.sourceName) - } ?: let { - rssSource = RssSource(sourceUrl = url) - } - } - }.onFinally { - finally() + fun init(bundle: Bundle?) { + bundle?.let { + sortName = it.getString("sortName") ?: "" + sortUrl = it.getString("sortUrl") ?: "" } } - fun loadContent() { + fun loadContent(rssSource: RssSource) { isLoading = true - rssSource?.let { rssSource -> - Rss.getArticles(rssSource, null) - .onSuccess(IO) { - nextPageUrl = it.nextPageUrl - it.articles.let { list -> - list.forEach { rssArticle -> - rssArticle.order = order-- - } - App.db.rssArticleDao().insert(*list.toTypedArray()) - if (!rssSource.ruleNextPage.isNullOrEmpty()) { - App.db.rssArticleDao().clearOld(url!!, order) - withContext(Main) { - callBack?.loadFinally(true) - } - } else { - withContext(Main) { - callBack?.loadFinally(false) - } + page = 1 + Rss.getArticles(sortName, sortUrl, rssSource, page) + .onSuccess(Dispatchers.IO) { + nextPageUrl = it.nextPageUrl + it.articles.let { list -> + list.forEach { rssArticle -> + rssArticle.order = order-- + } + App.db.rssArticleDao().insert(*list.toTypedArray()) + if (!rssSource.ruleNextPage.isNullOrEmpty()) { + App.db.rssArticleDao().clearOld(rssSource.sourceUrl, sortName, order) + loadFinally.postValue(true) + } else { + withContext(Dispatchers.Main) { + loadFinally.postValue(false) } - isLoading = false - } - }.onError { - toast(it.localizedMessage) + isLoading = false } - } + }.onError { + it.printStackTrace() + toast(it.localizedMessage) + } } - fun loadMore() { + fun loadMore(rssSource: RssSource) { isLoading = true - val source = rssSource + page++ val pageUrl = nextPageUrl - if (source != null && !pageUrl.isNullOrEmpty()) { - Rss.getArticles(source, pageUrl) - .onSuccess(IO) { + if (!pageUrl.isNullOrEmpty()) { + Rss.getArticles(sortName, pageUrl, rssSource, page) + .onSuccess(Dispatchers.IO) { nextPageUrl = it.nextPageUrl - it.articles.let { list -> - if (list.isEmpty()) { - callBack?.loadFinally(false) - return@let - } - callBack?.adapter?.getItems()?.let { adapterItems -> - if (adapterItems.contains(list.first())) { - callBack?.loadFinally(false) - } else { - list.forEach { rssArticle -> - rssArticle.order = order-- - } - App.db.rssArticleDao().insert(*list.toTypedArray()) - } - } - } - isLoading = false + loadMoreSuccess(it.articles) + } + .onError { + it.printStackTrace() + loadFinally.postValue(false) } } else { - callBack?.loadFinally(false) - } - } - - fun read(rssArticle: RssArticle) { - execute { - App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link)) + loadFinally.postValue(false) } } - fun clearArticles() { - execute { - url?.let { - App.db.rssArticleDao().delete(it) + 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()) } - order = System.currentTimeMillis() - }.onSuccess { - loadContent() } + isLoading = false } - interface CallBack { - var adapter: RssArticlesAdapter - fun loadFinally(hasMore: Boolean) - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt new file mode 100644 index 000000000..307374f9f --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt @@ -0,0 +1,107 @@ +package io.legado.app.ui.rss.article + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.ui.rss.source.edit.RssSourceEditActivity +import io.legado.app.utils.getViewModel +import io.legado.app.utils.gone +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.activity_rss_artivles.* +import org.jetbrains.anko.startActivityForResult + +class RssSortActivity : VMBaseActivity(R.layout.activity_rss_artivles) { + + override val viewModel: RssSortViewModel + get() = getViewModel(RssSortViewModel::class.java) + private val editSource = 12319 + private val fragments = linkedMapOf() + private lateinit var adapter: TabFragmentPageAdapter + + override fun onActivityCreated(savedInstanceState: Bundle?) { + adapter = TabFragmentPageAdapter(supportFragmentManager) + tab_layout.setupWithViewPager(view_pager) + view_pager.adapter = adapter + viewModel.titleLiveData.observe(this, { + title_bar.title = it + }) + viewModel.initData(intent) { + upFragments() + } + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.rss_articles, menu) + return super.onCompatCreateOptionsMenu(menu) + } + + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let { + startActivityForResult(editSource, Pair("data", it)) + } + R.id.menu_clear -> { + viewModel.url?.let { + viewModel.clearArticles() + } + } + R.id.menu_switch_layout -> { + viewModel.switchLayout() + upFragments() + } + } + return super.onCompatOptionsItemSelected(item) + } + + private fun upFragments() { + fragments.clear() + viewModel.rssSource?.sortUrls()?.forEach { + fragments[it.key] = RssArticlesFragment.create(it.key, it.value) + } + if (fragments.size == 1) { + tab_layout.gone() + } else { + tab_layout.visible() + } + adapter.notifyDataSetChanged() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + editSource -> if (resultCode == Activity.RESULT_OK) { + viewModel.initData(intent) { + upFragments() + } + } + } + } + + private inner class TabFragmentPageAdapter(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) + } + + override fun getItem(position: Int): Fragment { + return fragments.values.elementAt(position) + } + + override fun getCount(): Int { + return fragments.size + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt new file mode 100644 index 000000000..135064bce --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt @@ -0,0 +1,73 @@ +package io.legado.app.ui.rss.article + +import android.app.Application +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.RssArticle +import io.legado.app.data.entities.RssReadRecord +import io.legado.app.data.entities.RssSource + + +class RssSortViewModel(application: Application) : BaseViewModel(application) { + var url: String? = null + var rssSource: RssSource? = null + val titleLiveData = MutableLiveData() + var order = System.currentTimeMillis() + 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 { + url = intent.getStringExtra("url") + url?.let { url -> + rssSource = App.db.rssSourceDao().getByKey(url) + rssSource?.let { + titleLiveData.postValue(it.sourceName) + } ?: let { + rssSource = RssSource(sourceUrl = url) + } + } + }.onFinally { + finally() + } + } + + fun 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)) + } + } + + fun clearArticles() { + execute { + url?.let { + App.db.rssArticleDao().delete(it) + } + order = System.currentTimeMillis() + }.onSuccess { + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt index f50329296..09f6d1549 100644 --- a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt @@ -2,7 +2,6 @@ package io.legado.app.ui.rss.favorites import android.os.Bundle import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R @@ -37,7 +36,7 @@ class RssFavoritesActivity : BaseActivity(R.layout.activity_rss_favorites), private fun initData() { liveData?.removeObservers(this) liveData = App.db.rssStarDao().liveAll() - liveData?.observe(this, Observer { + liveData?.observe(this, { adapter.setItems(it) }) } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 73c55961b..c66cad69f 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -1,30 +1,33 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint +import android.app.DownloadManager import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.net.Uri import android.os.Bundle +import android.os.Environment import android.view.* import android.webkit.* import androidx.core.view.size -import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.service.help.Download import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_read.* import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils +import org.jetbrains.anko.downloadManager import org.jetbrains.anko.share -import org.jetbrains.anko.toast import org.jsoup.Jsoup -class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), +class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read, false), FileChooserDialog.CallBack, ReadRssViewModel.CallBack { @@ -140,22 +143,54 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } return@setOnLongClickListener false } + web_view.setDownloadListener { url, _, contentDisposition, _, _ -> + val fileName = URLUtil.guessFileName(url, contentDisposition, null) + ll_view.longSnackbar(fileName, getString(R.string.action_download)) { + // 指定下载地址 + val request = DownloadManager.Request(Uri.parse(url)) + // 允许媒体扫描,根据下载的文件类型被加入相册、音乐等媒体库 + @Suppress("DEPRECATION") + request.allowScanningByMediaScanner() + // 设置通知的显示类型,下载进行时和完成后显示通知 + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + // 允许在计费流量下下载 + request.setAllowedOverMetered(false) + // 允许该记录在下载管理界面可见 + @Suppress("DEPRECATION") + request.setVisibleInDownloadsUi(false) + // 允许漫游时下载 + request.setAllowedOverRoaming(true) + // 允许下载的网路类型 + request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) + // 设置下载文件保存的路径和文件名 + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName) + // 添加一个下载任务 + val downloadId = downloadManager.enqueue(request) + Download.start(this, downloadId, fileName) + } + } } private fun saveImage() { - FilePicker.selectFolder(this, savePathRequestCode, getString(R.string.save_image)) { - val path = ACache.get(this).getAsString(imagePathKey) - if (path.isNullOrEmpty()) { - toast(R.string.no_default_path) - } else { - viewModel.saveImage(webPic, path) - } + val default = arrayListOf() + val path = ACache.get(this).getAsString(imagePathKey) + if (!path.isNullOrEmpty()) { + default.add(path) + } + FilePicker.selectFolder( + this, + savePathRequestCode, + getString(R.string.save_image), + default + ) { + viewModel.saveImage(webPic, it) } } @SuppressLint("SetJavaScriptEnabled") private fun initLiveData() { - viewModel.contentLiveData.observe(this, Observer { content -> + viewModel.contentLiveData.observe(this, { content -> viewModel.rssArticle?.let { upJavaScriptEnable() val url = NetworkUtils.getAbsoluteURL(it.origin, it.link) @@ -179,7 +214,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } } }) - viewModel.urlLiveData.observe(this, Observer { + viewModel.urlLiveData.observe(this, { upJavaScriptEnable() web_view.loadUrl(it.url, it.headerMap) }) @@ -252,6 +287,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r web_view.settings.javaScriptEnabled = true web_view.evaluateJavascript("document.documentElement.outerHTML") { val html = StringEscapeUtils.unescapeJson(it) + .replace("^\"|\"$".toRegex(), "") Jsoup.parse(html).text() viewModel.readAloud(Jsoup.parse(html).textArray()) } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index f5b84642f..11b4ba940 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 @@ -17,7 +17,7 @@ import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssStar import io.legado.app.help.http.HttpHelper -import io.legado.app.model.Rss +import io.legado.app.model.rss.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.DocumentUtils import io.legado.app.utils.FileUtils @@ -131,24 +131,35 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), private suspend fun webData2bitmap(data: String): ByteArray? { return if (URLUtil.isValidUrl(data)) { - HttpHelper.simpleGetByteAsync(data) + HttpHelper.simpleGetBytesAsync(data) } else { Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) } } 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 944135b45..34b795a2b 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 @@ -1,9 +1,6 @@ package io.legado.app.ui.rss.source.edit import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.graphics.Rect import android.os.Bundle @@ -23,10 +20,7 @@ import io.legado.app.lib.theme.ATH import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.GSON -import io.legado.app.utils.applyTint -import io.legado.app.utils.getViewModel -import io.legado.app.utils.shareWithQr +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_source_edit.* import org.jetbrains.anko.* import kotlin.math.abs @@ -97,20 +91,11 @@ class RssSourceEditActivity : } } } - R.id.menu_copy_source -> { - GSON.toJson(getRssSource())?.let { sourceStr -> - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr)) - } - } + R.id.menu_copy_source -> sendToClip(GSON.toJson(getRssSource())) R.id.menu_qr_code_camera -> startActivityForResult(qrRequestCode) R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } - R.id.menu_share_str -> GSON.toJson(getRssSource())?.let { sourceStr -> - share(sourceStr) - } - R.id.menu_share_qr -> GSON.toJson(getRssSource())?.let { sourceStr -> - shareWithQr(getString(R.string.share_rss_source), sourceStr) - } + R.id.menu_share_str -> share(GSON.toJson(getRssSource())) + R.id.menu_share_qr -> shareWithQr(getString(R.string.share_rss_source), GSON.toJson(getRssSource())) } return super.onCompatOptionsItemSelected(item) } @@ -135,6 +120,7 @@ class RssSourceEditActivity : add(EditEntity("sourceUrl", rssSource?.sourceUrl, R.string.source_url)) add(EditEntity("sourceIcon", rssSource?.sourceIcon, R.string.source_icon)) add(EditEntity("sourceGroup", rssSource?.sourceGroup, R.string.source_group)) + add(EditEntity("sortUrl", rssSource?.sortUrl, R.string.sort_url)) add(EditEntity("ruleArticles", rssSource?.ruleArticles, R.string.r_articles)) add(EditEntity("ruleNextPage", rssSource?.ruleNextPage, R.string.r_next)) add(EditEntity("ruleTitle", rssSource?.ruleTitle, R.string.r_title)) @@ -143,6 +129,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 @@ -159,6 +146,7 @@ class RssSourceEditActivity : "sourceUrl" -> source.sourceUrl = it.value ?: "" "sourceIcon" -> source.sourceIcon = it.value ?: "" "sourceGroup" -> source.sourceGroup = it.value + "sortUrl" -> source.sortUrl = it.value "ruleArticles" -> source.ruleArticles = it.value "ruleNextPage" -> source.ruleNextPage = it.value "ruleTitle" -> source.ruleTitle = it.value @@ -167,6 +155,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 } } @@ -181,7 +170,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) { @@ -196,6 +185,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/edit/RssSourceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt index eb9c4e034..88a9a78dd 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt @@ -1,14 +1,13 @@ package io.legado.app.ui.rss.source.edit import android.app.Application -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssSource import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject +import io.legado.app.utils.getClipText import kotlinx.coroutines.Dispatchers class RssSourceEditViewModel(application: Application) : BaseViewModel(application) { @@ -53,12 +52,8 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati fun pasteSource(onSuccess: (source: RssSource) -> Unit) { execute(context = Dispatchers.Main) { var source: RssSource? = null - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? - clipboard?.primaryClip?.let { - if (it.itemCount > 0) { - val json = it.getItemAt(0).text.toString().trim() - source = GSON.fromJsonObject(json) - } + context.getClipText()?.let { json -> + source = GSON.fromJsonObject(json) } source }.onError { diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/DiffCallBack.kt index a623d55be..cb539e803 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/DiffCallBack.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.rss.source.manage +import android.os.Bundle import androidx.recyclerview.widget.DiffUtil import io.legado.app.data.entities.RssSource @@ -33,10 +34,19 @@ class DiffCallBack( override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - return when { - oldItem.sourceName == newItem.sourceName - && oldItem.enabled != newItem.enabled -> 2 - else -> null + val payload = Bundle() + if (oldItem.sourceName != newItem.sourceName) { + payload.putString("name", newItem.sourceName) } + if (oldItem.sourceGroup != newItem.sourceGroup) { + payload.putString("group", newItem.sourceGroup) + } + if (oldItem.enabled != newItem.enabled) { + payload.putBoolean("enabled", newItem.enabled) + } + if (payload.isEmpty) { + return null + } + return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt index ce2313f5a..48180951f 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt @@ -10,19 +10,19 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Theme +import io.legado.app.constant.AppPattern import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_edit_text.view.* @@ -30,7 +30,7 @@ import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* import org.jetbrains.anko.sdk27.listeners.onClick -class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { +class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { private lateinit var viewModel: RssSourceViewModel private lateinit var adapter: GroupAdapter @@ -50,15 +50,11 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { return inflater.inflate(R.layout.dialog_recycler_view, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initData() - } - - private fun initData() { + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) tool_bar.title = getString(R.string.group_manage) tool_bar.inflateMenu(R.menu.group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) @@ -67,10 +63,10 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { tv_ok.setTextColor(requireContext().accentColor) tv_ok.visible() tv_ok.onClick { dismiss() } - App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { + App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, { val groups = linkedSetOf() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } adapter.setItems(groups.toList()) }) 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..05885d183 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 @@ -11,24 +11,26 @@ import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -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.AppPattern import io.legado.app.data.entities.RssSource -import io.legado.app.help.ItemTouchCallback +import io.legado.app.help.IntentDataHelp import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.ui.association.ImportRssSourceActivity import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* @@ -60,7 +62,6 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r private var groupMenu: SubMenu? = null override fun onActivityCreated(savedInstanceState: Bundle?) { - initUriScheme() initRecyclerView() initSearchView() initLiveDataGroup() @@ -83,19 +84,14 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r when (item.itemId) { R.id.menu_add -> startActivity() R.id.menu_import_source_local -> FilePicker - .selectFile( - this, - importRequestCode, - type = "text/*", - allowExtensions = arrayOf("txt", "json") - ) + .selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) R.id.menu_group_manage -> GroupManageDialog() .show(supportFragmentManager, "rssGroupManage") - } - if (item.groupId == R.id.source_group) { - search_view.setQuery(item.title, true) + else -> if (item.groupId == R.id.source_group) { + search_view.setQuery(item.title, true) + } } return super.onCompatOptionsItemSelected(item) } @@ -106,35 +102,12 @@ 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 } - private fun initUriScheme() { - intent.data?.let { - 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)){ - viewModel.importSource(url) { msg -> - title_bar.snackbar(msg) - } - } - else{ - viewModel.importSourceFromFilePath(url) { msg -> - title_bar.snackbar(msg) - } - } - } - else -> { - toast("格式不对") - } - } - } - } - private fun initRecyclerView() { ATH.applyEdgeEffectColor(recycler_view) recycler_view.layoutManager = LinearLayoutManager(this) @@ -144,6 +117,13 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r val itemTouchCallback = ItemTouchCallback() itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.isCanDrag = true + val dragSelectTouchHelper: DragSelectTouchHelper = + DragSelectTouchHelper(adapter.initDragSelectTouchHelperCallback()).setSlideArea(16, 50) + dragSelectTouchHelper.attachToRecyclerView(recycler_view) + // When this page is opened, it is in selection mode + dragSelectTouchHelper.activeSlideSelect() + + // Note: need judge selection first, so add ItemTouchHelper after it. ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) } @@ -165,10 +145,10 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r } private fun initLiveDataGroup() { - App.db.rssSourceDao().liveGroup().observe(this, Observer { + App.db.rssSourceDao().liveGroup().observe(this, { groups.clear() it.map { group -> - groups.addAll(group.splitNotBlank(",", ";")) + groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) } upGroupMenu() }) @@ -204,7 +184,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) } @@ -218,7 +198,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r } else { App.db.rssSourceDao().liveSearch("%$key%") } - sourceLiveData?.observe(this, Observer { + sourceLiveData?.observe(this, { val diffResult = DiffUtil .calculateDiff(DiffCallBack(adapter.getItems(), it)) adapter.setItems(it, diffResult) @@ -256,10 +236,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r cacheUrls.add(0, it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + startActivity("source" to it) } } cancelButton() @@ -269,10 +246,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { importRequestCode -> { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() - viewModel.importSourceFromFilePath(currentPath) { msg -> - title_bar.snackbar(msg) - } + startActivity("filePath" to currentPath) } exportRequestCode -> viewModel.exportSelection( adapter.getSelection(), @@ -288,23 +262,17 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r data?.data?.let { uri -> try { uri.readText(this)?.let { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) - .show() - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + val dataKey = IntentDataHelp.putData(it) + startActivity("dataKey" to dataKey) } } catch (e: Exception) { - toast(e.localizedMessage ?: "ERROR") + toast("readTextError:${e.localizedMessage}") } } } qrRequestCode -> if (resultCode == RESULT_OK) { data?.getStringExtra("result")?.let { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) - viewModel.importSource(it) { msg -> - title_bar.snackbar(msg) - } + startActivity("source" to it) } } exportRequestCode -> if (resultCode == RESULT_OK) { @@ -339,6 +307,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..2652503b3 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 @@ -1,15 +1,18 @@ package io.legado.app.ui.rss.source.manage import android.content.Context +import android.os.Bundle import android.view.View import android.widget.PopupMenu +import androidx.core.os.bundleOf import androidx.recyclerview.widget.RecyclerView import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.RssSource -import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.theme.backgroundColor +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback import kotlinx.android.synthetic.main.item_rss_source.view.* import org.jetbrains.anko.sdk27.listeners.onClick import java.util.* @@ -24,7 +27,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : getItems().forEach { selected.add(it) } - notifyItemRangeChanged(0, itemCount, 1) + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) callBack.upCountView() } @@ -36,23 +39,24 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : selected.add(it) } } - notifyItemRangeChanged(0, itemCount, 1) + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) 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) { with(holder.itemView) { - if (payloads.isEmpty()) { + val bundle = payloads.getOrNull(0) as? Bundle + if (bundle == null) { this.setBackgroundColor(context.backgroundColor) if (item.sourceGroup.isNullOrEmpty()) { cb_source.text = item.sourceName @@ -63,9 +67,18 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : swt_enabled.isChecked = item.enabled cb_source.isChecked = selected.contains(item) } else { - when (payloads[0]) { - 1 -> cb_source.isChecked = selected.contains(item) - 2 -> swt_enabled.isChecked = item.enabled + bundle.keySet().map { + when (it) { + "name", "group" -> + if (item.sourceGroup.isNullOrEmpty()) { + cb_source.text = item.sourceName + } else { + cb_source.text = + String.format("%s (%s)", item.sourceName, item.sourceGroup) + } + "selected" -> cb_source.isChecked = selected.contains(item) + "enabled" -> swt_enabled.isChecked = item.enabled + } } } } @@ -111,6 +124,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 @@ -146,11 +160,38 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) : } } + fun initDragSelectTouchHelperCallback(): DragSelectTouchHelper.Callback { + return object : DragSelectTouchHelper.AdvanceCallback(Mode.ToggleAndReverse) { + override fun currentSelectedId(): MutableSet { + return selected + } + + override fun getItemId(position: Int): RssSource { + return getItem(position)!! + } + + override fun updateSelectState(position: Int, isSelected: Boolean): Boolean { + getItem(position)?.let { + if (isSelected) { + selected.add(it) + } else { + selected.remove(it) + } + notifyItemChanged(position, bundleOf(Pair("selected", null))) + callBack.upCountView() + return true + } + return false + } + } + } + interface CallBack { fun del(source: RssSource) 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 300d7468c..5c72b503d 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 @@ -3,23 +3,35 @@ package io.legado.app.ui.rss.source.manage import android.app.Application import android.text.TextUtils import androidx.documentfile.provider.DocumentFile -import com.jayway.jsonpath.JsonPath 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.http.HttpHelper -import io.legado.app.help.storage.Restore.jsonPath -import io.legado.app.utils.* +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.splitNotBlank +import io.legado.app.utils.writeText import org.jetbrains.anko.toast 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) } } @@ -41,7 +53,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelection(sources: LinkedHashSet) { + fun enableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -51,7 +63,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun disableSelection(sources: LinkedHashSet) { + fun disableSelection(sources: List) { execute { val list = arrayListOf() sources.forEach { @@ -61,13 +73,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") @@ -79,7 +91,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() @@ -132,78 +144,4 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) { - execute { - val file = File(path) - if (file.exists()) { - GSON.fromJsonArray(file.readText())?.let { - App.db.rssSourceDao().insert(*it.toTypedArray()) - } - } - }.onSuccess { - finally.invoke(context.getString(R.string.success)) - } - } - - fun importSource(text: String, finally: (msg: String) -> Unit) { - execute { - val text1 = text.trim() - when { - text1.isJsonObject() -> { - val json = JsonPath.parse(text1) - val urls = json.read>("$.sourceUrls") - var count = 0 - if (!urls.isNullOrEmpty()) { - urls.forEach { - count += importSourceUrl(it) - } - } else { - GSON.fromJsonArray(text1)?.let { - App.db.rssSourceDao().insert(*it.toTypedArray()) - count = 1 - } - } - "导入${count}条" - } - text1.isJsonArray() -> { - val rssSources = mutableListOf() - val items: List> = jsonPath.parse(text1).read("$") - for (item in items) { - val jsonItem = jsonPath.parse(item) - GSON.fromJsonObject(jsonItem.jsonString())?.let { - rssSources.add(it) - } - } - App.db.rssSourceDao().insert(*rssSources.toTypedArray()) - "导入${rssSources.size}条" - } - text1.isAbsUrl() -> { - val count = importSourceUrl(text1) - "导入${count}条" - } - else -> "格式不对" - } - }.onError { - finally(it.localizedMessage ?: "") - }.onSuccess { - finally(it) - } - } - - private fun importSourceUrl(url: String): Int { - HttpHelper.simpleGet(url)?.let { body -> - val sources = mutableListOf() - val items: List> = jsonPath.parse(body).read("$") - for (item in items) { - val jsonItem = jsonPath.parse(item) - GSON.fromJsonObject(jsonItem.jsonString())?.let { source -> - sources.add(source) - } - } - App.db.rssSourceDao().insert(*sources.toTypedArray()) - return sources.size - - } - return 0 - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt index fdf77bc85..4b6d4a159 100644 --- a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt +++ b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt @@ -2,7 +2,7 @@ package io.legado.app.ui.welcome import android.content.Intent import android.os.Bundle -import com.github.houbb.opencc4j.util.ZhConverterUtil +import com.hankcs.hanlp.HanLP import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity @@ -37,13 +37,13 @@ open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) { .clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)) //初始化简繁转换引擎 when (AppConfig.chineseConverterType) { - 1 -> ZhConverterUtil.toSimple("初始化") - 2 -> ZhConverterUtil.toTraditional("初始化") + 1 -> HanLP.convertToSimplifiedChinese("初始化") + 2 -> HanLP.convertToTraditionalChinese("初始化") else -> null } } SyncBookProgress.downloadBookProgress() - root_view.postDelayed({ startMainActivity() }, 300) + root_view.postDelayed({ startMainActivity() }, 500) } private fun startMainActivity() { diff --git a/app/src/main/java/io/legado/app/ui/widget/ArcView.kt b/app/src/main/java/io/legado/app/ui/widget/ArcView.kt index b9834132c..6338c6638 100644 --- a/app/src/main/java/io/legado/app/ui/widget/ArcView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/ArcView.kt @@ -18,7 +18,7 @@ class ArcView @JvmOverloads constructor( private val mArcHeight: Int //背景颜色 - private val mBgColor: Int + private var mBgColor: Int private val mPaint: Paint = Paint() private val mDirectionTop: Boolean val rect = Rect() @@ -76,4 +76,8 @@ class ArcView @JvmOverloads constructor( setMeasuredDimension(mWidth, mHeight) } + fun setBgColor(color: Int) { + mBgColor = color + invalidate() + } } \ 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 f11db64e8..3141c5a86 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 @@ -5,6 +5,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.Rect +import android.graphics.Typeface import android.util.AttributeSet import androidx.annotation.ColorInt import androidx.appcompat.widget.AppCompatTextView @@ -14,12 +15,14 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co private val batteryPaint = Paint() private val outFrame = Rect() private val polar = Rect() + var isBattery = false init { setPadding(4.dp, 0, 6.dp, 0) batteryPaint.strokeWidth = 1.dp.toFloat() batteryPaint.isAntiAlias = true batteryPaint.color = paint.color + typeface = Typeface.createFromAsset(context.assets, "number.ttf") } fun setColor(@ColorInt color: Int) { @@ -35,11 +38,12 @@ class BatteryView(context: Context, attrs: AttributeSet?) : AppCompatTextView(co override fun onDraw(canvas: Canvas) { super.onDraw(canvas) + if (!isBattery) return outFrame.set( 1.dp, - layout.getLineTop(0) + 2.dp, + 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/DetailSeekBar.kt b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt index 504837afd..5198599e4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt @@ -6,13 +6,16 @@ import android.view.View import android.widget.FrameLayout import android.widget.SeekBar import io.legado.app.R +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.utils.ColorUtils import io.legado.app.utils.progressAdd import kotlinx.android.synthetic.main.view_detail_seek_bar.view.* import org.jetbrains.anko.sdk27.listeners.onClick class DetailSeekBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs), SeekBar.OnSeekBarChangeListener { - + private val isBottomBackground: Boolean var valueFormat: ((progress: Int) -> String)? = null var onChanged: ((progress: Int) -> Unit)? = null var progress: Int @@ -30,10 +33,19 @@ class DetailSeekBar(context: Context, attrs: AttributeSet?) : FrameLayout(contex View.inflate(context, R.layout.view_detail_seek_bar, this) val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailSeekBar) + isBottomBackground = + typedArray.getBoolean(R.styleable.DetailSeekBar_isBottomBackground, false) tv_seek_title.text = typedArray.getText(R.styleable.DetailSeekBar_title) seek_bar.max = typedArray.getInteger(R.styleable.DetailSeekBar_max, 0) typedArray.recycle() - + if (isBottomBackground) { + val isLight = ColorUtils.isColorLight(context.bottomBackground) + val textColor = context.getPrimaryTextColor(isLight) + tv_seek_title.setTextColor(textColor) + iv_seek_plus.setColorFilter(textColor) + iv_seek_reduce.setColorFilter(textColor) + tv_seek_value.setTextColor(textColor) + } iv_seek_plus.onClick { seek_bar.progressAdd(1) onChanged?.invoke(seek_bar.progress) diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt index 765a0a580..6d340c6d7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -20,12 +20,12 @@ class SearchView : SearchView { private var textView: TextView? = null constructor( - context: Context?, + context: Context, attrs: AttributeSet? = null ) : super(context, attrs) constructor( - context: Context?, + context: Context, attrs: AttributeSet?, defStyleAttr: Int ) : super(context, attrs, defStyleAttr) diff --git a/app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt b/app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt index dc37085b0..4bef4c53d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt @@ -9,7 +9,8 @@ import androidx.annotation.MenuRes import androidx.annotation.StringRes import androidx.appcompat.widget.PopupMenu import io.legado.app.R -import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.* +import io.legado.app.utils.ColorUtils import io.legado.app.utils.dp import io.legado.app.utils.visible import kotlinx.android.synthetic.main.view_select_action_bar.view.* @@ -23,6 +24,12 @@ class SelectActionBar(context: Context, attrs: AttributeSet?) : FrameLayout(cont setBackgroundColor(context.bottomBackground) elevation = 10.dp.toFloat() View.inflate(context, R.layout.view_select_action_bar, this) + val textIsDark = ColorUtils.isColorLight(context.bottomBackground) + val primaryTextColor = context.getPrimaryTextColor(textIsDark) + val secondaryTextColor = context.getSecondaryTextColor(textIsDark) + cb_selected_all.setTextColor(primaryTextColor) + TintHelper.setTint(cb_selected_all, context.accentColor, !textIsDark) + iv_menu_more.setColorFilter(secondaryTextColor) cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked -> if (buttonView.isPressed) { callBack?.selectAll(isChecked) diff --git a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt index 94d3b7be6..39e1cd441 100644 --- a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt @@ -45,8 +45,6 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a private val attachToActivity: Boolean init { - inflate(context, R.layout.view_title_bar, this) - toolbar = findViewById(R.id.toolbar) val a = context.obtainStyledAttributes( attrs, R.styleable.TitleBar, R.attr.titleBarStyle, 0 @@ -62,6 +60,12 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a val titleText = a.getString(R.styleable.TitleBar_title) val subtitleText = a.getString(R.styleable.TitleBar_subtitle) + when (a.getInt(R.styleable.TitleBar_themeMode, 0)) { + 1 -> inflate(context, R.layout.view_title_bar_dark, this) + else -> inflate(context, R.layout.view_title_bar, this) + } + toolbar = findViewById(R.id.toolbar) + toolbar.apply { navigationIcon?.let { this.navigationIcon = it @@ -192,6 +196,10 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a backgroundColor = Color.TRANSPARENT } + fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, fullScreen: Boolean) { + topPadding = if (!isInMultiWindowMode && fullScreen) context.statusBarHeight else 0 + } + private fun attachToActivity() { if (attachToActivity) { activity?.let { diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/PhotoDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/PhotoDialog.kt new file mode 100644 index 000000000..36397a201 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/PhotoDialog.kt @@ -0,0 +1,72 @@ +package io.legado.app.ui.widget.dialog + +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.service.help.ReadBook +import io.legado.app.ui.book.read.page.provider.ImageProvider +import kotlinx.android.synthetic.main.dialog_photo_view.* + + +class PhotoDialog : BaseDialogFragment() { + + companion object { + + fun show( + fragmentManager: FragmentManager, + chapterIndex: Int, + src: String, + ) { + PhotoDialog().apply { + val bundle = Bundle() + bundle.putInt("chapterIndex", chapterIndex) + bundle.putString("src", src) + arguments = bundle + }.show(fragmentManager, "photoDialog") + } + + } + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.dialog_photo_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + arguments?.let { + val chapterIndex = it.getInt("chapterIndex") + val src = it.getString("src") + ReadBook.book?.let { book -> + src?.let { + execute { + ImageProvider.getImage(book, chapterIndex, src) + }.onSuccess { bitmap -> + if (bitmap != null) { + photo_view.setImageBitmap(bitmap) + } + } + } + } + } + + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt index 9c1c75d5e..dc6ba868b 100644 --- a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt @@ -23,7 +23,8 @@ class TextDialog : BaseDialogFragment() { fragmentManager: FragmentManager, content: String?, mode: Int = 0, - time: Long = 0 + time: Long = 0, + autoClose: Boolean = false ) { TextDialog().apply { val bundle = Bundle() @@ -32,6 +33,7 @@ class TextDialog : BaseDialogFragment() { bundle.putLong("time", time) arguments = bundle isCancelable = false + this.autoClose = autoClose }.show(fragmentManager, "textDialog") } @@ -39,6 +41,8 @@ class TextDialog : BaseDialogFragment() { private var time = 0L + private var autoClose: Boolean = false + override fun onStart() { super.onStart() val dm = DisplayMetrics() @@ -79,6 +83,7 @@ class TextDialog : BaseDialogFragment() { if (time <= 0) { view.post { dialog?.setCancelable(true) + if (autoClose) dialog?.cancel() } } } @@ -86,6 +91,7 @@ class TextDialog : BaseDialogFragment() { } else { view.post { dialog?.setCancelable(true) + if (autoClose) dialog?.cancel() } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt new file mode 100644 index 000000000..323e622ea --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt @@ -0,0 +1,85 @@ +package io.legado.app.ui.widget.dialog + +import android.content.Context +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import kotlinx.android.synthetic.main.dialog_recycler_view.* +import kotlinx.android.synthetic.main.item_log.view.* + +class TextListDialog : BaseDialogFragment() { + + companion object { + fun show(fragmentManager: FragmentManager, title: String, values: ArrayList) { + TextListDialog().apply { + val bundle = Bundle() + bundle.putString("title", title) + bundle.putStringArrayList("values", values) + arguments = bundle + }.show(fragmentManager, "textListDialog") + } + } + + lateinit var adapter: TextAdapter + var values: ArrayList? = null + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.9).toInt()) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_recycler_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + arguments?.let { + tool_bar.title = it.getString("title") + values = it.getStringArrayList("values") + } + recycler_view.layoutManager = LinearLayoutManager(requireContext()) + adapter = TextAdapter(requireContext()) + recycler_view.adapter = adapter + adapter.setItems(values) + } + + class TextAdapter(context: Context) : + SimpleRecyclerAdapter(context, R.layout.item_log) { + override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList) { + holder.itemView.apply { + if (text_view.getTag(R.id.tag1) == null) { + val listener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + text_view.isCursorVisible = false + text_view.isCursorVisible = true + } + + override fun onViewDetachedFromWindow(v: View) {} + } + text_view.addOnAttachStateChangeListener(listener) + text_view.setTag(R.id.tag1, listener) + } + text_view.text = item + } + } + + override fun registerListener(holder: ItemViewHolder) { + //nothing + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt index ab6e34a1c..a54ee0182 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt @@ -2,9 +2,12 @@ package io.legado.app.ui.widget.font import android.content.Context import android.graphics.Typeface +import android.os.Build import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.utils.DocItem +import io.legado.app.utils.RealPathUtil import io.legado.app.utils.invisible import io.legado.app.utils.visible import kotlinx.android.synthetic.main.item_font.view.* @@ -13,15 +16,28 @@ import org.jetbrains.anko.toast import java.io.File class FontAdapter(context: Context, val callBack: CallBack) : - SimpleRecyclerAdapter(context, R.layout.item_font) { + SimpleRecyclerAdapter(context, R.layout.item_font) { - override fun convert(holder: ItemViewHolder, item: File, payloads: MutableList) { + override fun convert(holder: ItemViewHolder, item: DocItem, payloads: MutableList) { with(holder.itemView) { try { - val typeface = Typeface.createFromFile(item) + val typeface: Typeface? = if (item.isContentPath) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.contentResolver + .openFileDescriptor(item.uri, "r") + ?.fileDescriptor?.let { + Typeface.Builder(it).build() + } + } else { + Typeface.createFromFile(RealPathUtil.getPath(context, item.uri)) + } + } else { + Typeface.createFromFile(item.uri.toString()) + } tv_font.typeface = typeface } catch (e: Exception) { - context.toast("读取${item.name}字体失败") + e.printStackTrace() + context.toast("Read ${item.name} Error: ${e.localizedMessage}") } tv_font.text = item.name this.onClick { callBack.onClick(item) } @@ -42,7 +58,7 @@ class FontAdapter(context: Context, val callBack: CallBack) : } interface CallBack { - fun onClick(file: File) + fun onClick(docItem: DocItem) val curFilePath: String } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index 8157ed870..0fa7bea7d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -1,6 +1,5 @@ package io.legado.app.ui.widget.font -import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent import android.net.Uri @@ -16,33 +15,30 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseDialogFragment -import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_font_select.* -import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.io.File +import java.util.* class FontSelectDialog : BaseDialogFragment(), FileChooserDialog.CallBack, Toolbar.OnMenuItemClickListener, FontAdapter.CallBack { private val fontFolderRequestCode = 35485 + private val fontRegex = Regex(".*\\.[ot]tf") private val fontFolder by lazy { FileUtils.createFolderIfNotExist(App.INSTANCE.filesDir, "Fonts") } - private val fontCacheFolder by lazy { - FileUtils.createFolderIfNotExist(App.INSTANCE.cacheDir, "Fonts") - } private var adapter: FontAdapter? = null override fun onStart() { @@ -61,8 +57,10 @@ class FontSelectDialog : BaseDialogFragment(), } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) tool_bar.setTitle(R.string.select_font) tool_bar.inflateMenu(R.menu.font_select) + tool_bar.menu.applyTint(requireContext()) tool_bar.setOnMenuItemClickListener(this) adapter = FontAdapter(requireContext(), this) recycler_view.layoutManager = LinearLayoutManager(context) @@ -75,12 +73,12 @@ class FontSelectDialog : BaseDialogFragment(), if (fontPath.isContentPath()) { val doc = DocumentFile.fromTreeUri(requireContext(), Uri.parse(fontPath)) if (doc?.canRead() == true) { - getFontFiles(doc) + loadFontFiles(doc) } else { openFolder() } } else { - getFontFilesByPermission(fontPath) + loadFontFilesByPermission(fontPath) } } } @@ -90,7 +88,9 @@ class FontSelectDialog : BaseDialogFragment(), R.id.menu_default -> { val requireContext = requireContext() requireContext.alert(titleResource = R.string.system_typeface) { - items(requireContext.resources.getStringArray(R.array.system_typefaces).toList()) { _, i -> + items( + requireContext.resources.getStringArray(R.array.system_typefaces).toList() + ) { _, i -> AppConfig.systemTypefaces = i onDefaultFontChange() dismiss() @@ -106,95 +106,92 @@ class FontSelectDialog : BaseDialogFragment(), private fun openFolder() { launch(Main) { - FilePicker.selectFolder(this@FontSelectDialog, fontFolderRequestCode) { - val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts" - putPrefString(PreferKey.fontFolder, path) - getFontFilesByPermission(path) + val defaultPath = "SD${File.separator}Fonts" + FilePicker.selectFolder( + this@FontSelectDialog, + fontFolderRequestCode, + otherActions = arrayListOf(defaultPath) + ) { + when (it) { + defaultPath -> { + val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts" + putPrefString(PreferKey.fontFolder, path) + loadFontFilesByPermission(path) + } + } } } } - @SuppressLint("DefaultLocale") - private fun getFontFiles(doc: DocumentFile) { + private fun loadFontFiles(doc: DocumentFile) { execute { + val fontItems = arrayListOf() val docItems = DocumentUtils.listFiles(App.INSTANCE, doc.uri) - fontCacheFolder.listFiles()?.forEach { fontFile -> - var contain = false - for (item in docItems) { - if (fontFile.name == item.name) { - contain = true - break - } - } - if (!contain) { - fontFile.delete() - } - } docItems.forEach { item -> - if (item.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())) { - val fontFile = FileUtils.getFile(fontCacheFolder, item.name) - if (!fontFile.exists()) { - DocumentUtils.readBytes(App.INSTANCE, item.uri)?.let { byteArray -> - fontFile.writeBytes(byteArray) - } - } - } - } - try { - fontCacheFolder.listFiles { pathName -> - pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex()) - }?.let { - withContext(Main) { - adapter?.setItems(it.toList()) - } + if (item.name.toLowerCase(Locale.getDefault()).matches(fontRegex)) { + fontItems.add(item) } - } catch (e: Exception) { - toast(e.localizedMessage ?: "") } + fontItems.sortedBy { it.name } + }.onSuccess { + adapter?.setItems(it) }.onError { toast("getFontFiles:${it.localizedMessage}") } } - @SuppressLint("DefaultLocale") - private fun getFontFilesByPermission(path: String) { + private fun loadFontFilesByPermission(path: String) { PermissionsCompat.Builder(this@FontSelectDialog) .addPermissions(*Permissions.Group.STORAGE) .rationale(R.string.tip_perm_request_storage) .onGranted { - try { - val file = File(path) - file.listFiles { pathName -> - pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex()) - }?.let { - adapter?.setItems(it.toList()) - } - } catch (e: Exception) { - toast(e.localizedMessage ?: "") - } + loadFontFiles(path) } .request() } - override fun onClick(file: File) { - launch(IO) { - file.copyTo(FileUtils.createFileIfNotExist(fontFolder, file.name), true) - .absolutePath.let { path -> - if (curFilePath != path) { - withContext(Main) { - callBack?.selectFile(path) - } - } + private fun loadFontFiles(path: String) { + execute { + val fontItems = arrayListOf() + val file = File(path) + file.listFiles { pathName -> + pathName.name.toLowerCase(Locale.getDefault()).matches(fontRegex) + }?.forEach { + fontItems.add( + DocItem( + it.name, + it.extension, + it.length(), + Date(it.lastModified()), + Uri.parse(it.absolutePath) + ) + ) } + fontItems.sortedBy { it.name } + }.onSuccess { + adapter?.setItems(it) + }.onError { + toast("getFontFiles:${it.localizedMessage}") + } + } + + override fun onClick(docItem: DocItem) { + execute { + FileUtils.deleteFile(fontFolder.absolutePath) + callBack?.selectFont(docItem.uri.toString()) + }.onSuccess { dialog?.dismiss() } } + /** + * 字体文件夹 + */ override fun onFilePicked(requestCode: Int, currentPath: String) { when (requestCode) { fontFolderRequestCode -> { putPrefString(PreferKey.fontFolder, currentPath) - getFontFilesByPermission(currentPath) + loadFontFilesByPermission(currentPath) } } } @@ -211,10 +208,10 @@ class FontSelectDialog : BaseDialogFragment(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) - getFontFiles(doc) + loadFontFiles(doc) } else { RealPathUtil.getPath(requireContext(), uri)?.let { - getFontFilesByPermission(it) + loadFontFilesByPermission(it) } } } @@ -223,11 +220,7 @@ class FontSelectDialog : BaseDialogFragment(), } private fun onDefaultFontChange() { - if (curFilePath == "") { - postEvent(EventBus.UP_CONFIG, true) - } else { - callBack?.selectFile("") - } + callBack?.selectFont("") } override val curFilePath: String get() = callBack?.curFontPath ?: "" @@ -236,7 +229,7 @@ class FontSelectDialog : BaseDialogFragment(), get() = (parentFragment as? CallBack) ?: (activity as? CallBack) interface CallBack { - fun selectFile(path: String) + fun selectFont(path: String) val curFontPath: String } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt b/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt index 7f14686e8..43e3f5506 100644 --- a/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt @@ -117,7 +117,9 @@ class CircleImageView(context: Context, attrs: AttributeSet) : private var text: String? = null - private var textColor = context.getCompatColor(R.color.tv_text_default) + private var textColor = context.getCompatColor(R.color.primaryText) + private var textBold = false + var isInView = false init { super.setScaleType(SCALE_TYPE) @@ -140,7 +142,7 @@ class CircleImageView(context: Context, attrs: AttributeSet) : if (a.hasValue(R.styleable.CircleImageView_textColor)) { textColor = a.getColor( R.styleable.CircleImageView_textColor, - context.getCompatColor(R.color.tv_text_default) + context.getCompatColor(R.color.primaryText) ) } a.recycle() @@ -210,6 +212,7 @@ class CircleImageView(context: Context, attrs: AttributeSet) : private fun drawText(canvas: Canvas) { text?.let { textPaint.color = textColor + textPaint.isFakeBoldText = textBold textPaint.textSize = 15.sp.toFloat() val fm = textPaint.fontMetrics canvas.drawText( @@ -221,11 +224,21 @@ class CircleImageView(context: Context, attrs: AttributeSet) : } } + fun setText(text: String?) { + this.text = text + invalidate() + } + fun setTextColor(@ColorInt textColor: Int) { this.textColor = textColor invalidate() } + fun setTextBold(bold: Boolean) { + this.textBold = bold + invalidate() + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) setup() @@ -415,7 +428,12 @@ class CircleImageView(context: Context, attrs: AttributeSet) : @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { - return inTouchableArea(event.x, event.y) && super.onTouchEvent(event) + when (event.action) { + MotionEvent.ACTION_DOWN -> { + isInView = (inTouchableArea(event.x, event.y)) + } + } + return super.onTouchEvent(event) } private fun inTouchableArea(x: Float, y: Float): Boolean { 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..f2b4373a4 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 @@ -10,8 +10,11 @@ import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target +import io.legado.app.App import io.legado.app.R +import io.legado.app.constant.PreferKey import io.legado.app.help.ImageLoader +import io.legado.app.utils.getPrefString class CoverImageView : androidx.appcompat.widget.AppCompatImageView { @@ -89,7 +92,7 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView { canvas.clipPath(path) } super.onDraw(canvas) - if (!loadFailed) return + if (!loadFailed || !showBookName) return name?.let { namePaint.color = Color.WHITE namePaint.style = Paint.Style.STROKE @@ -130,9 +133,9 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView { fun load(path: String?, name: String?, author: String?) { setText(name, author) - ImageLoader.load(context, path)//Glide自动识别http://和file:// - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) + ImageLoader.load(context, path)//Glide自动识别http://,content://和file:// + .placeholder(defaultDrawable) + .error(defaultDrawable) .listener(object : RequestListener { override fun onLoadFailed( e: GlideException?, @@ -159,4 +162,26 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView { .centerCrop() .into(this) } + + companion object { + private var showBookName = false + lateinit var defaultDrawable: Drawable + + init { + upDefaultCover() + } + + fun upDefaultCover() { + val path = App.INSTANCE.getPrefString(PreferKey.defaultCover) + var dw = Drawable.createFromPath(path) + if (dw == null) { + showBookName = true + dw = App.INSTANCE.resources.getDrawable(R.drawable.image_cover_default, null) + } else { + showBookName = false + } + defaultDrawable = dw!! + } + + } } diff --git a/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt b/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt new file mode 100644 index 000000000..44f108e91 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt @@ -0,0 +1,1269 @@ +package io.legado.app.ui.widget.image + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.PointF +import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.* +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.ScaleGestureDetector.OnScaleGestureListener +import android.view.animation.DecelerateInterpolator +import android.view.animation.Interpolator +import android.widget.ImageView +import android.widget.OverScroller +import android.widget.Scroller +import io.legado.app.R +import io.legado.app.ui.widget.image.photo.Info +import io.legado.app.ui.widget.image.photo.OnRotateListener +import io.legado.app.ui.widget.image.photo.RotateGestureDetector +import kotlin.math.abs +import kotlin.math.roundToInt + + +@SuppressLint("AppCompatCustomView") +class PhotoView : ImageView { + val MIN_ROTATE = 35 + val ANIMA_DURING = 340 + val MAX_SCALE = 2.5f + + private var mMinRotate = 0 + var mAnimaDuring = 0 + private var mMaxScale = 0f + + var MAX_OVER_SCROLL = 0 + var MAX_FLING_OVER_SCROLL = 0 + var MAX_OVER_RESISTANCE = 0 + var MAX_ANIM_FROM_WAITE = 500 + + private val mBaseMatrix: Matrix = Matrix() + private val mAnimMatrix: Matrix = Matrix() + private val mSynthesisMatrix: Matrix = Matrix() + private val mTmpMatrix: Matrix = Matrix() + + private var mRotateDetector: RotateGestureDetector? = null + private var mDetector: GestureDetector? = null + private var mScaleDetector: ScaleGestureDetector? = null + private var mClickListener: OnClickListener? = null + + private var mScaleType: ScaleType? = null + + private var hasMultiTouch = false + private var hasDrawable = false + private var isKnowSize = false + private var hasOverTranslate = false + + //缩放 + var isEnable = true + + //旋转 + var isRotateEnable = false + private var isInit = false + private var mAdjustViewBounds = false + + // 当前是否处于放大状态 + private var isZoonUp = false + private var canRotate = false + + private var imgLargeWidth = false + private var imgLargeHeight = false + + private var mRotateFlag = 0f + private var mDegrees = 0f + private var mScale = 1.0f + private var mTranslateX = 0 + private var mTranslateY = 0 + + private var mHalfBaseRectWidth = 0f + private var mHalfBaseRectHeight = 0f + + private val mWidgetRect = RectF() + private val mBaseRect = RectF() + private val mImgRect = RectF() + private val mTmpRect = RectF() + private val mCommonRect = RectF() + + private val mScreenCenter = PointF() + private val mScaleCenter = PointF() + private val mRotateCenter = PointF() + + private val mTranslate: Transform = Transform() + + private var mClip: RectF? = null + private var mFromInfo: Info? = null + private var mInfoTime: Long = 0 + private var mCompleteCallBack: Runnable? = null + + private var mLongClick: OnLongClickListener? = null + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init() + } + + private fun init() { + super.setScaleType(ScaleType.MATRIX) + if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE + mRotateDetector = RotateGestureDetector(mRotateListener) + mDetector = GestureDetector(context, mGestureListener) + mScaleDetector = ScaleGestureDetector(context, mScaleListener) + val density = resources.displayMetrics.density + MAX_OVER_SCROLL = (density * 30).toInt() + MAX_FLING_OVER_SCROLL = (density * 30).toInt() + MAX_OVER_RESISTANCE = (density * 140).toInt() + mMinRotate = MIN_ROTATE + mAnimaDuring = ANIMA_DURING + mMaxScale = MAX_SCALE + } + + /** + * 获取默认的动画持续时间 + */ + fun getDefaultAnimDuring(): Int { + return ANIMA_DURING + } + + override fun setOnClickListener(l: OnClickListener?) { + super.setOnClickListener(l) + mClickListener = l + } + + override fun setScaleType(scaleType: ScaleType) { + if (scaleType == ScaleType.MATRIX) return + if (scaleType != mScaleType) { + mScaleType = scaleType + if (isInit) { + initBase() + } + } + } + + override fun setOnLongClickListener(l: OnLongClickListener?) { + mLongClick = l + } + + /** + * 设置动画的插入器 + */ + fun setInterpolator(interpolator: Interpolator?) { + mTranslate.setInterpolator(interpolator) + } + + /** + * 获取动画持续时间 + */ + fun getAnimDuring(): Int { + return mAnimaDuring + } + + /** + * 设置动画的持续时间 + */ + fun setAnimDuring(during: Int) { + mAnimaDuring = during + } + + /** + * 设置最大可以缩放的倍数 + */ + fun setMaxScale(maxScale: Float) { + mMaxScale = maxScale + } + + /** + * 获取最大可以缩放的倍数 + */ + fun getMaxScale(): Float { + return mMaxScale + } + + /** + */ + fun setMaxAnimFromWaiteTime(wait: Int) { + MAX_ANIM_FROM_WAITE = wait + } + + override fun setImageResource(resId: Int) { + var drawable: Drawable? = null + try { + drawable = resources.getDrawable(resId, null) + } catch (e: Exception) { + } + setImageDrawable(drawable) + } + + override fun setImageDrawable(drawable: Drawable?) { + super.setImageDrawable(drawable) + if (drawable == null) { + hasDrawable = false + return + } + if (!hasSize(drawable)) return + if (!hasDrawable) { + hasDrawable = true + } + initBase() + } + + private fun hasSize(d: Drawable): Boolean { + return !((d.intrinsicHeight <= 0 || d.intrinsicWidth <= 0) + && (d.minimumWidth <= 0 || d.minimumHeight <= 0) + && (d.bounds.width() <= 0 || d.bounds.height() <= 0)) + } + + private fun getDrawableWidth(d: Drawable): Int { + var width = d.intrinsicWidth + if (width <= 0) width = d.minimumWidth + if (width <= 0) width = d.bounds.width() + return width + } + + private fun getDrawableHeight(d: Drawable): Int { + var height = d.intrinsicHeight + if (height <= 0) height = d.minimumHeight + if (height <= 0) height = d.bounds.height() + return height + } + + private fun initBase() { + if (!hasDrawable) return + if (!isKnowSize) return + mBaseMatrix.reset() + mAnimMatrix.reset() + isZoonUp = false + val img = drawable + val w = width + val h = height + val imgW = getDrawableWidth(img) + val imgH = getDrawableHeight(img) + mBaseRect[0f, 0f, imgW.toFloat()] = imgH.toFloat() + + // 以图片中心点居中位移 + val tx = (w - imgW) / 2 + val ty = (h - imgH) / 2 + var sx = 1f + var sy = 1f + + // 缩放,默认不超过屏幕大小 + if (imgW > w) { + sx = w.toFloat() / imgW + } + if (imgH > h) { + sy = h.toFloat() / imgH + } + val scale = if (sx < sy) sx else sy + mBaseMatrix.reset() + mBaseMatrix.postTranslate(tx.toFloat(), ty.toFloat()) + mBaseMatrix.postScale(scale, scale, mScreenCenter.x, mScreenCenter.y) + mBaseMatrix.mapRect(mBaseRect) + mHalfBaseRectWidth = mBaseRect.width() / 2 + mHalfBaseRectHeight = mBaseRect.height() / 2 + mScaleCenter.set(mScreenCenter) + mRotateCenter.set(mScaleCenter) + executeTranslate() + when (mScaleType) { + ScaleType.CENTER -> initCenter() + ScaleType.CENTER_CROP -> initCenterCrop() + ScaleType.CENTER_INSIDE -> initCenterInside() + ScaleType.FIT_CENTER -> initFitCenter() + ScaleType.FIT_START -> initFitStart() + ScaleType.FIT_END -> initFitEnd() + ScaleType.FIT_XY -> initFitXY() + else -> { + } + } + isInit = true + mFromInfo?.let { + if (System.currentTimeMillis() - mInfoTime < MAX_ANIM_FROM_WAITE) { + animaFrom(it) + } + } + mFromInfo = null + } + + private fun initCenter() { + if (!hasDrawable) return + if (!isKnowSize) return + val img = drawable + val imgW = getDrawableWidth(img) + val imgH = getDrawableHeight(img) + if (imgW > mWidgetRect.width() || imgH > mWidgetRect.height()) { + val scaleX = imgW / mImgRect.width() + val scaleY = imgH / mImgRect.height() + mScale = if (scaleX > scaleY) scaleX else scaleY + mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y) + executeTranslate() + resetBase() + } + } + + private fun initCenterCrop() { + if (mImgRect.width() < mWidgetRect.width() || mImgRect.height() < mWidgetRect.height()) { + val scaleX = mWidgetRect.width() / mImgRect.width() + val scaleY = mWidgetRect.height() / mImgRect.height() + mScale = if (scaleX > scaleY) scaleX else scaleY + mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y) + executeTranslate() + resetBase() + } + } + + private fun initCenterInside() { + if (mImgRect.width() > mWidgetRect.width() || mImgRect.height() > mWidgetRect.height()) { + val scaleX = mWidgetRect.width() / mImgRect.width() + val scaleY = mWidgetRect.height() / mImgRect.height() + mScale = if (scaleX < scaleY) scaleX else scaleY + mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y) + executeTranslate() + resetBase() + } + } + + private fun initFitCenter() { + if (mImgRect.width() < mWidgetRect.width()) { + mScale = mWidgetRect.width() / mImgRect.width() + mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y) + executeTranslate() + resetBase() + } + } + + private fun initFitStart() { + initFitCenter() + val ty = -mImgRect.top + mAnimMatrix.postTranslate(0f, ty) + executeTranslate() + resetBase() + mTranslateY += ty.toInt() + } + + private fun initFitEnd() { + initFitCenter() + val ty = mWidgetRect.bottom - mImgRect.bottom + mTranslateY += ty.toInt() + mAnimMatrix.postTranslate(0f, ty) + executeTranslate() + resetBase() + } + + private fun initFitXY() { + val scaleX = mWidgetRect.width() / mImgRect.width() + val scaleY = mWidgetRect.height() / mImgRect.height() + mAnimMatrix.postScale(scaleX, scaleY, mScreenCenter.x, mScreenCenter.y) + executeTranslate() + resetBase() + } + + private fun resetBase() { + val img = drawable + val imgW = getDrawableWidth(img) + val imgH = getDrawableHeight(img) + mBaseRect[0f, 0f, imgW.toFloat()] = imgH.toFloat() + mBaseMatrix.set(mSynthesisMatrix) + mBaseMatrix.mapRect(mBaseRect) + mHalfBaseRectWidth = mBaseRect.width() / 2 + mHalfBaseRectHeight = mBaseRect.height() / 2 + mScale = 1f + mTranslateX = 0 + mTranslateY = 0 + mAnimMatrix.reset() + } + + private fun executeTranslate() { + mSynthesisMatrix.set(mBaseMatrix) + mSynthesisMatrix.postConcat(mAnimMatrix) + imageMatrix = mSynthesisMatrix + mAnimMatrix.mapRect(mImgRect, mBaseRect) + imgLargeWidth = mImgRect.width() > mWidgetRect.width() + imgLargeHeight = mImgRect.height() > mWidgetRect.height() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (!hasDrawable) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + return + } + val d = drawable + val drawableW = getDrawableWidth(d) + val drawableH = getDrawableHeight(d) + val pWidth = MeasureSpec.getSize(widthMeasureSpec) + val pHeight = MeasureSpec.getSize(heightMeasureSpec) + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val heightMode = MeasureSpec.getMode(heightMeasureSpec) + var width: Int + var height: Int + var p = layoutParams + if (p == null) { + p = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + width = if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) { + if (widthMode == MeasureSpec.UNSPECIFIED) { + drawableW + } else { + pWidth + } + } else { + if (widthMode == MeasureSpec.EXACTLY) { + pWidth + } else if (widthMode == MeasureSpec.AT_MOST) { + if (drawableW > pWidth) pWidth else drawableW + } else { + drawableW + } + } + height = if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) { + if (heightMode == MeasureSpec.UNSPECIFIED) { + drawableH + } else { + pHeight + } + } else { + if (heightMode == MeasureSpec.EXACTLY) { + pHeight + } else if (heightMode == MeasureSpec.AT_MOST) { + if (drawableH > pHeight) pHeight else drawableH + } else { + drawableH + } + } + if (mAdjustViewBounds && drawableW.toFloat() / drawableH != width.toFloat() / height) { + val hScale = height.toFloat() / drawableH + val wScale = width.toFloat() / drawableW + val scale = if (hScale < wScale) hScale else wScale + width = + if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) width else (drawableW * scale).toInt() + height = + if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) height else (drawableH * scale).toInt() + } + setMeasuredDimension(width, height) + } + + override fun setAdjustViewBounds(adjustViewBounds: Boolean) { + super.setAdjustViewBounds(adjustViewBounds) + mAdjustViewBounds = adjustViewBounds + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mWidgetRect[0f, 0f, w.toFloat()] = h.toFloat() + mScreenCenter[w / 2.toFloat()] = h / 2.toFloat() + if (!isKnowSize) { + isKnowSize = true + initBase() + } + } + + override fun draw(canvas: Canvas) { + mClip?.let { + canvas.clipRect(it) + mClip = null + } + super.draw(canvas) + } + + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + return if (isEnable) { + val action = event.actionMasked + if (event.pointerCount >= 2) hasMultiTouch = true + mDetector!!.onTouchEvent(event) + if (isRotateEnable) { + mRotateDetector!!.onTouchEvent(event) + } + mScaleDetector!!.onTouchEvent(event) + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) onUp() + true + } else { + super.dispatchTouchEvent(event) + } + } + + private fun onUp() { + if (mTranslate.isRunning) return + if (canRotate || mDegrees % 90 != 0f) { + var toDegrees = (mDegrees / 90).toInt() * 90.toFloat() + val remainder = mDegrees % 90 + if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f + mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt()) + mDegrees = toDegrees + } + var scale = mScale + if (mScale < 1) { + scale = 1f + mTranslate.withScale(mScale, 1F) + } else if (mScale > mMaxScale) { + scale = mMaxScale + mTranslate.withScale(mScale, mMaxScale) + } + val cx = mImgRect.left + mImgRect.width() / 2 + val cy = mImgRect.top + mImgRect.height() / 2 + mScaleCenter[cx] = cy + mRotateCenter[cx] = cy + mTranslateX = 0 + mTranslateY = 0 + mTmpMatrix.reset() + mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top) + mTmpMatrix.postTranslate(cx - mHalfBaseRectWidth, cy - mHalfBaseRectHeight) + mTmpMatrix.postScale(scale, scale, cx, cy) + mTmpMatrix.postRotate(mDegrees, cx, cy) + mTmpMatrix.mapRect(mTmpRect, mBaseRect) + doTranslateReset(mTmpRect) + mTranslate.start() + } + + private fun doTranslateReset(imgRect: RectF) { + var tx = 0 + var ty = 0 + if (imgRect.width() <= mWidgetRect.width()) { + if (!isImageCenterWidth(imgRect)) tx = + (-((mWidgetRect.width() - imgRect.width()) / 2 - imgRect.left)).toInt() + } else { + if (imgRect.left > mWidgetRect.left) { + tx = (imgRect.left - mWidgetRect.left).toInt() + } else if (imgRect.right < mWidgetRect.right) { + tx = (imgRect.right - mWidgetRect.right).toInt() + } + } + if (imgRect.height() <= mWidgetRect.height()) { + if (!isImageCenterHeight(imgRect)) ty = + (-((mWidgetRect.height() - imgRect.height()) / 2 - imgRect.top)).toInt() + } else { + if (imgRect.top > mWidgetRect.top) { + ty = (imgRect.top - mWidgetRect.top).toInt() + } else if (imgRect.bottom < mWidgetRect.bottom) { + ty = (imgRect.bottom - mWidgetRect.bottom).toInt() + } + } + if (tx != 0 || ty != 0) { + if (!mTranslate.mFlingScroller.isFinished) mTranslate.mFlingScroller.abortAnimation() + mTranslate.withTranslate(mTranslateX, mTranslateY, -tx, -ty) + } + } + + private fun isImageCenterHeight(rect: RectF): Boolean { + return abs(rect.top.roundToInt() - (mWidgetRect.height() - rect.height()) / 2) < 1 + } + + private fun isImageCenterWidth(rect: RectF): Boolean { + return abs(rect.left.roundToInt() - (mWidgetRect.width() - rect.width()) / 2) < 1 + } + + private val mRotateListener: OnRotateListener = object : + OnRotateListener { + override fun onRotate( + degrees: Float, + focusX: Float, + focusY: Float + ) { + mRotateFlag += degrees + if (canRotate) { + mDegrees += degrees + mAnimMatrix.postRotate(degrees, focusX, focusY) + } else { + if (abs(mRotateFlag) >= mMinRotate) { + canRotate = true + mRotateFlag = 0f + } + } + } + } + + private val mScaleListener: OnScaleGestureListener = object : OnScaleGestureListener { + override fun onScale(detector: ScaleGestureDetector): Boolean { + val scaleFactor = detector.scaleFactor + if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false + mScale *= scaleFactor + //mScaleCenter.set(detector.getFocusX(), detector.getFocusY()); + mAnimMatrix.postScale( + scaleFactor, + scaleFactor, + detector.focusX, + detector.focusY + ) + executeTranslate() + return true + } + + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + return true + } + + override fun onScaleEnd(detector: ScaleGestureDetector) {} + } + + private fun resistanceScrollByX( + overScroll: Float, + detalX: Float + ): Float { + return detalX * (abs(abs(overScroll) - MAX_OVER_RESISTANCE) / MAX_OVER_RESISTANCE.toFloat()) + } + + private fun resistanceScrollByY( + overScroll: Float, + detalY: Float + ): Float { + return detalY * (abs(abs(overScroll) - MAX_OVER_RESISTANCE) / MAX_OVER_RESISTANCE.toFloat()) + } + + /** + * 匹配两个Rect的共同部分输出到out,若无共同部分则输出0,0,0,0 + */ + private fun mapRect(r1: RectF, r2: RectF, out: RectF) { + val l: Float = if (r1.left > r2.left) r1.left else r2.left + val r: Float = if (r1.right < r2.right) r1.right else r2.right + if (l > r) { + out[0f, 0f, 0f] = 0f + return + } + val t: Float = if (r1.top > r2.top) r1.top else r2.top + val b: Float = if (r1.bottom < r2.bottom) r1.bottom else r2.bottom + if (t > b) { + out[0f, 0f, 0f] = 0f + return + } + out[l, t, r] = b + } + + private fun checkRect() { + if (!hasOverTranslate) { + mapRect(mWidgetRect, mImgRect, mCommonRect) + } + } + + private val mClickRunnable = Runnable { + mClickListener?.onClick(this) + } + + private val mGestureListener: GestureDetector.OnGestureListener = + object : SimpleOnGestureListener() { + override fun onLongPress(e: MotionEvent) { + mLongClick?.onLongClick(this@PhotoView) + } + + override fun onDown(e: MotionEvent): Boolean { + hasOverTranslate = false + hasMultiTouch = false + canRotate = false + removeCallbacks(mClickRunnable) + return false + } + + override fun onFling( + e1: MotionEvent, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + if (hasMultiTouch) return false + if (!imgLargeWidth && !imgLargeHeight) return false + if (mTranslate.isRunning) return false + var vx = velocityX + var vy = velocityY + if (mImgRect.left.roundToInt() >= mWidgetRect.left + || mImgRect.right.roundToInt() <= mWidgetRect.right + ) { + vx = 0f + } + if (mImgRect.top.roundToInt() >= mWidgetRect.top + || mImgRect.bottom.roundToInt() <= mWidgetRect.bottom + ) { + vy = 0f + } + if (canRotate || mDegrees % 90 != 0f) { + var toDegrees = (mDegrees / 90).toInt() * 90.toFloat() + val remainder = mDegrees % 90 + if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f + mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt()) + mDegrees = toDegrees + } + doTranslateReset(mImgRect) + mTranslate.withFling(vx, vy) + mTranslate.start() + // onUp(e2); + return super.onFling(e1, e2, velocityX, velocityY) + } + + override fun onScroll( + e1: MotionEvent, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + var x = distanceX + var y = distanceY + if (mTranslate.isRunning) { + mTranslate.stop() + } + if (canScrollHorizontallySelf(x)) { + if (x < 0 && mImgRect.left - x > mWidgetRect.left) + x = mImgRect.left + if (x > 0 && mImgRect.right - x < mWidgetRect.right) + x = mImgRect.right - mWidgetRect.right + mAnimMatrix.postTranslate(-x, 0f) + mTranslateX -= x.toInt() + } else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) { + checkRect() + if (!hasMultiTouch) { + if (x < 0 && mImgRect.left - x > mCommonRect.left) x = + resistanceScrollByX(mImgRect.left - mCommonRect.left, x) + if (x > 0 && mImgRect.right - x < mCommonRect.right) x = + resistanceScrollByX(mImgRect.right - mCommonRect.right, x) + } + mTranslateX -= x.toInt() + mAnimMatrix.postTranslate(-x, 0f) + hasOverTranslate = true + } + if (canScrollVerticallySelf(y)) { + if (y < 0 && mImgRect.top - y > mWidgetRect.top) y = + mImgRect.top + if (y > 0 && mImgRect.bottom - y < mWidgetRect.bottom) y = + mImgRect.bottom - mWidgetRect.bottom + mAnimMatrix.postTranslate(0f, -y) + mTranslateY -= y.toInt() + } else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) { + checkRect() + if (!hasMultiTouch) { + if (y < 0 && mImgRect.top - y > mCommonRect.top) y = + resistanceScrollByY(mImgRect.top - mCommonRect.top, y) + if (y > 0 && mImgRect.bottom - y < mCommonRect.bottom) y = + resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, y) + } + mAnimMatrix.postTranslate(0f, -y) + mTranslateY -= y.toInt() + hasOverTranslate = true + } + executeTranslate() + return true + } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + postDelayed(mClickRunnable, 250) + return false + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + mTranslate.stop() + val from: Float + val to: Float + val imgCx = mImgRect.left + mImgRect.width() / 2 + val imgCy = mImgRect.top + mImgRect.height() / 2 + mScaleCenter[imgCx] = imgCy + mRotateCenter[imgCx] = imgCy + mTranslateX = 0 + mTranslateY = 0 + if (isZoonUp) { + from = mScale + to = 1f + } else { + from = mScale + to = mMaxScale + mScaleCenter[e.x] = e.y + } + mTmpMatrix.reset() + mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top) + mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y) + mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight) + mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y) + mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y) + mTmpMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat()) + mTmpMatrix.mapRect(mTmpRect, mBaseRect) + doTranslateReset(mTmpRect) + isZoonUp = !isZoonUp + mTranslate.withScale(from, to) + mTranslate.start() + return false + } + } + + fun canScrollHorizontallySelf(direction: Float): Boolean { + if (mImgRect.width() <= mWidgetRect.width()) + return false + if (direction < 0 && mImgRect.left.roundToInt() - direction >= mWidgetRect.left) + return false + return !(direction > 0 && mImgRect.right.roundToInt() - direction <= mWidgetRect.right) + } + + fun canScrollVerticallySelf(direction: Float): Boolean { + if (mImgRect.height() <= mWidgetRect.height()) + return false + if (direction < 0 && mImgRect.top.roundToInt() - direction >= mWidgetRect.top) + return false + return !(direction > 0 && mImgRect.bottom.roundToInt() - direction <= mWidgetRect.bottom) + } + + override fun canScrollHorizontally(direction: Int): Boolean { + return if (hasMultiTouch) true else canScrollHorizontallySelf(direction.toFloat()) + } + + override fun canScrollVertically(direction: Int): Boolean { + return if (hasMultiTouch) true else canScrollVerticallySelf(direction.toFloat()) + } + + private inner class InterpolatorProxy : Interpolator { + private var mTarget: Interpolator? + + init { + mTarget = DecelerateInterpolator() + } + + fun setTargetInterpolator(interpolator: Interpolator?) { + mTarget = interpolator + } + + override fun getInterpolation(input: Float): Float { + return mTarget?.getInterpolation(input) ?: input + } + + } + + private inner class Transform internal constructor() : Runnable { + var isRunning = false + var mTranslateScroller: OverScroller + var mFlingScroller: OverScroller + var mScaleScroller: Scroller + var mClipScroller: Scroller + var mRotateScroller: Scroller + var c: ClipCalculate? = null + var mLastFlingX = 0 + var mLastFlingY = 0 + var mLastTranslateX = 0 + var mLastTranslateY = 0 + var mClipRect = RectF() + var mInterpolatorProxy = InterpolatorProxy() + + fun setInterpolator(interpolator: Interpolator?) { + mInterpolatorProxy.setTargetInterpolator(interpolator) + } + + init { + val ctx: Context = context + mTranslateScroller = OverScroller(ctx, mInterpolatorProxy) + mScaleScroller = Scroller(ctx, mInterpolatorProxy) + mFlingScroller = OverScroller(ctx, mInterpolatorProxy) + mClipScroller = Scroller(ctx, mInterpolatorProxy) + mRotateScroller = Scroller(ctx, mInterpolatorProxy) + } + + fun withTranslate(startX: Int, startY: Int, deltaX: Int, deltaY: Int) { + mLastTranslateX = 0 + mLastTranslateY = 0 + mTranslateScroller.startScroll(0, 0, deltaX, deltaY, mAnimaDuring) + } + + fun withScale(form: Float, to: Float) { + mScaleScroller.startScroll( + (form * 10000).toInt(), + 0, + ((to - form) * 10000).toInt(), + 0, + mAnimaDuring + ) + } + + fun withClip( + fromX: Float, + fromY: Float, + deltaX: Float, + deltaY: Float, + d: Int, + c: ClipCalculate? + ) { + mClipScroller.startScroll( + (fromX * 10000).toInt(), + (fromY * 10000).toInt(), + (deltaX * 10000).toInt(), + (deltaY * 10000).toInt(), + d + ) + this.c = c + } + + fun withRotate(fromDegrees: Int, toDegrees: Int) { + mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, mAnimaDuring) + } + + fun withRotate(fromDegrees: Int, toDegrees: Int, during: Int) { + mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, during) + } + + fun withFling(velocityX: Float, velocityY: Float) { + mLastFlingX = if (velocityX < 0) Int.MAX_VALUE else 0 + var distanceX = + (if (velocityX > 0) abs(mImgRect.left) else mImgRect.right - mWidgetRect.right).toInt() + distanceX = if (velocityX < 0) Int.MAX_VALUE - distanceX else distanceX + var minX = if (velocityX < 0) distanceX else 0 + var maxX = if (velocityX < 0) Int.MAX_VALUE else distanceX + val overX = if (velocityX < 0) Int.MAX_VALUE - minX else distanceX + mLastFlingY = if (velocityY < 0) Int.MAX_VALUE else 0 + var distanceY = + (if (velocityY > 0) abs(mImgRect.top) else mImgRect.bottom - mWidgetRect.bottom).toInt() + distanceY = if (velocityY < 0) Int.MAX_VALUE - distanceY else distanceY + var minY = if (velocityY < 0) distanceY else 0 + var maxY = if (velocityY < 0) Int.MAX_VALUE else distanceY + val overY = if (velocityY < 0) Int.MAX_VALUE - minY else distanceY + if (velocityX == 0f) { + maxX = 0 + minX = 0 + } + if (velocityY == 0f) { + maxY = 0 + minY = 0 + } + mFlingScroller.fling( + mLastFlingX, + mLastFlingY, + velocityX.toInt(), + velocityY.toInt(), + minX, + maxX, + minY, + maxY, + if (abs(overX) < MAX_FLING_OVER_SCROLL * 2) 0 else MAX_FLING_OVER_SCROLL, + if (abs(overY) < MAX_FLING_OVER_SCROLL * 2) 0 else MAX_FLING_OVER_SCROLL + ) + } + + fun start() { + isRunning = true + postExecute() + } + + fun stop() { + removeCallbacks(this) + mTranslateScroller.abortAnimation() + mScaleScroller.abortAnimation() + mFlingScroller.abortAnimation() + mRotateScroller.abortAnimation() + isRunning = false + } + + override fun run() { + + // if (!isRuning) return; + var endAnima = true + if (mScaleScroller.computeScrollOffset()) { + mScale = mScaleScroller.currX / 10000f + endAnima = false + } + if (mTranslateScroller.computeScrollOffset()) { + val tx = mTranslateScroller.currX - mLastTranslateX + val ty = mTranslateScroller.currY - mLastTranslateY + mTranslateX += tx + mTranslateY += ty + mLastTranslateX = mTranslateScroller.currX + mLastTranslateY = mTranslateScroller.currY + endAnima = false + } + if (mFlingScroller.computeScrollOffset()) { + val x = mFlingScroller.currX - mLastFlingX + val y = mFlingScroller.currY - mLastFlingY + mLastFlingX = mFlingScroller.currX + mLastFlingY = mFlingScroller.currY + mTranslateX += x + mTranslateY += y + endAnima = false + } + if (mRotateScroller.computeScrollOffset()) { + mDegrees = mRotateScroller.currX.toFloat() + endAnima = false + } + if (mClipScroller.computeScrollOffset() || mClip != null) { + val sx = mClipScroller.currX / 10000f + val sy = mClipScroller.currY / 10000f + mTmpMatrix.setScale( + sx, + sy, + (mImgRect.left + mImgRect.right) / 2, + c!!.calculateTop() + ) + mTmpMatrix.mapRect(mClipRect, mImgRect) + if (sx == 1f) { + mClipRect.left = mWidgetRect.left + mClipRect.right = mWidgetRect.right + } + if (sy == 1f) { + mClipRect.top = mWidgetRect.top + mClipRect.bottom = mWidgetRect.bottom + } + mClip = mClipRect + } + if (!endAnima) { + applyAnima() + postExecute() + } else { + isRunning = false + + // 修复动画结束后边距有些空隙, + var needFix = false + if (imgLargeWidth) { + if (mImgRect.left > 0) { + mTranslateX -= mImgRect.left.toInt() + } else if (mImgRect.right < mWidgetRect.width()) { + mTranslateX -= (mWidgetRect.width() - mImgRect.right).toInt() + } + needFix = true + } + if (imgLargeHeight) { + if (mImgRect.top > 0) { + mTranslateY -= mImgRect.top.toInt() + } else if (mImgRect.bottom < mWidgetRect.height()) { + mTranslateY -= (mWidgetRect.height() - mImgRect.bottom).toInt() + } + needFix = true + } + if (needFix) { + applyAnima() + } + invalidate() + mCompleteCallBack?.let { + it.run() + mCompleteCallBack = null + } + } + } + + private fun applyAnima() { + mAnimMatrix.reset() + mAnimMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top) + mAnimMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y) + mAnimMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight) + mAnimMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y) + mAnimMatrix.postScale(mScale, mScale, mScaleCenter.x, mScaleCenter.y) + mAnimMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat()) + executeTranslate() + } + + private fun postExecute() { + if (isRunning) post(this) + } + + } + + fun getInfo(): Info { + val rect = RectF() + val p = IntArray(2) + getLocation(this, p) + rect[p[0] + mImgRect.left, p[1] + mImgRect.top, p[0] + mImgRect.right] = + p[1] + mImgRect.bottom + return Info( + rect, + mImgRect, + mWidgetRect, + mBaseRect, + mScreenCenter, + mScale, + mDegrees, + mScaleType + ) + } + + fun getImageViewInfo(imgView: ImageView): Info? { + val p = IntArray(2) + getLocation(imgView, p) + val drawable: Drawable = imgView.drawable + val matrix: Matrix = imgView.imageMatrix + val width = getDrawableWidth(drawable) + val height = getDrawableHeight(drawable) + val imgRect = RectF(0F, 0F, width.toFloat(), height.toFloat()) + matrix.mapRect(imgRect) + val rect = RectF( + p[0] + imgRect.left, + p[1] + imgRect.top, + p[0] + imgRect.right, + p[1] + imgRect.bottom + ) + val widgetRect = RectF(0F, 0F, imgView.width.toFloat(), imgView.height.toFloat()) + val baseRect = RectF(widgetRect) + val screenCenter = PointF(widgetRect.width() / 2, widgetRect.height() / 2) + return Info( + rect, + imgRect, + widgetRect, + baseRect, + screenCenter, + 1F, + 0F, + imgView.scaleType + ) + } + + private fun getLocation(target: View, position: IntArray) { + position[0] += target.left + position[1] += target.top + var viewParent: ViewParent = target.parent + while (viewParent is View) { + val view: View = viewParent + if (view.id == R.id.content) return + position[0] -= view.scrollX + position[1] -= view.scrollY + position[0] += view.left + position[1] += view.top + viewParent = view.parent + } + position[0] = (position[0] + 0.5f).toInt() + position[1] = (position[1] + 0.5f).toInt() + } + + private fun reset() { + mAnimMatrix.reset() + executeTranslate() + mScale = 1f + mTranslateX = 0 + mTranslateY = 0 + } + + interface ClipCalculate { + fun calculateTop(): Float + } + + inner class START : ClipCalculate { + override fun calculateTop(): Float { + return mImgRect.top + } + } + + inner class END : ClipCalculate { + override fun calculateTop(): Float { + return mImgRect.bottom + } + } + + inner class OTHER : ClipCalculate { + override fun calculateTop(): Float { + return (mImgRect.top + mImgRect.bottom) / 2 + } + } + + /** + * 在PhotoView内部还没有图片的时候同样可以调用该方法 + * + * + * 此时并不会播放动画,当给PhotoView设置图片后会自动播放动画。 + * + * + * 若等待时间过长也没有给控件设置图片,则会忽略该动画,若要再次播放动画则需要重新调用该方法 + * (等待的时间默认500毫秒,可以通过setMaxAnimFromWaiteTime(int)设置最大等待时间) + */ + fun animaFrom(info: Info) { + if (isInit) { + reset() + val mine = getInfo() + val scaleX = info.mImgRect.width() / mine.mImgRect.width() + val scaleY = info.mImgRect.height() / mine.mImgRect.height() + val scale = if (scaleX < scaleY) scaleX else scaleY + val ocx = info.mRect.left + info.mRect.width() / 2 + val ocy = info.mRect.top + info.mRect.height() / 2 + val mcx = mine.mRect.left + mine.mRect.width() / 2 + val mcy = mine.mRect.top + mine.mRect.height() / 2 + mAnimMatrix.reset() + // mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top); + mAnimMatrix.postTranslate(ocx - mcx, ocy - mcy) + mAnimMatrix.postScale(scale, scale, ocx, ocy) + mAnimMatrix.postRotate(info.mDegrees, ocx, ocy) + executeTranslate() + mScaleCenter[ocx] = ocy + mRotateCenter[ocx] = ocy + mTranslate.withTranslate(0, 0, (-(ocx - mcx)).toInt(), (-(ocy - mcy)).toInt()) + mTranslate.withScale(scale, 1F) + mTranslate.withRotate(info.mDegrees.toInt(), 0) + if (info.mWidgetRect.width() < info.mImgRect.width() || info.mWidgetRect.height() < info.mImgRect.height()) { + var clipX = info.mWidgetRect.width() / info.mImgRect.width() + var clipY = info.mWidgetRect.height() / info.mImgRect.height() + clipX = if (clipX > 1) 1F else clipX + clipY = if (clipY > 1) 1F else clipY + val c = + if (info.mScaleType == ScaleType.FIT_START) START() else if (info.mScaleType == ScaleType.FIT_END) END() else OTHER() + mTranslate.withClip(clipX, clipY, 1 - clipX, 1 - clipY, mAnimaDuring / 3, c) + mTmpMatrix.setScale( + clipX, + clipY, + (mImgRect.left + mImgRect.right) / 2, + c.calculateTop() + ) + mTmpMatrix.mapRect(mTranslate.mClipRect, mImgRect) + mClip = mTranslate.mClipRect + } + mTranslate.start() + } else { + mFromInfo = info + mInfoTime = System.currentTimeMillis() + } + } + + fun animaTo( + info: Info, + completeCallBack: Runnable + ) { + if (isInit) { + mTranslate.stop() + mTranslateX = 0 + mTranslateY = 0 + val tcx = info.mRect.left + info.mRect.width() / 2 + val tcy = info.mRect.top + info.mRect.height() / 2 + mScaleCenter[mImgRect.left + mImgRect.width() / 2] = + mImgRect.top + mImgRect.height() / 2 + mRotateCenter.set(mScaleCenter) + + // 将图片旋转回正常位置,用以计算 + mAnimMatrix.postRotate(-mDegrees, mScaleCenter.x, mScaleCenter.y) + mAnimMatrix.mapRect(mImgRect, mBaseRect) + + // 缩放 + val scaleX = info.mImgRect.width() / mBaseRect.width() + val scaleY = info.mImgRect.height() / mBaseRect.height() + val scale = if (scaleX > scaleY) scaleX else scaleY + mAnimMatrix.postRotate(mDegrees, mScaleCenter.x, mScaleCenter.y) + mAnimMatrix.mapRect(mImgRect, mBaseRect) + mDegrees = mDegrees % 360 + mTranslate.withTranslate( + 0, + 0, + (tcx - mScaleCenter.x).toInt(), + (tcy - mScaleCenter.y).toInt() + ) + mTranslate.withScale(mScale, scale) + mTranslate.withRotate(mDegrees.toInt(), info.mDegrees.toInt(), mAnimaDuring * 2 / 3) + if (info.mWidgetRect.width() < info.mRect.width() || info.mWidgetRect.height() < info.mRect.height()) { + var clipX = info.mWidgetRect.width() / info.mRect.width() + var clipY = info.mWidgetRect.height() / info.mRect.height() + clipX = if (clipX > 1) 1F else clipX + clipY = if (clipY > 1) 1F else clipY + val cx = clipX + val cy = clipY + val c = + if (info.mScaleType == ScaleType.FIT_START) START() else if (info.mScaleType == ScaleType.FIT_END) END() else OTHER() + postDelayed( + { mTranslate.withClip(1F, 1F, -1 + cx, -1 + cy, mAnimaDuring / 2, c) }, + mAnimaDuring / 2.toLong() + ) + } + mCompleteCallBack = completeCallBack + mTranslate.start() + } + } + + fun rotate(degrees: Float) { + mDegrees += degrees + val centerX = (mWidgetRect.left + mWidgetRect.width() / 2).toInt() + val centerY = (mWidgetRect.top + mWidgetRect.height() / 2).toInt() + mAnimMatrix.postRotate(degrees, centerX.toFloat(), centerY.toFloat()) + executeTranslate() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/image/photo/Info.kt b/app/src/main/java/io/legado/app/ui/widget/image/photo/Info.kt new file mode 100644 index 000000000..abd6f9080 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/image/photo/Info.kt @@ -0,0 +1,48 @@ +package io.legado.app.ui.widget.image.photo + +import android.graphics.PointF + +import android.graphics.RectF +import android.widget.ImageView + + +class Info( + rect: RectF, + img: RectF, + widget: RectF, + base: RectF, + screenCenter: PointF, + scale: Float, + degrees: Float, + scaleType: ImageView.ScaleType? +) { + // 内部图片在整个手机界面的位置 + var mRect = RectF() + + // 控件在窗口的位置 + var mImgRect = RectF() + + var mWidgetRect = RectF() + + var mBaseRect = RectF() + + var mScreenCenter = PointF() + + var mScale = 0f + + var mDegrees = 0f + + var mScaleType: ImageView.ScaleType? = null + + init { + mRect.set(rect) + mImgRect.set(img) + mWidgetRect.set(widget) + mScale = scale + mScaleType = scaleType + mDegrees = degrees + mBaseRect.set(base) + mScreenCenter.set(screenCenter) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/image/photo/RotateGestureDetector.kt b/app/src/main/java/io/legado/app/ui/widget/image/photo/RotateGestureDetector.kt new file mode 100644 index 000000000..4bc309b56 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/image/photo/RotateGestureDetector.kt @@ -0,0 +1,54 @@ +package io.legado.app.ui.widget.image.photo + +import android.view.MotionEvent +import kotlin.math.abs +import kotlin.math.atan + +class RotateGestureDetector(private val mListener: OnRotateListener) { + + private val MAX_DEGREES_STEP = 120 + + private var mPrevSlope = 0f + private var mCurrSlope = 0f + + private val x1 = 0f + private val y1 = 0f + private val x2 = 0f + private val y2 = 0f + + fun onTouchEvent(event: MotionEvent) { + + when (event.actionMasked) { + MotionEvent.ACTION_POINTER_DOWN, + MotionEvent.ACTION_POINTER_UP -> { + if (event.pointerCount == 2) mPrevSlope = caculateSlope(event) + } + MotionEvent.ACTION_MOVE -> if (event.pointerCount > 1) { + mCurrSlope = caculateSlope(event) + + val currDegrees = Math.toDegrees(atan(mCurrSlope.toDouble())); + val prevDegrees = Math.toDegrees(atan(mPrevSlope.toDouble())); + + val deltaSlope = currDegrees - prevDegrees; + + if (abs(deltaSlope) <= MAX_DEGREES_STEP) { + mListener?.onRotate(deltaSlope.toFloat(), (x2 + x1) / 2, (y2 + y1) / 2); + } + mPrevSlope = mCurrSlope; + } + } + + } + + private fun caculateSlope(event: MotionEvent): Float { + val x1 = event.getX(0); + val y1 = event.getY(0); + val x2 = event.getX(1); + val y2 = event.getY(1); + return (y2 - y1) / (x2 - x1); + } +} + +interface OnRotateListener { + fun onRotate(degrees: Float, focusX: Float, focusY: Float) +} \ No newline at end of file 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..8f293eebb 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,16 +14,20 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.jaredrummler.android.colorpicker.* import io.legado.app.lib.theme.ATH +import io.legado.app.utils.ColorUtils class ColorPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), ColorPickerDialogListener { + var onSaveColor: ((color: Int) -> Boolean)? = null + private val sizeNormal = 0 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 +46,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 +55,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 +73,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 +84,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 +117,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 +130,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) } } @@ -134,6 +142,10 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex } override fun onColorSelected(dialogId: Int, @ColorInt color: Int) { + //返回值为true时说明已经处理过,不再处理 + if (onSaveColor?.invoke(color) == true) { + return + } saveValue(color) } @@ -147,8 +159,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 +243,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 +267,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/EditTextPreferenceDialog.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreferenceDialog.kt new file mode 100644 index 000000000..abbff9a87 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreferenceDialog.kt @@ -0,0 +1,29 @@ +package io.legado.app.ui.widget.prefs + +import android.app.Dialog +import android.os.Bundle +import androidx.preference.EditTextPreferenceDialogFragmentCompat +import androidx.preference.PreferenceDialogFragmentCompat +import io.legado.app.lib.theme.ATH + +class EditTextPreferenceDialog : EditTextPreferenceDialogFragmentCompat() { + + companion object { + + fun newInstance(key: String): EditTextPreferenceDialog { + val fragment = EditTextPreferenceDialog() + val b = Bundle(1) + b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key) + fragment.arguments = b + return fragment + } + + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.window?.setBackgroundDrawable(ATH.getDialogBackground()) + return dialog + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt index fa8b4eeb7..7747c6bf2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt @@ -10,14 +10,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView -import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity import androidx.preference.ListPreference import androidx.preference.PreferenceViewHolder import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.lib.theme.primaryColor import io.legado.app.utils.getCompatDrawable import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_icon_preference.view.* @@ -109,7 +110,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference return "icon_$key" } - class IconDialog : DialogFragment() { + class IconDialog : BaseDialogFragment() { var onChanged: ((value: String) -> Unit)? = null var dialogValue: String? = null @@ -135,8 +136,8 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference return inflater.inflate(R.layout.dialog_recycler_view, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + tool_bar.setBackgroundColor(primaryColor) tool_bar.setTitle(R.string.change_icon) recycler_view.layoutManager = LinearLayoutManager(requireContext()) val adapter = Adapter(requireContext()) diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ListPreferenceDialog.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ListPreferenceDialog.kt new file mode 100644 index 000000000..520d49d68 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ListPreferenceDialog.kt @@ -0,0 +1,29 @@ +package io.legado.app.ui.widget.prefs + +import android.app.Dialog +import android.os.Bundle +import androidx.preference.ListPreferenceDialogFragmentCompat +import androidx.preference.PreferenceDialogFragmentCompat +import io.legado.app.lib.theme.ATH + +class ListPreferenceDialog : ListPreferenceDialogFragmentCompat() { + + companion object { + + fun newInstance(key: String?): ListPreferenceDialog { + val fragment = ListPreferenceDialog() + val b = Bundle(1) + b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key) + fragment.arguments = b + return fragment + } + + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.window?.setBackgroundDrawable(ATH.getDialogBackground()) + return dialog + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/MultiSelectListPreferenceDialog.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/MultiSelectListPreferenceDialog.kt new file mode 100644 index 000000000..6336120b0 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/MultiSelectListPreferenceDialog.kt @@ -0,0 +1,32 @@ +package io.legado.app.ui.widget.prefs + +import android.app.Dialog +import android.os.Bundle +import androidx.preference.MultiSelectListPreferenceDialogFragmentCompat +import androidx.preference.PreferenceDialogFragmentCompat +import io.legado.app.lib.theme.ATH + +class MultiSelectListPreferenceDialog : MultiSelectListPreferenceDialogFragmentCompat() { + + companion object { + + fun newInstance(key: String?): MultiSelectListPreferenceDialog { + val fragment = + MultiSelectListPreferenceDialog() + val b = Bundle(1) + b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key) + fragment.arguments = b + return fragment + } + + } + + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + dialog.window?.setBackgroundDrawable(ATH.getDialogBackground()) + return dialog + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt index 0763c8474..bb1e5d791 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt @@ -6,20 +6,43 @@ import android.widget.TextView import androidx.preference.ListPreference import androidx.preference.PreferenceViewHolder import io.legado.app.R +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.utils.ColorUtils class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) { + private val isBottomBackground: Boolean + init { layoutResource = R.layout.view_preference widgetLayoutResource = R.layout.item_fillet_text + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference) + isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false) + typedArray.recycle() } override fun onBindViewHolder(holder: PreferenceViewHolder?) { - val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.text_view) + val v = Preference.bindView( + context, + holder, + icon, + title, + summary, + widgetLayoutResource, + R.id.text_view, + isBottomBackground = isBottomBackground + ) if (v is TextView) { v.text = entry + if (isBottomBackground) { + val bgColor = context.bottomBackground + val pTextColor = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor)) + v.setTextColor(pTextColor) + } } super.onBindViewHolder(holder) } + } \ No newline at end of file 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 0190dcd9f..042b03868 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,47 +8,73 @@ 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 import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.lib.theme.getSecondaryTextColor +import io.legado.app.utils.ColorUtils import org.jetbrains.anko.layoutInflater +import org.jetbrains.anko.sdk27.listeners.onLongClick import kotlin.math.roundToInt -class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Preference(context, attrs) { +class Preference(context: Context, attrs: AttributeSet) : + androidx.preference.Preference(context, attrs) { + + var onLongClick: (() -> Unit)? = null + private val isBottomBackground: Boolean init { - // isPersistent = true layoutResource = R.layout.view_preference + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference) + isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false) + typedArray.recycle() } companion object { - fun bindView(context: Context, it: PreferenceViewHolder?, icon: Drawable?, title: CharSequence?, summary: CharSequence?, weightLayoutRes: Int?, viewId: Int?, - weightWidth: Int = 0, weightHeight: Int = 0): T? { + fun bindView( + context: Context, + it: PreferenceViewHolder?, + icon: Drawable?, + title: CharSequence?, + summary: CharSequence?, + weightLayoutRes: Int? = null, + viewId: Int? = null, + weightWidth: Int = 0, + weightHeight: Int = 0, + isBottomBackground: Boolean = false + ): T? { if (it == null) return null - val view = it.findViewById(R.id.preference_title) - if (view is TextView) { - view.text = title - view.isVisible = title != null && title.isNotEmpty() - - val tvSummary = it.findViewById(R.id.preference_desc) - if (tvSummary is TextView) { - tvSummary.text = summary - tvSummary.isVisible = summary != null && summary.isNotEmpty() - } - - val iconView = it.findViewById(R.id.preference_icon) - if (iconView is ImageView) { - iconView.isVisible = icon != null && icon.isVisible - iconView.setImageDrawable(icon) - iconView.setColorFilter(context.accentColor) - } + val tvTitle = it.findViewById(R.id.preference_title) as TextView + tvTitle.text = title + tvTitle.isVisible = title != null && title.isNotEmpty() + val tvSummary = it.findViewById(R.id.preference_desc) as? TextView + tvSummary?.let { + tvSummary.text = summary + tvSummary.isGone = summary.isNullOrEmpty() + } + if (isBottomBackground && !tvTitle.isInEditMode) { + val isLight = ColorUtils.isColorLight(context.bottomBackground) + val pTextColor = context.getPrimaryTextColor(isLight) + tvTitle.setTextColor(pTextColor) + val sTextColor = context.getSecondaryTextColor(isLight) + tvSummary?.setTextColor(sTextColor) + } + val iconView = it.findViewById(R.id.preference_icon) + if (iconView is ImageView) { + iconView.isVisible = icon != null + 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 @@ -57,16 +83,20 @@ class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Pr lay.addView(childView) lay.isVisible = true v = lay.findViewById(viewId) - } + } else + needRequestLayout = true if (weightWidth > 0 || weightHeight > 0) { val lp = lay.layoutParams if (weightHeight > 0) - lp.height = (context.resources.displayMetrics.density * weightHeight).roundToInt() + lp.height = + (context.resources.displayMetrics.density * weightHeight).roundToInt() if (weightWidth > 0) - lp.width = (context.resources.displayMetrics.density * weightWidth).roundToInt() + lp.width = + (context.resources.displayMetrics.density * weightWidth).roundToInt() lay.layoutParams = lp - } + } else if (needRequestLayout) + v.requestLayout() return v } @@ -78,8 +108,19 @@ class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Pr } override fun onBindViewHolder(holder: PreferenceViewHolder?) { - bindView(context, holder, icon, title, summary, null, null) + bindView( + context, + holder, + icon, + title, + summary, + isBottomBackground = isBottomBackground + ) super.onBindViewHolder(holder) + holder?.itemView?.onLongClick { + onLongClick?.invoke() + true + } } } 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 a7dcd2e6c..a21d0e1ce 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 @@ -1,7 +1,6 @@ package io.legado.app.ui.widget.prefs import android.content.Context -import android.graphics.Color import android.util.AttributeSet import android.view.View import android.widget.TextView @@ -9,7 +8,10 @@ import androidx.core.view.isVisible import androidx.preference.PreferenceCategory import androidx.preference.PreferenceViewHolder import io.legado.app.R +import io.legado.app.help.AppConfig import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.utils.ColorUtils class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCategory(context, attrs) { @@ -25,15 +27,24 @@ class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCate val view = it.findViewById(R.id.preference_title) if (view is TextView) { // && !view.isInEditMode view.text = title - view.setTextColor(context.accentColor) //设置title文本的颜色 + 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) + val dividerColor = if (AppConfig.isNightTheme) { + ColorUtils.shiftColor(context.backgroundColor, 1.05f) + } else { + ColorUtils.shiftColor(context.backgroundColor, 0.95f) + } if (da is View) { + da.setBackgroundColor(dividerColor) da.isVisible = it.isDividerAllowedAbove } val db = it.findViewById(R.id.preference_divider_below) if (db is View) { + db.setBackgroundColor(dividerColor) db.isVisible = it.isDividerAllowedBelow } } 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..3ff87e05b 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 @@ -12,8 +12,13 @@ import io.legado.app.lib.theme.accentColor class SwitchPreference(context: Context, attrs: AttributeSet) : SwitchPreferenceCompat(context, attrs) { + private val isBottomBackground: Boolean + init { layoutResource = R.layout.view_preference + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference) + isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false) + typedArray.recycle() } override fun onBindViewHolder(holder: PreferenceViewHolder?) { @@ -24,9 +29,10 @@ class SwitchPreference(context: Context, attrs: AttributeSet) : title, summary, widgetLayoutResource, - R.id.switchWidget + R.id.switchWidget, + isBottomBackground = isBottomBackground ) - 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/DragSelectTouchHelper.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/DragSelectTouchHelper.kt new file mode 100644 index 000000000..015595ec1 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/DragSelectTouchHelper.kt @@ -0,0 +1,976 @@ +/* + * Copyright 2020 Mupceet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.legado.app.ui.widget.recycler + +import android.content.res.Resources +import android.text.TextUtils +import android.util.DisplayMetrics +import android.util.Log +import android.util.TypedValue +import android.view.MotionEvent +import android.view.View +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener +import io.legado.app.BuildConfig +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper.AdvanceCallback.Mode +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * @author mupceet + * !autoChangeMode +-------------------+ inactiveSelect() + * +------------------------------------> | | <--------------------+ + * | | Normal | | + * | activeDragSelect(position) | | activeSlideSelect() | + * | +------------------------------ | | ----------+ | + * | v +-------------------+ v | + * +-------------------+ autoChangeMode +-----------------------+ + * | Drag From Disable | ----------------------------------------------> | | + * +-------------------+ | | + * | | | | + * | | activeDragSelect(position) && allowDragInSlide | Slide | + * | | <---------------------------------------------- | | + * | Drag From Slide | | | + * | | | | + * | | ----------------------------------------------> | | + * +-------------------+ +-----------------------+ + */ +@Suppress("unused") +class DragSelectTouchHelper( + /** + * Developer callback which controls the behavior of DragSelectTouchHelper. + */ + private val mCallback: Callback, +) { + + companion object { + private const val TAG = "DSTH" + private const val MAX_HOTSPOT_RATIO = 0.5f + private val DEFAULT_EDGE_TYPE = EdgeType.INSIDE_EXTEND + private const val DEFAULT_HOTSPOT_RATIO = 0.2f + private const val DEFAULT_HOTSPOT_OFFSET = 0 + private const val DEFAULT_MAX_SCROLL_VELOCITY = 10 + private const val SELECT_STATE_NORMAL = 0x00 + private const val SELECT_STATE_SLIDE = 0x01 + private const val SELECT_STATE_DRAG_FROM_NORMAL = 0x10 + private const val SELECT_STATE_DRAG_FROM_SLIDE = 0x11 + } + + private val mDisplayMetrics: DisplayMetrics = Resources.getSystem().displayMetrics + + /** + * Start of the slide area. + */ + private var mSlideAreaLeft = 0f + + /** + * End of the slide area. + */ + private var mSlideAreaRight = 0f + + /** + * The hotspot height by the ratio of RecyclerView. + */ + private var mHotspotHeightRatio = 0f + + /** + * The hotspot height. + */ + private var mHotspotHeight = 0f + + /** + * The hotspot offset. + */ + private var mHotspotOffset = 0f + + /** + * Whether should continue scrolling when move outside top hotspot region. + */ + private var mScrollAboveTopRegion = false + + /** + * Whether should continue scrolling when move outside bottom hotspot region. + */ + private var mScrollBelowBottomRegion = false + + /** + * The maximum velocity of auto scrolling. + */ + private var mMaximumVelocity = 0 + + /** + * Whether should auto enter slide mode after drag select finished. + */ + private var mShouldAutoChangeState = false + + /** + * Whether can drag selection in slide select mode. + */ + private var mIsAllowDragInSlideState = false + private var mRecyclerView: RecyclerView? = null + + /** + * The coordinate of hotspot area. + */ + private var mTopRegionFrom = -1f + private var mTopRegionTo = -1f + private var mBottomRegionFrom = -1f + private var mBottomRegionTo = -1f + private val mOnLayoutChangeListener = + View.OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + if (oldLeft != left || oldRight != right || oldTop != top || oldBottom != bottom) { + if (v === mRecyclerView) { + Logger.i("onLayoutChange:new: " + + left + " " + top + " " + right + " " + bottom) + Logger.i("onLayoutChange:old: " + + oldLeft + " " + oldTop + " " + oldRight + " " + oldBottom) + init(bottom - top) + } + } + } + + /** + * The current mode of selection. + */ + private var mSelectState = SELECT_STATE_NORMAL + + /** + * Whether is in top hotspot area. + */ + private var mIsInTopHotspot = false + + /** + * Whether is in bottom hotspot area. + */ + private var mIsInBottomHotspot = false + + /** + * Indicates automatically scroll. + */ + private var mIsScrolling = false + + /** + * The actual speed of the current moment. + */ + private var mScrollDistance = 0 + + /** + * The reference coordinate for the action start, used to avoid reverse scrolling. + */ + private var mDownY = Float.MIN_VALUE + + /** + * The reference coordinates for the last action. + */ + private var mLastX = Float.MIN_VALUE + private var mLastY = Float.MIN_VALUE + + /** + * The selected items position. + */ + private var mStart = RecyclerView.NO_POSITION + private var mEnd = RecyclerView.NO_POSITION + private var mLastRealStart = RecyclerView.NO_POSITION + private var mLastRealEnd = RecyclerView.NO_POSITION + private var mSlideStateStartPosition = RecyclerView.NO_POSITION + private var mHaveCalledSelectStart = false + private val mScrollRunnable: Runnable by lazy { + object : Runnable { + override fun run() { + if (mIsScrolling) { + scrollBy(mScrollDistance) + ViewCompat.postOnAnimation(mRecyclerView!!, this) + } + } + } + } + private val mOnItemTouchListener: OnItemTouchListener by lazy { + object : OnItemTouchListener { + override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { + Logger.d("onInterceptTouchEvent: x:" + e.x + ",y:" + e.y + + ", " + MotionEvent.actionToString(e.action)) + val adapter = rv.adapter + if (adapter == null || adapter.itemCount == 0) { + return false + } + var intercept = false + val action = e.action + when (action and MotionEvent.ACTION_MASK) { + MotionEvent.ACTION_DOWN -> { + mDownY = e.y + // call the selection start's callback before moving + if (mSelectState == SELECT_STATE_SLIDE && isInSlideArea(e)) { + mSlideStateStartPosition = getItemPosition(rv, e) + if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { + mCallback.onSelectStart(mSlideStateStartPosition) + mHaveCalledSelectStart = true + } + intercept = true + } + } + MotionEvent.ACTION_MOVE -> if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL + || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE + ) { + Logger.i("onInterceptTouchEvent: drag mode move") + intercept = true + } + MotionEvent.ACTION_UP -> { + if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL + || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE + ) { + intercept = true + } + // finger is lifted before moving + if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { + selectFinished(mSlideStateStartPosition) + mSlideStateStartPosition = RecyclerView.NO_POSITION + } + // selection has triggered + if (mStart != RecyclerView.NO_POSITION) { + selectFinished(mEnd) + } + } + MotionEvent.ACTION_CANCEL -> { + if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { + selectFinished(mSlideStateStartPosition) + mSlideStateStartPosition = RecyclerView.NO_POSITION + } + if (mStart != RecyclerView.NO_POSITION) { + selectFinished(mEnd) + } + } + else -> { + } + } + // Intercept only when the selection is triggered + Logger.d("intercept result: $intercept") + return intercept + } + + override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) { + if (!isActivated) { + return + } + Logger.d("onTouchEvent: x:" + e.x + ",y:" + e.y + + ", " + MotionEvent.actionToString(e.action)) + val action = e.action + when (action and MotionEvent.ACTION_MASK) { + MotionEvent.ACTION_MOVE -> { + if (mSlideStateStartPosition != RecyclerView.NO_POSITION) { + selectFirstItem(mSlideStateStartPosition) + // selection is triggered + mSlideStateStartPosition = RecyclerView.NO_POSITION + Logger.i("onTouchEvent: after slide mode down") + } + processAutoScroll(e) + if (!mIsInTopHotspot && !mIsInBottomHotspot) { + updateSelectedRange(rv, e) + } + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> selectFinished(mEnd) + else -> { + } + } + } + + override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + if (disallowIntercept) { + inactiveSelect() + } + } + } + } + + init { + setHotspotRatio(DEFAULT_HOTSPOT_RATIO) + setHotspotOffset(DEFAULT_HOTSPOT_OFFSET) + setMaximumVelocity(DEFAULT_MAX_SCROLL_VELOCITY) + setEdgeType(DEFAULT_EDGE_TYPE) + setAutoEnterSlideState(false) + setAllowDragInSlideState(false) + setSlideArea(0, 0) + } + + /** + * Attaches the DragSelectTouchHelper to the provided RecyclerView. If TouchHelper is already + * attached to a RecyclerView, it will first detach from the previous one. You can call this + * method with `null` to detach it from the current RecyclerView. + * + * @param recyclerView The RecyclerView instance to which you want to add this helper or + * `null` if you want to remove DragSelectTouchHelper from the + * current RecyclerView. + */ + fun attachToRecyclerView(recyclerView: RecyclerView?) { + if (mRecyclerView === recyclerView) { + return // nothing to do + } + mRecyclerView?.removeOnItemTouchListener(mOnItemTouchListener) + mRecyclerView = recyclerView + mRecyclerView?.let { + it.addOnItemTouchListener(mOnItemTouchListener) + it.addOnLayoutChangeListener(mOnLayoutChangeListener) + } + } + + /** + * Activate the slide selection mode. + */ + fun activeSlideSelect() { + activeSelectInternal(RecyclerView.NO_POSITION) + } + + /** + * Activate the selection mode with selected item position. Normally called on long press. + * + * @param position Indicates the position of selected item. + */ + fun activeDragSelect(position: Int) { + activeSelectInternal(position) + } + + /** + * Exit the selection mode. + */ + fun inactiveSelect() { + if (isActivated) { + selectFinished(mEnd) + } else { + selectFinished(RecyclerView.NO_POSITION) + } + Logger.logSelectStateChange(mSelectState, SELECT_STATE_NORMAL) + mSelectState = SELECT_STATE_NORMAL + } + + /** + * To determine whether it is in the selection mode. + * + * @return true if is in the selection mode. + */ + val isActivated: Boolean + get() = mSelectState != SELECT_STATE_NORMAL + + /** + * Sets hotspot height by ratio of RecyclerView. + * + * @param ratio range (0, 0.5). + * @return The select helper, which may used to chain setter calls. + */ + fun setHotspotRatio(ratio: Float): DragSelectTouchHelper { + mHotspotHeightRatio = ratio + return this + } + + /** + * Sets hotspot height. + * + * @param hotspotHeight hotspot height which unit is dp. + * @return The select helper, which may used to chain setter calls. + */ + fun setHotspotHeight(hotspotHeight: Int): DragSelectTouchHelper { + mHotspotHeight = dp2px(hotspotHeight.toFloat()).toFloat() + return this + } + + /** + * Sets hotspot offset. It don't need to be set if no special requirement. + * + * @param hotspotOffset hotspot offset which unit is dp. + * @return The select helper, which may used to chain setter calls. + */ + fun setHotspotOffset(hotspotOffset: Int): DragSelectTouchHelper { + mHotspotOffset = dp2px(hotspotOffset.toFloat()).toFloat() + return this + } + + /** + * Sets the activation edge type, one of: + * + * * [EdgeType.INSIDE] for edges that respond to touches inside + * the bounds of the host view. If touch moves outside the bounds, scrolling + * will stop. + * * [EdgeType.INSIDE_EXTEND] for inside edges that continued to + * scroll when touch moves outside the bounds of the host view. + * + * + * @param type The type of edge to use. + * @return The select helper, which may used to chain setter calls. + */ + fun setEdgeType(type: EdgeType?): DragSelectTouchHelper { + when (type) { + EdgeType.INSIDE -> { + mScrollAboveTopRegion = false + mScrollBelowBottomRegion = false + } + EdgeType.INSIDE_EXTEND -> { + mScrollAboveTopRegion = true + mScrollBelowBottomRegion = true + } + else -> { + mScrollAboveTopRegion = true + mScrollBelowBottomRegion = true + } + } + return this + } + + /** + * Sets sliding area's start and end, has been considered RTL situation + * + * @param startDp The start of the sliding area + * @param endDp The end of the sliding area + * @return The select helper, which may used to chain setter calls. + */ + fun setSlideArea(startDp: Int, endDp: Int): DragSelectTouchHelper { + if (!isRtl) { + mSlideAreaLeft = dp2px(startDp.toFloat()).toFloat() + mSlideAreaRight = dp2px(endDp.toFloat()).toFloat() + } else { + val displayWidth = mDisplayMetrics.widthPixels + mSlideAreaLeft = displayWidth - dp2px(endDp.toFloat()).toFloat() + mSlideAreaRight = displayWidth - dp2px(startDp.toFloat()).toFloat() + } + return this + } + + /** + * Sets the maximum velocity for scrolling + * + * @param velocity maximum velocity + * @return The select helper, which may used to chain setter calls. + */ + fun setMaximumVelocity(velocity: Int): DragSelectTouchHelper { + mMaximumVelocity = (velocity * mDisplayMetrics.density + 0.5f).toInt() + return this + } + + /** + * Sets whether should auto enter slide mode after drag select finished. + * It's usefully for LinearLayout RecyclerView. + * + * @param autoEnterSlideState should auto enter slide mode + * @return The select helper, which may used to chain setter calls. + */ + fun setAutoEnterSlideState(autoEnterSlideState: Boolean): DragSelectTouchHelper { + mShouldAutoChangeState = autoEnterSlideState + return this + } + + /** + * Sets whether can drag selection in slide select mode. + * It's usefully for LinearLayout RecyclerView. + * + * @param allowDragInSlideState allow drag selection in slide select mode + * @return The select helper, which may used to chain setter calls. + */ + fun setAllowDragInSlideState(allowDragInSlideState: Boolean): DragSelectTouchHelper { + mIsAllowDragInSlideState = allowDragInSlideState + return this + } + + private fun init(rvHeight: Int) { + if (mHotspotOffset >= rvHeight * MAX_HOTSPOT_RATIO) { + mHotspotOffset = rvHeight * MAX_HOTSPOT_RATIO + } + // The height of hotspot area is not set, using (RV height x ratio) + if (mHotspotHeight <= 0) { + if (mHotspotHeightRatio <= 0 || mHotspotHeightRatio >= MAX_HOTSPOT_RATIO) { + mHotspotHeightRatio = DEFAULT_HOTSPOT_RATIO + } + mHotspotHeight = rvHeight * mHotspotHeightRatio + } else { + if (mHotspotHeight >= rvHeight * MAX_HOTSPOT_RATIO) { + mHotspotHeight = rvHeight * MAX_HOTSPOT_RATIO + } + } + mTopRegionFrom = mHotspotOffset + mTopRegionTo = mTopRegionFrom + mHotspotHeight + mBottomRegionTo = rvHeight - mHotspotOffset + mBottomRegionFrom = mBottomRegionTo - mHotspotHeight + if (mTopRegionTo > mBottomRegionFrom) { + mBottomRegionFrom = (rvHeight shr 1.toFloat().toInt()).toFloat() + mTopRegionTo = mBottomRegionFrom + } + Logger.d("Hotspot: [" + mTopRegionFrom + ", " + mTopRegionTo + "], [" + + mBottomRegionFrom + ", " + mBottomRegionTo + "]") + } + + private fun activeSelectInternal(position: Int) { + // We should initialize the hotspot here, because its data may be delayed load + mRecyclerView?.let { + init(it.height) + } + if (position == RecyclerView.NO_POSITION) { + Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE) + mSelectState = SELECT_STATE_SLIDE + } else { + if (!mHaveCalledSelectStart) { + mCallback.onSelectStart(position) + mHaveCalledSelectStart = true + } + if (mSelectState == SELECT_STATE_SLIDE) { + if (mIsAllowDragInSlideState && selectFirstItem(position)) { + Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_SLIDE) + mSelectState = SELECT_STATE_DRAG_FROM_SLIDE + } + } else if (mSelectState == SELECT_STATE_NORMAL) { + if (selectFirstItem(position)) { + Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_NORMAL) + mSelectState = SELECT_STATE_DRAG_FROM_NORMAL + } + } else { + Logger.e("activeSelect in unexpected state: $mSelectState") + } + } + } + + private fun selectFirstItem(position: Int): Boolean { + val selectFirstItemSucceed = mCallback.onSelectChange(position, true) + // The drag select feature is only available if the first item is available for selection + if (selectFirstItemSucceed) { + mStart = position + mEnd = position + mLastRealStart = position + mLastRealEnd = position + } + return selectFirstItemSucceed + } + + private fun updateSelectedRange(rv: RecyclerView, e: MotionEvent) { + updateSelectedRange(rv, e.x, e.y) + } + + private fun updateSelectedRange(rv: RecyclerView, x: Float, y: Float) { + val position = getItemPosition(rv, x, y) + if (position != RecyclerView.NO_POSITION && mEnd != position) { + mEnd = position + notifySelectRangeChange() + } + } + + private fun notifySelectRangeChange() { + if (mStart == RecyclerView.NO_POSITION || mEnd == RecyclerView.NO_POSITION) { + return + } + val newStart: Int = min(mStart, mEnd) + val newEnd: Int = max(mStart, mEnd) + if (mLastRealStart == RecyclerView.NO_POSITION || mLastRealEnd == RecyclerView.NO_POSITION) { + if (newEnd - newStart == 1) { + notifySelectChange(newStart, newStart, true) + } else { + notifySelectChange(newStart, newEnd, true) + } + } else { + if (newStart > mLastRealStart) { + notifySelectChange(mLastRealStart, newStart - 1, false) + } else if (newStart < mLastRealStart) { + notifySelectChange(newStart, mLastRealStart - 1, true) + } + if (newEnd > mLastRealEnd) { + notifySelectChange(mLastRealEnd + 1, newEnd, true) + } else if (newEnd < mLastRealEnd) { + notifySelectChange(newEnd + 1, mLastRealEnd, false) + } + } + mLastRealStart = newStart + mLastRealEnd = newEnd + } + + private fun notifySelectChange(start: Int, end: Int, newState: Boolean) { + for (i in start..end) { + mCallback.onSelectChange(i, newState) + } + } + + private fun selectFinished(lastItem: Int) { + if (lastItem != RecyclerView.NO_POSITION) { + mCallback.onSelectEnd(lastItem) + } + mStart = RecyclerView.NO_POSITION + mEnd = RecyclerView.NO_POSITION + mLastRealStart = RecyclerView.NO_POSITION + mLastRealEnd = RecyclerView.NO_POSITION + mHaveCalledSelectStart = false + mIsInTopHotspot = false + mIsInBottomHotspot = false + stopAutoScroll() + when (mSelectState) { + SELECT_STATE_DRAG_FROM_NORMAL -> mSelectState = if (mShouldAutoChangeState) { + Logger.logSelectStateChange( + mSelectState, + SELECT_STATE_SLIDE) + SELECT_STATE_SLIDE + } else { + Logger.logSelectStateChange( + mSelectState, + SELECT_STATE_NORMAL) + SELECT_STATE_NORMAL + } + SELECT_STATE_DRAG_FROM_SLIDE -> { + Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE) + mSelectState = SELECT_STATE_SLIDE + } + else -> { + } + } + } + + /** + * Process motion event, according to the location to determine whether to scroll + */ + private fun processAutoScroll(e: MotionEvent) { + val y = e.y + if (y in mTopRegionFrom..mTopRegionTo && y < mDownY) { + mLastX = e.x + mLastY = e.y + val scrollDistanceFactor = (y - mTopRegionTo) / mHotspotHeight + mScrollDistance = (mMaximumVelocity * scrollDistanceFactor).toInt() + if (!mIsInTopHotspot) { + mIsInTopHotspot = true + startAutoScroll() + mDownY = mTopRegionTo + } + } else if (mScrollAboveTopRegion && y < mTopRegionFrom && mIsInTopHotspot) { + mLastX = e.x + mLastY = mTopRegionFrom + // Use the maximum speed + mScrollDistance = mMaximumVelocity * -1 + startAutoScroll() + } else if (y in mBottomRegionFrom..mBottomRegionTo && y > mDownY) { + mLastX = e.x + mLastY = e.y + val scrollDistanceFactor = (y - mBottomRegionFrom) / mHotspotHeight + mScrollDistance = (mMaximumVelocity * scrollDistanceFactor).toInt() + if (!mIsInBottomHotspot) { + mIsInBottomHotspot = true + startAutoScroll() + mDownY = mBottomRegionFrom + } + } else if (mScrollBelowBottomRegion && y > mBottomRegionTo && mIsInBottomHotspot) { + mLastX = e.x + mLastY = mBottomRegionTo + // Use the maximum speed + mScrollDistance = mMaximumVelocity + startAutoScroll() + } else { + mIsInTopHotspot = false + mIsInBottomHotspot = false + mLastX = Float.MIN_VALUE + mLastY = Float.MIN_VALUE + stopAutoScroll() + } + } + + private fun startAutoScroll() { + if (!mIsScrolling) { + mIsScrolling = true + mRecyclerView!!.removeCallbacks(mScrollRunnable) + ViewCompat.postOnAnimation(mRecyclerView!!, mScrollRunnable) + } + } + + private fun stopAutoScroll() { + if (mIsScrolling) { + mIsScrolling = false + mRecyclerView?.removeCallbacks(mScrollRunnable) + } + } + + private fun scrollBy(distance: Int) { + val scrollDistance: Int = + if (distance > 0) { + min(distance, mMaximumVelocity) + } else { + max(distance, -mMaximumVelocity) + } + mRecyclerView!!.scrollBy(0, scrollDistance) + if (mLastX != Float.MIN_VALUE && mLastY != Float.MIN_VALUE) { + updateSelectedRange(mRecyclerView!!, mLastX, mLastY) + } + } + + private fun dp2px(dpVal: Float): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, mDisplayMetrics).toInt() + } + + private val isRtl: Boolean + get() = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) + == View.LAYOUT_DIRECTION_RTL) + + private fun isInSlideArea(e: MotionEvent): Boolean { + val x = e.x + return x > mSlideAreaLeft && x < mSlideAreaRight + } + + private fun getItemPosition(rv: RecyclerView, e: MotionEvent): Int { + return getItemPosition(rv, e.x, e.y) + } + + private fun getItemPosition(rv: RecyclerView, x: Float, y: Float): Int { + val v = rv.findChildViewUnder(x, y) + if (v == null) { + val layoutManager = rv.layoutManager + if (layoutManager is GridLayoutManager) { + val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() + val lastItemPosition = layoutManager.getItemCount() - 1 + if (lastItemPosition == lastVisibleItemPosition) { + return lastItemPosition + } + } + return RecyclerView.NO_POSITION + } + return rv.getChildAdapterPosition(v) + } + + /** + * Edge type that specifies an activation area starting at the view bounds and extending inward. + */ + enum class EdgeType { + /** + * After activation begins, moving outside the view bounds will stop scrolling. + */ + INSIDE, + + /** + * After activation begins, moving outside the view bounds will continue scrolling. + */ + INSIDE_EXTEND + } + + /** + * This class is the contract between DragSelectTouchHelper and your application. It lets you + * update adapter when selection start/end and state changed. + */ + abstract class Callback { + /** + * Called when changing item state. + * + * @param position this item want to change the state to new state. + * @param isSelected true if the position should be selected, false otherwise. + * @return Whether to set the new state successfully. + */ + abstract fun onSelectChange(position: Int, isSelected: Boolean): Boolean + + /** + * Called when selection start. + * + * @param start the first selected item. + */ + open fun onSelectStart(start: Int) {} + + /** + * Called when selection end. + * + * @param end the last selected item. + */ + open fun onSelectEnd(end: Int) {} + } + + /** + * An advance Callback which provide 4 useful selection modes [Mode]. + * + * + * Note: Since the state of item may be repeatedly set, in order to improve efficiency, + * please process it in the Adapter + */ + abstract class AdvanceCallback : Callback { + private var mMode: Mode? = null + private var mOriginalSelection: MutableSet = mutableSetOf() + private var mFirstWasSelected = false + + /** + * Creates a SimpleCallback with default [Mode.SelectAndReverse]# mode. + * + * @see Mode + */ + constructor() { + setMode(Mode.SelectAndReverse) + } + + /** + * Creates a SimpleCallback with select mode. + * + * @param mode the initial select mode + * @see Mode + */ + constructor(mode: Mode?) { + setMode(mode) + } + + /** + * Sets the select mode. + * + * @param mode The type of select mode. + * @see Mode + */ + fun setMode(mode: Mode?) { + mMode = mode + } + + override fun onSelectStart(start: Int) { + mOriginalSelection.clear() + val selected = currentSelectedId() + if (selected != null) { + mOriginalSelection.addAll(selected) + } + mFirstWasSelected = mOriginalSelection.contains(getItemId(start)) + } + + override fun onSelectEnd(end: Int) { + mOriginalSelection.clear() + } + + override fun onSelectChange(position: Int, isSelected: Boolean): Boolean { + return when (mMode) { + Mode.SelectAndKeep -> { + updateSelectState(position, true) + } + Mode.SelectAndReverse -> { + updateSelectState(position, isSelected) + } + Mode.SelectAndUndo -> { + if (isSelected) { + updateSelectState(position, true) + } else { + updateSelectState(position, + mOriginalSelection.contains(getItemId(position))) + } + } + Mode.ToggleAndKeep -> { + updateSelectState(position, !mFirstWasSelected) + } + Mode.ToggleAndReverse -> { + if (isSelected) { + updateSelectState(position, !mFirstWasSelected) + } else { + updateSelectState(position, mFirstWasSelected) + } + } + Mode.ToggleAndUndo -> { + if (isSelected) { + updateSelectState(position, !mFirstWasSelected) + } else { + updateSelectState(position, + mOriginalSelection.contains(getItemId(position))) + } + } + else -> // SelectAndReverse Mode + updateSelectState(position, isSelected) + } + } + + /** + * Get the currently selected items when selecting first item. + * + * @return the currently selected item's id set. + */ + abstract fun currentSelectedId(): Set? + + /** + * Get the ID of the item. + * + * @param position item position to be judged. + * @return item's identity. + */ + abstract fun getItemId(position: Int): T + + /** + * Update the selection status of the position. + * + * @param position the position who's selection state changed. + * @param isSelected true if the position should be selected, false otherwise. + * @return Whether to set the state successfully. + */ + abstract fun updateSelectState(position: Int, isSelected: Boolean): Boolean + + /** + * Different existing selection modes + */ + enum class Mode { + /** + * Selects the first item and applies the same state to each item you go by + * and keep the state on move back + */ + SelectAndKeep, + + /** + * Selects the first item and applies the same state to each item you go by + * and applies inverted state on move back + */ + SelectAndReverse, + + /** + * Selects the first item and applies the same state to each item you go by + * and reverts to the original state on move back + */ + SelectAndUndo, + + /** + * Toggles the first item and applies the same state to each item you go by + * and keep the state on move back + */ + ToggleAndKeep, + + /** + * Toggles the first item and applies the same state to each item you go by + * and applies inverted state on move back + */ + ToggleAndReverse, + + /** + * Toggles the first item and applies the same state to each item you go by + * and reverts to the original state on move back + */ + ToggleAndUndo + } + } + + private object Logger { + private val DEBUG = BuildConfig.DEBUG + fun d(msg: String) { + if (DEBUG) { + Log.d(TAG, msg) + } + } + + fun e(msg: String) { + Log.e(TAG, msg) + } + + fun i(msg: String) { + Log.i(TAG, msg) + } + + fun logSelectStateChange(before: Int, after: Int) { + i("Select state changed: " + stateName(before) + " --> " + stateName(after)) + } + + private fun stateName(state: Int): String { + return when (state) { + SELECT_STATE_NORMAL -> "NormalState" + SELECT_STATE_SLIDE -> "SlideState" + SELECT_STATE_DRAG_FROM_NORMAL -> "DragFromNormal" + SELECT_STATE_DRAG_FROM_SLIDE -> "DragFromSlide" + else -> "Unknown" + } + } + } + + +} \ 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/ui/widget/recycler/HeaderAdapterDataObserver.kt similarity index 85% rename from app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt rename to app/src/main/java/io/legado/app/ui/widget/recycler/HeaderAdapterDataObserver.kt index 24f0c0f2b..f8e2a0c8c 100644 --- a/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/HeaderAdapterDataObserver.kt @@ -1,10 +1,10 @@ -package io.legado.app.help +package io.legado.app.ui.widget.recycler import androidx.recyclerview.widget.RecyclerView -internal class AdapterDataObserverHeader( - var adapterDataObserver: RecyclerView.AdapterDataObserver, - var headerCount: Int +internal class HeaderAdapterDataObserver( + 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/ItemTouchCallback.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/ItemTouchCallback.kt similarity index 99% rename from app/src/main/java/io/legado/app/help/ItemTouchCallback.kt rename to app/src/main/java/io/legado/app/ui/widget/recycler/ItemTouchCallback.kt index 9c1a0bbf2..b015a0086 100644 --- a/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/ItemTouchCallback.kt @@ -1,4 +1,4 @@ -package io.legado.app.help +package io.legado.app.ui.widget.recycler import androidx.recyclerview.widget.GridLayoutManager diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt index 0df53aba7..0af559c45 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt @@ -4,12 +4,7 @@ import android.content.Context import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller -class UpLinearLayoutManager: LinearLayoutManager { - val context: Context - - constructor(context: Context) : super(context) { - this.context = context - } +class UpLinearLayoutManager(val context: Context) : LinearLayoutManager(context) { fun smoothScrollToPosition(position: Int) { smoothScrollToPosition(position, 0) 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/ui/widget/text/AccentBgTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/AccentBgTextView.kt index b2dcc2578..f6fc05cd4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/AccentBgTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/AccentBgTextView.kt @@ -5,9 +5,9 @@ import android.graphics.Color import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView import io.legado.app.R -import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.Selector import io.legado.app.lib.theme.ThemeStore +import io.legado.app.utils.ColorUtils import io.legado.app.utils.dp import io.legado.app.utils.getCompatColor @@ -33,8 +33,8 @@ class AccentBgTextView(context: Context, attrs: AttributeSet?) : background = if (isInEditMode) { Selector.shapeBuild() .setCornerRadius(radius) - .setDefaultBgColor(context.getCompatColor(R.color.colorAccent)) - .setPressedBgColor(ColorUtils.darkenColor(context.getCompatColor(R.color.colorAccent))) + .setDefaultBgColor(context.getCompatColor(R.color.accent)) + .setPressedBgColor(ColorUtils.darkenColor(context.getCompatColor(R.color.accent))) .create() } else { Selector.shapeBuild() diff --git a/app/src/main/java/io/legado/app/ui/widget/text/AccentTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/AccentTextView.kt index f797a9f57..be3074766 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/AccentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/AccentTextView.kt @@ -15,7 +15,7 @@ class AccentTextView(context: Context, attrs: AttributeSet?) : if (!isInEditMode) { textColor = context.accentColor } else { - textColorResource = R.color.colorAccent + textColorResource = R.color.accent } } diff --git a/app/src/main/java/io/legado/app/ui/widget/text/BadgeView.kt b/app/src/main/java/io/legado/app/ui/widget/text/BadgeView.kt index 33fc8b3a2..c24e13e87 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/BadgeView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/BadgeView.kt @@ -148,7 +148,11 @@ class BadgeView @JvmOverloads constructor( } fun setHighlight(highlight: Boolean) { - setBackground(context.getCompatColor(if (highlight) R.color.highlight else R.color.darker_gray)) + if (highlight) { + setBackground(context.accentColor) + } else { + setBackground(context.getCompatColor(R.color.darker_gray)) + } } fun setBadgeMargin(dipMargin: Int) { diff --git a/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt index 839ca59bd..56e36f07d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt @@ -5,7 +5,7 @@ import android.graphics.Canvas import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView -class MultilineTextView(context: Context?, attrs: AttributeSet?) : +class MultilineTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { override fun onDraw(canvas: Canvas?) { diff --git a/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt index d0b579cd4..4a9da6e0f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt @@ -6,7 +6,7 @@ import android.util.AttributeSet import android.view.MotionEvent import androidx.appcompat.widget.AppCompatTextView -class ScrollTextView(context: Context?, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { +class ScrollTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { //滑动距离的最大边界 private var mOffsetHeight = 0 diff --git a/app/src/main/java/io/legado/app/ui/widget/text/StrokeTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/StrokeTextView.kt index 3fa0544d3..62f8f18f2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/StrokeTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/StrokeTextView.kt @@ -4,8 +4,8 @@ import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatTextView import io.legado.app.R -import io.legado.app.lib.theme.Selector -import io.legado.app.lib.theme.ThemeStore +import io.legado.app.lib.theme.* +import io.legado.app.utils.ColorUtils import io.legado.app.utils.dp import io.legado.app.utils.getCompatColor @@ -13,10 +13,13 @@ open class StrokeTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { private var radius = 1.dp + private val isBottomBackground: Boolean init { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StrokeTextView) radius = typedArray.getDimensionPixelOffset(R.styleable.StrokeTextView_radius, radius) + isBottomBackground = + typedArray.getBoolean(R.styleable.StrokeTextView_isBottomBackground, false) typedArray.recycle() upBackground() } @@ -27,38 +30,59 @@ open class StrokeTextView(context: Context, attrs: AttributeSet?) : } private fun upBackground() { - if (isInEditMode) { - background = Selector.shapeBuild() - .setCornerRadius(radius) - .setStrokeWidth(1.dp) - .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500)) - .setDefaultStrokeColor(context.getCompatColor(R.color.tv_text_secondary)) - .setSelectedStrokeColor(context.getCompatColor(R.color.colorAccent)) - .setPressedBgColor(context.getCompatColor(R.color.transparent30)) - .create() - this.setTextColor( - Selector.colorBuild() - .setDefaultColor(context.getCompatColor(R.color.tv_text_secondary)) - .setSelectedColor(context.getCompatColor(R.color.colorAccent)) - .setDisabledColor(context.getCompatColor(R.color.md_grey_500)) + when { + isInEditMode -> { + background = Selector.shapeBuild() + .setCornerRadius(radius) + .setStrokeWidth(1.dp) + .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500)) + .setDefaultStrokeColor(context.getCompatColor(R.color.secondaryText)) + .setSelectedStrokeColor(context.getCompatColor(R.color.accent)) + .setPressedBgColor(context.getCompatColor(R.color.transparent30)) .create() - ) - } else { - background = Selector.shapeBuild() - .setCornerRadius(radius) - .setStrokeWidth(1.dp) - .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500)) - .setDefaultStrokeColor(ThemeStore.textColorSecondary(context)) - .setSelectedStrokeColor(ThemeStore.accentColor(context)) - .setPressedBgColor(context.getCompatColor(R.color.transparent30)) - .create() - this.setTextColor( - Selector.colorBuild() - .setDefaultColor(ThemeStore.textColorSecondary(context)) - .setSelectedColor(ThemeStore.accentColor(context)) - .setDisabledColor(context.getCompatColor(R.color.md_grey_500)) + this.setTextColor( + Selector.colorBuild() + .setDefaultColor(context.getCompatColor(R.color.secondaryText)) + .setSelectedColor(context.getCompatColor(R.color.accent)) + .setDisabledColor(context.getCompatColor(R.color.md_grey_500)) + .create() + ) + } + isBottomBackground -> { + val isLight = ColorUtils.isColorLight(context.bottomBackground) + background = Selector.shapeBuild() + .setCornerRadius(radius) + .setStrokeWidth(1.dp) + .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500)) + .setDefaultStrokeColor(context.getPrimaryTextColor(isLight)) + .setSelectedStrokeColor(context.accentColor) + .setPressedBgColor(context.getCompatColor(R.color.transparent30)) .create() - ) + this.setTextColor( + Selector.colorBuild() + .setDefaultColor(context.getPrimaryTextColor(isLight)) + .setSelectedColor(context.accentColor) + .setDisabledColor(context.getCompatColor(R.color.md_grey_500)) + .create() + ) + } + else -> { + background = Selector.shapeBuild() + .setCornerRadius(radius) + .setStrokeWidth(1.dp) + .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500)) + .setDefaultStrokeColor(ThemeStore.textColorSecondary(context)) + .setSelectedStrokeColor(ThemeStore.accentColor(context)) + .setPressedBgColor(context.getCompatColor(R.color.transparent30)) + .create() + this.setTextColor( + Selector.colorBuild() + .setDefaultColor(ThemeStore.textColorSecondary(context)) + .setSelectedColor(ThemeStore.accentColor(context)) + .setDisabledColor(context.getCompatColor(R.color.md_grey_500)) + .create() + ) + } } } } 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/ColorUtils.kt b/app/src/main/java/io/legado/app/utils/ColorUtils.kt index 9eceb6724..8a9a35052 100644 --- a/app/src/main/java/io/legado/app/utils/ColorUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ColorUtils.kt @@ -4,9 +4,8 @@ import android.graphics.Color import androidx.annotation.ColorInt import androidx.annotation.FloatRange -import kotlin.math.max -import kotlin.math.min -import kotlin.math.roundToInt +import java.util.* +import kotlin.math.* @Suppress("unused") object ColorUtils { @@ -82,4 +81,172 @@ object ColorUtils { return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt()) } + /** + * 按条件的到随机颜色 + * + * @param alpha 透明 + * @param lower 下边界 + * @param upper 上边界 + * @return 颜色值 + */ + fun getRandomColor(alpha: Int, lower: Int, upper: Int): Int { + return RandomColor(alpha, lower, upper).color + } + + /** + * @return 获取随机色 + */ + fun getRandomColor(): Int { + return RandomColor(255, 80, 200).color + } + + + /** + * 随机颜色 + */ + class RandomColor(alpha: Int, lower: Int, upper: Int) { + private var alpha: Int = 0 + private var lower: Int = 0 + private var upper: Int = 0 + + //随机数是前闭 后开 + val color: Int + get() { + val red = getLower() + Random().nextInt(getUpper() - getLower() + 1) + val green = getLower() + Random().nextInt(getUpper() - getLower() + 1) + val blue = getLower() + Random().nextInt(getUpper() - getLower() + 1) + + return Color.argb(getAlpha(), red, green, blue) + } + + init { + require(upper > lower) { "must be lower < upper" } + setAlpha(alpha) + setLower(lower) + setUpper(upper) + } + + private fun getAlpha(): Int { + return alpha + } + + private fun setAlpha(alpha: Int) { + var alpha1 = alpha + if (alpha1 > 255) alpha1 = 255 + if (alpha1 < 0) alpha1 = 0 + this.alpha = alpha1 + } + + private fun getLower(): Int { + return lower + } + + private fun setLower(lower: Int) { + var lower1 = lower + if (lower1 < 0) lower1 = 0 + this.lower = lower1 + } + + private fun getUpper(): Int { + return upper + } + + private fun setUpper(upper: Int) { + var upper1 = upper + if (upper1 > 255) upper1 = 255 + this.upper = upper1 + } + } + + fun argb(R: Int, G: Int, B: Int): Int { + return argb(Byte.MAX_VALUE.toInt(), R, G, B) + } + + fun argb(A: Int, R: Int, G: Int, B: Int): Int { + val colorByteArr = + byteArrayOf(A.toByte(), R.toByte(), G.toByte(), B.toByte()) + return byteArrToInt(colorByteArr) + } + + fun rgb(argb: Int): IntArray? { + return intArrayOf(argb shr 16 and 0xFF, argb shr 8 and 0xFF, argb and 0xFF) + } + + fun byteArrToInt(colorByteArr: ByteArray): Int { + return ((colorByteArr[0].toInt() shl 24) + (colorByteArr[1].toInt() and 0xFF shl 16) + + (colorByteArr[2].toInt() and 0xFF shl 8) + (colorByteArr[3].toInt() and 0xFF)) + } + + fun rgb2lab(R: Int, G: Int, B: Int): IntArray { + val x: Float + val y: Float + val z: Float + val fx: Float + val fy: Float + val fz: Float + val xr: Float + val yr: Float + val zr: Float + val ls: Float + val `as`: Float + val bs: Float + val eps = 216f / 24389f + val k = 24389f / 27f + val xr1 = 0.964221f // reference white D50 + val yr1 = 1.0f + val zr1 = 0.825211f + + // RGB to XYZ + var r: Float = R / 255f //R 0..1 + var g: Float = G / 255f //G 0..1 + var b: Float = B / 255f //B 0..1 + + // assuming sRGB (D65) + r = if (r <= 0.04045) r / 12 else ((r + 0.055) / 1.055).pow(2.4).toFloat() + g = if (g <= 0.04045) g / 12 else ((g + 0.055) / 1.055).pow(2.4).toFloat() + b = if (b <= 0.04045) b / 12 else ((b + 0.055) / 1.055).pow(2.4).toFloat() + x = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b + y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b + z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b + + // XYZ to Lab + xr = x / xr1 + yr = y / yr1 + zr = z / zr1 + fx = if (xr > eps) xr.toDouble().pow(1 / 3.0) + .toFloat() else ((k * xr + 16.0) / 116.0).toFloat() + fy = if (yr > eps) yr.toDouble().pow(1 / 3.0) + .toFloat() else ((k * yr + 16.0) / 116.0).toFloat() + fz = if (zr > eps) zr.toDouble().pow(1 / 3.0) + .toFloat() else ((k * zr + 16.0) / 116).toFloat() + ls = 116 * fy - 16 + `as` = 500 * (fx - fy) + bs = 200 * (fy - fz) + val lab = IntArray(3) + lab[0] = (2.55 * ls + .5).toInt() + lab[1] = (`as` + .5).toInt() + lab[2] = (bs + .5).toInt() + return lab + } + + /** + * Computes the difference between two RGB colors by converting them to the L*a*b scale and + * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76} + */ + fun getColorDifference(a: Int, b: Int): Double { + val r1: Int = Color.red(a) + val g1: Int = Color.green(a) + val b1: Int = Color.blue(a) + val r2: Int = Color.red(b) + val g2: Int = Color.green(b) + val b2: Int = Color.blue(b) + val lab1 = rgb2lab(r1, g1, b1) + val lab2 = rgb2lab(r2, g2, b2) + return sqrt( + (lab2[0] - lab1[0].toDouble()) + .pow(2.0) + (lab2[1] - lab1[1].toDouble()) + .pow(2.0) + (lab2[2] - lab1[2].toDouble()) + .pow(2.0) + ) + } } diff --git a/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt new file mode 100644 index 000000000..b7ef740cc --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ConstraintUtil.kt @@ -0,0 +1,280 @@ +package io.legado.app.utils + +import androidx.annotation.IdRes +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.transition.TransitionManager + + +class ConstraintUtil(private val constraintLayout: ConstraintLayout) { + + private var begin: ConstraintBegin? = null + private val applyConstraintSet = ConstraintSet() + private val resetConstraintSet = ConstraintSet() + + init { + resetConstraintSet.clone(constraintLayout) + } + + /** + * 开始修改 + */ + fun begin(): ConstraintBegin { + synchronized(ConstraintBegin::class.java) { + if (begin == null) { + begin = ConstraintBegin(constraintLayout, applyConstraintSet) + } + } + applyConstraintSet.clone(constraintLayout) + return begin!! + } + + /** + * 带动画的修改 + * @return + */ + fun beginWithAnim(): ConstraintBegin? { + TransitionManager.beginDelayedTransition(constraintLayout) + return begin() + } + + /** + * 重置 + */ + fun reSet() { + resetConstraintSet.applyTo(constraintLayout) + } + + /** + * 带动画的重置 + */ + fun reSetWidthAnim() { + TransitionManager.beginDelayedTransition(constraintLayout) + resetConstraintSet.applyTo(constraintLayout) + } + +} + +class ConstraintBegin( + private val constraintLayout: ConstraintLayout, + private val applyConstraintSet: ConstraintSet +) { + + /** + * 清除关系

+ * 注意:这里不仅仅会清除关系,还会清除对应控件的宽高为 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 + } + + fun setHorizontalWeight(viewId: Int, weight: Float): ConstraintBegin { + applyConstraintSet.setHorizontalWeight(viewId, weight) + return this + } + + fun setVerticalWeight(viewId: Int, weight: Float): ConstraintBegin { + applyConstraintSet.setVerticalWeight(viewId, weight) + return this + } + + /** + * 为某个控件设置 margin + * @param viewId 某个控件ID + * @param left marginLeft + * @param top marginTop + * @param right marginRight + * @param bottom marginBottom + * @return + */ + fun setMargin( + @IdRes viewId: Int, + left: Int, + top: Int, + right: Int, + bottom: Int + ): ConstraintBegin { + setMarginLeft(viewId, left) + setMarginTop(viewId, top) + setMarginRight(viewId, right) + setMarginBottom(viewId, bottom) + return this + } + + /** + * 为某个控件设置 marginLeft + * @param viewId 某个控件ID + * @param left marginLeft + * @return + */ + fun setMarginLeft(@IdRes viewId: Int, left: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.LEFT, left) + return this + } + + /** + * 为某个控件设置 marginRight + * @param viewId 某个控件ID + * @param right marginRight + * @return + */ + fun setMarginRight(@IdRes viewId: Int, right: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.RIGHT, right) + return this + } + + /** + * 为某个控件设置 marginTop + * @param viewId 某个控件ID + * @param top marginTop + * @return + */ + fun setMarginTop(@IdRes viewId: Int, top: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.TOP, top) + return this + } + + /** + * 为某个控件设置marginBottom + * @param viewId 某个控件ID + * @param bottom marginBottom + * @return + */ + fun setMarginBottom(@IdRes viewId: Int, bottom: Int): ConstraintBegin { + applyConstraintSet.setMargin(viewId, ConstraintSet.BOTTOM, bottom) + return this + } + + /** + * 为某个控件设置关联关系 left_to_left_of + * @param startId + * @param endId + * @return + */ + fun leftToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.LEFT) + return this + } + + /** + * 为某个控件设置关联关系 left_to_right_of + * @param startId + * @param endId + * @return + */ + fun leftToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.RIGHT) + return this + } + + /** + * 为某个控件设置关联关系 top_to_top_of + * @param startId + * @param endId + * @return + */ + fun topToTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.TOP) + return this + } + + /** + * 为某个控件设置关联关系 top_to_bottom_of + * @param startId + * @param endId + * @return + */ + fun topToBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.BOTTOM) + return this + } + + /** + * 为某个控件设置关联关系 right_to_left_of + * @param startId + * @param endId + * @return + */ + fun rightToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.LEFT) + return this + } + + /** + * 为某个控件设置关联关系 right_to_right_of + * @param startId + * @param endId + * @return + */ + fun rightToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin { + applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.RIGHT) + return this + } + + /** + * 为某个控件设置关联关系 bottom_to_bottom_of + * @param startId + * @param endId + * @return + */ + fun 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 + } + + /** + * 为某个控件设置宽度 + * @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..489f5d508 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -1,8 +1,10 @@ @file:Suppress("unused") + 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 @@ -22,6 +24,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import io.legado.app.BuildConfig import io.legado.app.R import org.jetbrains.anko.defaultSharedPreferences +import org.jetbrains.anko.longToast import org.jetbrains.anko.toast import java.io.File import java.io.FileOutputStream @@ -56,8 +59,10 @@ fun Context.getPrefString(@StringRes keyId: Int, defValue: String? = null) = fun Context.putPrefString(key: String, value: String) = defaultSharedPreferences.edit { putString(key, value) } -fun Context.getPrefStringSet(key: String, defValue: MutableSet? = null) = - defaultSharedPreferences.getStringSet(key, defValue) +fun Context.getPrefStringSet( + key: String, + defValue: MutableSet? = null +): MutableSet? = defaultSharedPreferences.getStringSet(key, defValue) fun Context.putPrefStringSet(key: String, value: MutableSet) = defaultSharedPreferences.edit { putStringSet(key, value) } @@ -80,24 +85,25 @@ val Context.sysScreenOffTime: Int get() { var screenOffTime = 0 try { - screenOffTime = Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) + screenOffTime = + Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) } catch (e: Exception) { e.printStackTrace() } return screenOffTime -} + } val Context.statusBarHeight: Int get() { val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") return resources.getDimensionPixelSize(resourceId) -} + } val Context.navigationBarHeight: Int get() { val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") return resources.getDimensionPixelSize(resourceId) -} + } @SuppressLint("SetWorldReadable") fun Context.shareWithQr(title: String, text: String) { @@ -136,8 +142,18 @@ fun Context.sendToClip(text: String) { val clipData = ClipData.newPlainText(null, text) clipboard?.let { clipboard.setPrimaryClip(clipData) - toast(R.string.copy_complete) + longToast(R.string.copy_complete) + } +} + +fun Context.getClipText(): String? { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? + clipboard?.primaryClip?.let { + if (it.itemCount > 0) { + return it.getItemAt(0).text.toString().trim() + } } + return null } /** @@ -156,7 +172,13 @@ val Context.sysBattery: Int val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) val batteryStatus = registerReceiver(null, iFilter) return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 -} + } + +val Context.externalFilesDir: File + get() = this.getExternalFilesDir(null) ?: this.filesDir + +val Context.eCacheDir: File + get() = this.externalCacheDir ?: this.cacheDir fun Context.openUrl(url: String) { openUrl(Uri.parse(url)) @@ -178,4 +200,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/DocumentUtils.kt b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt index b53cebb14..041189c7f 100644 --- a/app/src/main/java/io/legado/app/utils/DocumentUtils.kt +++ b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt @@ -138,6 +138,8 @@ data class DocItem( val isDir: Boolean by lazy { DocumentsContract.Document.MIME_TYPE_DIR == attr } + + val isContentPath get() = uri.toString().isContentPath() } @Throws(Exception::class) diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt index a9408dd10..ef67fab8b 100644 --- a/app/src/main/java/io/legado/app/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -1,24 +1,30 @@ package io.legado.app.utils import android.os.Environment +import android.webkit.MimeTypeMap +import androidx.annotation.IntDef import io.legado.app.App -import java.io.File -import java.io.IOException +import io.legado.app.ui.filechooser.utils.ConvertUtils +import java.io.* +import java.nio.charset.Charset +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern @Suppress("unused") object FileUtils { - fun exists(root: File, fileName: String, vararg subDirs: String): Boolean { - return getFile(root, fileName, subDirs = *subDirs).exists() + fun exists(root: File, vararg subDirFiles: String): Boolean { + return getFile(root, *subDirFiles).exists() } - fun createFileIfNotExist(root: File, fileName: String, vararg subDirs: String): File { - val filePath = getPath(root, fileName, *subDirs) + fun createFileIfNotExist(root: File, vararg subDirFiles: String): File { + val filePath = getPath(root, *subDirFiles) return createFileIfNotExist(filePath) } fun createFolderIfNotExist(root: File, vararg subDirs: String): File { - val filePath = root.absolutePath + File.separator + subDirs.joinToString(File.separator) + val filePath = getPath(root, *subDirs) return createFolderIfNotExist(filePath) } @@ -49,22 +55,36 @@ object FileUtils { return file } - fun getFile(root: File, fileName: String, vararg subDirs: String): File { - val filePath = getPath(root, fileName, *subDirs) - return File(filePath) + fun createFileWithReplace(filePath: String) : File{ + val file = File(filePath) + if (!file.exists()) { + //创建父类文件夹 + file.parent?.let { + createFolderIfNotExist(it) + } + //创建文件 + file.createNewFile() + } + else{ + file.delete() + file.createNewFile() + } + return file } - fun getDirFile(root: File, vararg subDirs: String): File { - val filePath = getPath(root, subDirs = *subDirs) + fun getFile(root: File, vararg subDirFiles: String): File { + val filePath = getPath(root, *subDirFiles) return File(filePath) } - fun getPath(root: File, fileName: String? = null, vararg subDirs: String): String { - return if (fileName.isNullOrEmpty()) { - root.absolutePath + File.separator + subDirs.joinToString(File.separator) - } else { - root.absolutePath + File.separator + subDirs.joinToString(File.separator) + File.separator + fileName + fun getPath(root: File, vararg subDirFiles: String): String { + val path = StringBuilder(root.absolutePath) + subDirFiles.forEach { + if (it.isNotEmpty()) { + path.append(File.separator).append(it) + } } + return path.toString() } //递归删除文件夹下的数据 @@ -100,4 +120,656 @@ object FileUtils { return sdCardDirectory } + const val BY_NAME_ASC = 0 + const val BY_NAME_DESC = 1 + const val BY_TIME_ASC = 2 + const val BY_TIME_DESC = 3 + const val BY_SIZE_ASC = 4 + const val BY_SIZE_DESC = 5 + const val BY_EXTENSION_ASC = 6 + const val BY_EXTENSION_DESC = 7 + + @IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC]) + @kotlin.annotation.Retention(AnnotationRetention.SOURCE) + annotation class SortType + + /** + * 将目录分隔符统一为平台默认的分隔符,并为目录结尾添加分隔符 + */ + fun separator(path: String): String { + var path1 = path + val separator = File.separator + path1 = path1.replace("\\", separator) + if (!path1.endsWith(separator)) { + path1 += separator + } + return path1 + } + + fun closeSilently(c: Closeable?) { + if (c == null) { + return + } + try { + c.close() + } catch (ignored: IOException) { + } + + } + + /** + * 列出指定目录下的所有子目录 + */ + @JvmOverloads + fun listDirs( + startDirPath: String, + excludeDirs: Array? = null, @SortType sortType: Int = BY_NAME_ASC + ): Array { + var excludeDirs1 = excludeDirs + val dirList = ArrayList() + val startDir = File(startDirPath) + if (!startDir.isDirectory) { + return arrayOfNulls(0) + } + val dirs = startDir.listFiles(FileFilter { f -> + if (f == null) { + return@FileFilter false + } + f.isDirectory + }) + ?: return arrayOfNulls(0) + if (excludeDirs1 == null) { + excludeDirs1 = arrayOf() + } + for (dir in dirs) { + val file = dir.absoluteFile + if (!excludeDirs1.contentDeepToString().contains(file.name)) { + dirList.add(file) + } + } + when (sortType) { + BY_NAME_ASC -> Collections.sort(dirList, SortByName()) + BY_NAME_DESC -> { + Collections.sort(dirList, SortByName()) + dirList.reverse() + } + BY_TIME_ASC -> Collections.sort(dirList, SortByTime()) + BY_TIME_DESC -> { + Collections.sort(dirList, SortByTime()) + dirList.reverse() + } + BY_SIZE_ASC -> Collections.sort(dirList, SortBySize()) + BY_SIZE_DESC -> { + Collections.sort(dirList, SortBySize()) + dirList.reverse() + } + BY_EXTENSION_ASC -> Collections.sort(dirList, SortByExtension()) + BY_EXTENSION_DESC -> { + Collections.sort(dirList, SortByExtension()) + dirList.reverse() + } + } + return dirList.toTypedArray() + } + + /** + * 列出指定目录下的所有子目录及所有文件 + */ + @JvmOverloads + fun listDirsAndFiles( + startDirPath: String, + allowExtensions: Array? = null + ): Array? { + val dirs: Array? + val files: Array? = if (allowExtensions == null) { + listFiles(startDirPath) + } else { + listFiles(startDirPath, allowExtensions) + } + val dirsAndFiles: Array + dirs = listDirs(startDirPath) + if (files == null) { + return null + } + dirsAndFiles = arrayOfNulls(dirs.size + files.size) + System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.size) + System.arraycopy(files, 0, dirsAndFiles, dirs.size, files.size) + return dirsAndFiles + } + + /** + * 列出指定目录下的所有文件 + */ + @JvmOverloads + fun listFiles( + startDirPath: String, + filterPattern: Pattern? = null, @SortType sortType: Int = BY_NAME_ASC + ): Array { + val fileList = ArrayList() + val f = File(startDirPath) + if (!f.isDirectory) { + return arrayOfNulls(0) + } + val files = f.listFiles(FileFilter { file -> + if (file == null) { + return@FileFilter false + } + if (file.isDirectory) { + return@FileFilter false + } + + filterPattern?.matcher(file.name)?.find() ?: true + }) + ?: return arrayOfNulls(0) + for (file in files) { + fileList.add(file.absoluteFile) + } + when (sortType) { + BY_NAME_ASC -> Collections.sort(fileList, SortByName()) + BY_NAME_DESC -> { + Collections.sort(fileList, SortByName()) + fileList.reverse() + } + BY_TIME_ASC -> Collections.sort(fileList, SortByTime()) + BY_TIME_DESC -> { + Collections.sort(fileList, SortByTime()) + fileList.reverse() + } + BY_SIZE_ASC -> Collections.sort(fileList, SortBySize()) + BY_SIZE_DESC -> { + Collections.sort(fileList, SortBySize()) + fileList.reverse() + } + BY_EXTENSION_ASC -> Collections.sort(fileList, SortByExtension()) + BY_EXTENSION_DESC -> { + Collections.sort(fileList, SortByExtension()) + fileList.reverse() + } + } + return fileList.toTypedArray() + } + + /** + * 列出指定目录下的所有文件 + */ + fun listFiles(startDirPath: String, allowExtensions: Array?): Array? { + val file = File(startDirPath) + return file.listFiles { _, name -> + //返回当前目录所有以某些扩展名结尾的文件 + val extension = getExtension(name) + allowExtensions?.contentDeepToString()?.contains(extension) == true + || allowExtensions == null + } + } + + /** + * 列出指定目录下的所有文件 + */ + fun listFiles(startDirPath: String, allowExtension: String?): Array? { + return if (allowExtension == null) + listFiles(startDirPath, allowExtension = null) + else + listFiles(startDirPath, arrayOf(allowExtension)) + } + + /** + * 判断文件或目录是否存在 + */ + fun exist(path: String): Boolean { + val file = File(path) + return file.exists() + } + + /** + * 删除文件或目录 + */ + @JvmOverloads + fun delete(file: File, deleteRootDir: Boolean = false): Boolean { + var result = false + if (file.isFile) { + //是文件 + result = deleteResolveEBUSY(file) + } else { + //是目录 + val files = file.listFiles() ?: return false + if (files.isEmpty()) { + result = deleteRootDir && deleteResolveEBUSY(file) + } else { + for (f in files) { + delete(f, deleteRootDir) + result = deleteResolveEBUSY(f) + } + } + if (deleteRootDir) { + result = deleteResolveEBUSY(file) + } + } + return result + } + + /** + * bug: open failed: EBUSY (Device or resource busy) + * fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy + */ + private fun deleteResolveEBUSY(file: File): Boolean { + // Before you delete a Directory or File: rename it! + val to = File(file.absolutePath + System.currentTimeMillis()) + + file.renameTo(to) + return to.delete() + } + + /** + * 删除文件或目录 + */ + @JvmOverloads + fun delete(path: String, deleteRootDir: Boolean = false): Boolean { + val file = File(path) + + return if (file.exists()) { + delete(file, deleteRootDir) + } else false + } + + /** + * 复制文件为另一个文件,或复制某目录下的所有文件及目录到另一个目录下 + */ + fun copy(src: String, tar: String): Boolean { + val srcFile = File(src) + return srcFile.exists() && copy(srcFile, File(tar)) + } + + /** + * 复制文件或目录 + */ + fun copy(src: File, tar: File): Boolean { + try { + if (src.isFile) { + val `is` = FileInputStream(src) + val op = FileOutputStream(tar) + val bis = BufferedInputStream(`is`) + val bos = BufferedOutputStream(op) + val bt = ByteArray(1024 * 8) + while (true) { + val len = bis.read(bt) + if (len == -1) { + break + } else { + bos.write(bt, 0, len) + } + } + bis.close() + bos.close() + } else if (src.isDirectory) { + tar.mkdirs() + src.listFiles()?.forEach { file -> + copy(file.absoluteFile, File(tar.absoluteFile, file.name)) + } + } + return true + } catch (e: Exception) { + return false + } + + } + + /** + * 移动文件或目录 + */ + fun move(src: String, tar: String): Boolean { + return move(File(src), File(tar)) + } + + /** + * 移动文件或目录 + */ + fun move(src: File, tar: File): Boolean { + return rename(src, tar) + } + + /** + * 文件重命名 + */ + fun rename(oldPath: String, newPath: String): Boolean { + return rename(File(oldPath), File(newPath)) + } + + /** + * 文件重命名 + */ + fun rename(src: File, tar: File): Boolean { + return src.renameTo(tar) + } + + /** + * 读取文本文件, 失败将返回空串 + */ + @JvmOverloads + fun readText(filepath: String, charset: String = "utf-8"): String { + try { + val data = readBytes(filepath) + if (data != null) { + return String(data, Charset.forName(charset)).trim { it <= ' ' } + } + } catch (ignored: UnsupportedEncodingException) { + } + + return "" + } + + /** + * 读取文件内容, 失败将返回空串 + */ + fun readBytes(filepath: String): ByteArray? { + var fis: FileInputStream? = null + try { + fis = FileInputStream(filepath) + val baos = ByteArrayOutputStream() + val buffer = ByteArray(1024) + while (true) { + val len = fis.read(buffer, 0, buffer.size) + if (len == -1) { + break + } else { + baos.write(buffer, 0, len) + } + } + val data = baos.toByteArray() + baos.close() + return data + } catch (e: IOException) { + return null + } finally { + closeSilently(fis) + } + } + + /** + * 保存文本内容 + */ + @JvmOverloads + fun writeText(filepath: String, content: String, charset: String = "utf-8"): Boolean { + try { + writeBytes(filepath, content.toByteArray(charset(charset))) + return true + } catch (e: UnsupportedEncodingException) { + return false + } + + } + + /** + * 保存文件内容 + */ + fun writeBytes(filepath: String, data: ByteArray): Boolean { + val file = File(filepath) + var fos: FileOutputStream? = null + return try { + if (!file.exists()) { + file.parentFile?.mkdirs() + file.createNewFile() + } + fos = FileOutputStream(filepath) + fos.write(data) + true + } catch (e: IOException) { + false + } finally { + closeSilently(fos) + } + } + + /** + * 追加文本内容 + */ + fun appendText(path: String, content: String): Boolean { + val file = File(path) + var writer: FileWriter? = null + return try { + if (!file.exists()) { + + file.createNewFile() + } + writer = FileWriter(file, true) + writer.write(content) + true + } catch (e: IOException) { + false + } finally { + closeSilently(writer) + } + } + + /** + * 获取文件大小 + */ + fun getLength(path: String): Long { + val file = File(path) + return if (!file.isFile || !file.exists()) { + 0 + } else file.length() + } + + /** + * 获取文件或网址的名称(包括后缀) + */ + fun getName(pathOrUrl: String?): String { + if (pathOrUrl == null) { + return "" + } + val pos = pathOrUrl.lastIndexOf('/') + return if (0 <= pos) { + pathOrUrl.substring(pos + 1) + } else { + System.currentTimeMillis().toString() + "." + getExtension(pathOrUrl) + } + } + + /** + * 获取文件名(不包括扩展名) + */ + fun getNameExcludeExtension(path: String): String { + try { + var fileName = File(path).name + val lastIndexOf = fileName.lastIndexOf(".") + if (lastIndexOf != -1) { + fileName = fileName.substring(0, lastIndexOf) + } + return fileName + } catch (e: Exception) { + return "" + } + + } + + /** + * 获取格式化后的文件大小 + */ + fun getSize(path: String): String { + val fileSize = getLength(path) + return ConvertUtils.toFileSizeString(fileSize) + } + + /** + * 获取文件后缀,不包括“.” + */ + fun getExtension(pathOrUrl: String): String { + val dotPos = pathOrUrl.lastIndexOf('.') + return if (0 <= dotPos) { + pathOrUrl.substring(dotPos + 1) + } else { + "ext" + } + } + + /** + * 获取文件的MIME类型 + */ + fun getMimeType(pathOrUrl: String): String { + val ext = getExtension(pathOrUrl) + val map = MimeTypeMap.getSingleton() + return map.getMimeTypeFromExtension(ext) ?: "*/*" + } + + /** + * 获取格式化后的文件/目录创建或最后修改时间 + */ + @JvmOverloads + fun getDateTime(path: String, format: String = "yyyy年MM月dd日HH:mm"): String { + val file = File(path) + return getDateTime(file, format) + } + + /** + * 获取格式化后的文件/目录创建或最后修改时间 + */ + fun getDateTime(file: File, format: String): String { + val cal = Calendar.getInstance() + cal.timeInMillis = file.lastModified() + return SimpleDateFormat(format, Locale.PRC).format(cal.time) + } + + /** + * 比较两个文件的最后修改时间 + */ + fun compareLastModified(path1: String, path2: String): Int { + val stamp1 = File(path1).lastModified() + val stamp2 = File(path2).lastModified() + return when { + stamp1 > stamp2 -> { + 1 + } + stamp1 < stamp2 -> { + -1 + } + else -> { + 0 + } + } + } + + /** + * 创建多级别的目录 + */ + fun makeDirs(path: String): Boolean { + return makeDirs(File(path)) + } + + /** + * 创建多级别的目录 + */ + fun makeDirs(file: File): Boolean { + return file.mkdirs() + } + + class SortByExtension : Comparator { + + override fun compare(f1: File?, f2: File?): Int { + return if (f1 == null || f2 == null) { + if (f1 == null) { + -1 + } else { + 1 + } + } else { + if (f1.isDirectory && f2.isFile) { + -1 + } else if (f1.isFile && f2.isDirectory) { + 1 + } else { + f1.name.compareTo(f2.name, ignoreCase = true) + } + } + } + + } + + class SortByName : Comparator { + private var caseSensitive: Boolean = false + + constructor(caseSensitive: Boolean) { + this.caseSensitive = caseSensitive + } + + constructor() { + this.caseSensitive = false + } + + override fun compare(f1: File?, f2: File?): Int { + if (f1 == null || f2 == null) { + return if (f1 == null) { + -1 + } else { + 1 + } + } else { + return if (f1.isDirectory && f2.isFile) { + -1 + } else if (f1.isFile && f2.isDirectory) { + 1 + } else { + val s1 = f1.name + val s2 = f2.name + if (caseSensitive) { + s1.compareTo(s2) + } else { + s1.compareTo(s2, ignoreCase = true) + } + } + } + } + + } + + class SortBySize : Comparator { + + override fun compare(f1: File?, f2: File?): Int { + return if (f1 == null || f2 == null) { + if (f1 == null) { + -1 + } else { + 1 + } + } else { + if (f1.isDirectory && f2.isFile) { + -1 + } else if (f1.isFile && f2.isDirectory) { + 1 + } else { + if (f1.length() < f2.length()) { + -1 + } else { + 1 + } + } + } + } + + } + + class SortByTime : Comparator { + + override fun compare(f1: File?, f2: File?): Int { + return if (f1 == null || f2 == null) { + if (f1 == null) { + -1 + } else { + 1 + } + } else { + if (f1.isDirectory && f2.isFile) { + -1 + } else if (f1.isFile && f2.isDirectory) { + 1 + } else { + if (f1.lastModified() > f2.lastModified()) { + -1 + } else { + 1 + } + } + } + } + + } } diff --git a/app/src/main/java/io/legado/app/utils/GsonExtensions.kt b/app/src/main/java/io/legado/app/utils/GsonExtensions.kt index 13b105618..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,24 +1,28 @@ 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() } -inline fun genericType() = object : TypeToken() {}.type - +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()) @@ -26,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)) @@ -41,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/LanguageUtils.kt b/app/src/main/java/io/legado/app/utils/LanguageUtils.kt new file mode 100644 index 000000000..ae6c465a8 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/LanguageUtils.kt @@ -0,0 +1,67 @@ +package io.legado.app.utils + +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import io.legado.app.constant.PreferKey +import java.util.* + + +object LanguageUtils { + + /** + * 设置语言 + */ + fun setConfiguration(context: Context): Context { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val resources: Resources = context.resources + val targetLocale: Locale = when (context.getPrefString(PreferKey.language)) { + "zh" -> Locale.CHINESE + "tw" -> Locale.TRADITIONAL_CHINESE + "en" -> Locale.ENGLISH + else -> getSystemLocale() + } + val configuration: Configuration = resources.configuration + configuration.setLocale(targetLocale) + context.createConfigurationContext(configuration) + } else { + setConfigurationOld(context) + context + } + } + + /** + * 设置语言 + */ + fun setConfigurationOld(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + val resources: Resources = context.resources + val targetLocale: Locale = when (context.getPrefString(PreferKey.language)) { + "zh" -> Locale.CHINESE + "tw" -> Locale.TRADITIONAL_CHINESE + "en" -> Locale.ENGLISH + else -> getSystemLocale() + } + val configuration: Configuration = resources.configuration + @Suppress("DEPRECATION") + configuration.locale = targetLocale + @Suppress("DEPRECATION") + resources.updateConfiguration(configuration, resources.displayMetrics) + } + } + + /** + * 当前系统语言 + */ + private fun getSystemLocale(): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0有多语言设置获取顶部的语言 + Resources.getSystem().configuration.locales.get(0) + } else { + @Suppress("DEPRECATION") + Resources.getSystem().configuration.locale + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/LogUtils.kt b/app/src/main/java/io/legado/app/utils/LogUtils.kt index e70813dee..a96bc7c75 100644 --- a/app/src/main/java/io/legado/app/utils/LogUtils.kt +++ b/app/src/main/java/io/legado/app/utils/LogUtils.kt @@ -32,7 +32,7 @@ object LogUtils { private val fileHandler by lazy { val root = App.INSTANCE.externalCacheDir ?: return@lazy null val logFolder = FileUtils.createFolderIfNotExist(root, "logs") - val logPath = FileUtils.getPath(logFolder, "appLog") + val logPath = FileUtils.getPath(root = logFolder, "appLog") FileHandler(logPath, 10240, 10).apply { formatter = object : Formatter() { override fun format(record: LogRecord): String { diff --git a/app/src/main/java/io/legado/app/utils/MD5Utils.kt b/app/src/main/java/io/legado/app/utils/MD5Utils.kt index f0044a999..c0ce9febe 100644 --- a/app/src/main/java/io/legado/app/utils/MD5Utils.kt +++ b/app/src/main/java/io/legado/app/utils/MD5Utils.kt @@ -9,15 +9,15 @@ import java.security.NoSuchAlgorithmException @Suppress("unused") object MD5Utils { - fun md5Encode(str: String?): String? { - if (str == null) return null - var reStr: String? = null + fun md5Encode(str: String?): String { + if (str == null) return "" + var reStr = "" try { - val md5:MessageDigest = MessageDigest.getInstance("MD5") - val bytes:ByteArray = md5.digest(str.toByteArray()) - val stringBuffer:StringBuilder = StringBuilder() + val md5: MessageDigest = MessageDigest.getInstance("MD5") + val bytes: ByteArray = md5.digest(str.toByteArray()) + val stringBuffer: StringBuilder = StringBuilder() for (b in bytes) { - val bt:Int = b.toInt() and 0xff + val bt: Int = b.toInt() and 0xff if (bt < 16) { stringBuffer.append(0) } @@ -31,11 +31,9 @@ object MD5Utils { return reStr } - fun md5Encode16(str: String): String? { + fun md5Encode16(str: String): String { var reStr = md5Encode(str) - if (reStr != null) { - reStr = reStr.substring(8, 24) - } + reStr = reStr.substring(8, 24) return reStr } } 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..ace9e9531 100644 --- a/app/src/main/java/io/legado/app/utils/MenuExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/MenuExtensions.kt @@ -10,7 +10,6 @@ import androidx.core.view.forEach import io.legado.app.R import io.legado.app.constant.Theme import io.legado.app.lib.theme.DrawableUtils -import io.legado.app.lib.theme.primaryTextColor import java.lang.reflect.Method import java.util.* @@ -19,13 +18,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 defaultTextColor = context.getCompatColor(R.color.primaryText) + val tintColor = UIUtils.getMenuColor(context, theme) menu.forEach { item -> (item as MenuItemImpl).let { impl -> //overflow:展开的item @@ -42,7 +36,7 @@ fun Menu.applyTint(context: Context, theme: Theme = Theme.Auto): Menu = this.let fun Menu.applyOpenTint(context: Context) { //展开菜单显示图标 if (this.javaClass.simpleName.equals("MenuBuilder", ignoreCase = true)) { - val defaultTextColor = context.getCompatColor(R.color.tv_text_default) + val defaultTextColor = context.getCompatColor(R.color.primaryText) try { var method: Method = this.javaClass.getDeclaredMethod("setOptionalIconsVisible", java.lang.Boolean.TYPE) diff --git a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt index 0c59f05a3..4a3e236cb 100644 --- a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt +++ b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt @@ -11,9 +11,9 @@ import java.util.regex.Pattern @Suppress("unused") object NetworkUtils { fun getUrl(response: Response<*>): String { - val networkResponse = response.raw().networkResponse - return networkResponse?.request?.url?.toString() - ?: response.raw().request.url.toString() + val networkResponse = response.raw().networkResponse() + return networkResponse?.request()?.url()?.toString() + ?: response.raw().request().url().toString() } private val notNeedEncoding: BitSet by lazy { @@ -98,6 +98,14 @@ object NetworkUtils { } else url.substring(0, index) } + fun getSubDomain(url: String?): String { + var baseUrl = getBaseUrl(url) + if (baseUrl == null) return "" + return if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { + baseUrl.substring(baseUrl.lastIndexOf("/")+1) + } else baseUrl.substring(baseUrl.indexOf(".")+1) + } + /** * Get local Ip address. */ diff --git a/app/src/main/java/io/legado/app/utils/RealPathUtil.kt b/app/src/main/java/io/legado/app/utils/RealPathUtil.kt index 3eedc2fde..1f9998277 100644 --- a/app/src/main/java/io/legado/app/utils/RealPathUtil.kt +++ b/app/src/main/java/io/legado/app/utils/RealPathUtil.kt @@ -23,10 +23,7 @@ object RealPathUtil { private var filePathUri: Uri? = null @Suppress("DEPRECATION") - fun getPath( - context: Context, - uri: Uri - ): String? { + fun getPath(context: Context, uri: Uri): String? { //check here to KITKAT or new version @SuppressLint("ObsoleteSdkInt") val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT @@ -53,12 +50,16 @@ object RealPathUtil { val split = docId.split(":").toTypedArray() val type = split[0] var contentUri: Uri? = null - if ("image" == type) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } else if ("video" == type) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - } else if ("audio" == type) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + when (type) { + "image" -> { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + "video" -> { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } + "audio" -> { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } } val selection = "_id=?" val selectionArgs = arrayOf( diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index ae881eb64..dc53b7bc8 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -1,9 +1,24 @@ package io.legado.app.utils +import android.net.Uri +import java.io.File + +val removeHtmlRegex = "]*>".toRegex() +val imgRegex = "]*>".toRegex() +val notImgHtmlRegex = "]*>".toRegex() + fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.isContentPath(): Boolean = this?.startsWith("content://") == true +fun String.parseToUri(): Uri { + return if (isContentPath()) { + Uri.parse(this) + } else { + Uri.fromFile(File(this)) + } +} + fun String?.isAbsUrl() = this?.let { it.startsWith("http://", true) @@ -32,13 +47,16 @@ fun String?.isJsonArray(): Boolean = str.startsWith("[") && str.endsWith("]") } ?: false -fun String?.htmlFormat(): String = - this?.replace("(?i)<(br[\\s/]*|/*p\\b.*?|/*div\\b.*?)>".toRegex(), "\n") - ?.replace("<[script>]*.*?>| ".toRegex(), "") - ?.replace("\\s*\\n+\\s*".toRegex(), "\n  ") - ?.replace("^[\\n\\s]+".toRegex(), "  ") - ?.replace("[\\n\\s]+$".toRegex(), "") - ?: "" +fun String?.htmlFormat(): String { + this ?: return "" + return this + .replace(imgRegex, "\n$0\n") + .replace(removeHtmlRegex, "\n") + .replace(notImgHtmlRegex, "") + .replace("\\s*\\n+\\s*".toRegex(), "\n  ") + .replace("^[\\n\\s]+".toRegex(), "  ") + .replace("[\\n\\s]+$".toRegex(), "") +} fun String.splitNotBlank(vararg delimiter: String): Array = run { this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() @@ -48,25 +66,19 @@ fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array = run { this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() } +/** + * 将字符串拆分为单个字符,包含emoji + */ fun String.toStringArray(): Array { var codePointIndex = 0 - return Array(codePointCount(0, length)) { - substring( - codePointIndex, - offsetByCodePoints(codePointIndex, 1) - .apply { codePointIndex = this } - ) + return try { + Array(codePointCount(0, length)) { + val start = codePointIndex + codePointIndex = offsetByCodePoints(start, 1) + substring(start, codePointIndex) + } + } catch (e: Exception) { + split("").toTypedArray() } } -fun Char?.isHAN(): Boolean { - this ?: return false - val ub: Character.UnicodeBlock = Character.UnicodeBlock.of(this) ?: return false - return ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS - || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A - || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B - || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C - || ub === Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D - || ub === Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS - || ub === Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT -} diff --git a/app/src/main/java/io/legado/app/utils/StringUtils.kt b/app/src/main/java/io/legado/app/utils/StringUtils.kt index dda534eed..e5b658c21 100644 --- a/app/src/main/java/io/legado/app/utils/StringUtils.kt +++ b/app/src/main/java/io/legado/app/utils/StringUtils.kt @@ -214,7 +214,7 @@ object StringUtils { fun stringToInt(str: String?): Int { if (str != null) { - val num = fullToHalf(str).replace("\\s".toRegex(), "") + val num = fullToHalf(str).replace("\\s+".toRegex(), "") return try { Integer.parseInt(num) } catch (e: Exception) { @@ -226,17 +226,35 @@ object StringUtils { } fun isContainNumber(company: String): Boolean { - val p = Pattern.compile("[0-9]") + val p = Pattern.compile("[0-9]+") val m = p.matcher(company) return m.find() } fun isNumeric(str: String): Boolean { - val pattern = Pattern.compile("[0-9]*") + val pattern = Pattern.compile("[0-9]+") val isNum = pattern.matcher(str) return isNum.matches() } + fun wordCountFormat(wc: String?): String { + if (wc == null) return "" + var wordsS = "" + if (isNumeric(wc)) { + val words: Int = wc.toInt() + if (words > 0) { + wordsS = words.toString() + "字" + if (words > 10000) { + val df = DecimalFormat("#.#") + wordsS = df.format(words * 1.0f / 10000f.toDouble()) + "万字" + } + } + } else { + wordsS = wc + } + return wordsS + } + // 移除字符串首尾空字符的高效方法(利用ASCII值判断,包括全角空格) fun trim(s: String): String { if (isEmpty(s)) return "" @@ -274,14 +292,4 @@ object StringUtils { return buf.toString() } - fun formatHtml(html: String): String { - return if (isEmpty(html)) "" else html.replace( - "(?i)<(br[\\s/]*|/*p.*?|/*div.*?)>".toRegex(), - "\n" - )// 替换特定标签为换行符 - .replace("<[script>]*.*?>| ".toRegex(), "")// 删除script标签对和空格转义符 - .replace("\\s*\\n+\\s*".toRegex(), "\n  ")// 移除空行,并增加段前缩进2个汉字 - .replace("^[\\n\\s]+".toRegex(), "  ")//移除开头空行,并增加段前缩进2个汉字 - .replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行 - } } diff --git a/app/src/main/java/io/legado/app/utils/SystemUtils.kt b/app/src/main/java/io/legado/app/utils/SystemUtils.kt index c29381bbf..b8027528d 100644 --- a/app/src/main/java/io/legado/app/utils/SystemUtils.kt +++ b/app/src/main/java/io/legado/app/utils/SystemUtils.kt @@ -8,11 +8,15 @@ import android.content.Intent import android.net.Uri import android.os.PowerManager import android.provider.Settings +import android.view.View +import android.view.ViewGroup @Suppress("unused") object SystemUtils { + private const val NAVIGATION = "navigationBarBackground" + fun getScreenOffTime(context: Context): Int { var screenOffTime = 0 try { @@ -44,4 +48,26 @@ object SystemUtils { } } + + /** + * 返回NavigationBar是否存在 + * 该方法需要在View完全被绘制出来之后调用,否则判断不了 + * 在比如 onWindowFocusChanged()方法中可以得到正确的结果 + */ + fun isNavigationBarExist(activity: Activity?): Boolean { + activity?.let { + val vp = it.window.decorView as? ViewGroup + if (vp != null) { + for (i in 0 until vp.childCount) { + vp.getChildAt(i).context.packageName + if (vp.getChildAt(i).id != View.NO_ID + && NAVIGATION == activity.resources.getResourceEntryName(vp.getChildAt(i).id) + ) { + return true + } + } + } + } + return false + } } diff --git a/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt b/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt new file mode 100644 index 000000000..409152490 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt @@ -0,0 +1,11 @@ +package io.legado.app.utils + +val Throwable.msg: String + get() { + val stackTrace = stackTraceToString() + val lMsg = this.localizedMessage ?: "noErrorMsg" + return when { + stackTrace.isNotEmpty() -> stackTrace + else -> lMsg + } + } \ No newline at end of file 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..b50a4c493 --- /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.primaryText) + 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/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt index 583f699f7..77c0fcaee 100644 --- a/app/src/main/java/io/legado/app/utils/UriExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -4,6 +4,8 @@ import android.content.Context import android.net.Uri import java.io.File +fun Uri.isContentPath() = this.toString().isContentPath() + @Throws(Exception::class) fun Uri.readBytes(context: Context): ByteArray? { if (this.toString().isContentPath()) { 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/java/io/legado/app/web/HttpServer.kt b/app/src/main/java/io/legado/app/web/HttpServer.kt index 7afcd6c25..066a921c3 100644 --- a/app/src/main/java/io/legado/app/web/HttpServer.kt +++ b/app/src/main/java/io/legado/app/web/HttpServer.kt @@ -33,10 +33,10 @@ class HttpServer(port: Int) : NanoHTTPD(port) { val postData = files["postData"] when (uri) { - "/saveSource" -> returnData = SourceController().saveSource(postData) - "/saveSources" -> returnData = SourceController().saveSources(postData) - "/saveBook" -> returnData = BookshelfController().saveBook(postData) - "/deleteSources" -> returnData = SourceController().deleteSources(postData) + "/saveSource" -> returnData = SourceController.saveSource(postData) + "/saveSources" -> returnData = SourceController.saveSources(postData) + "/saveBook" -> returnData = BookshelfController.saveBook(postData) + "/deleteSources" -> returnData = SourceController.deleteSources(postData) } } @@ -44,13 +44,13 @@ class HttpServer(port: Int) : NanoHTTPD(port) { val parameters = session.parameters when (uri) { - "/getSource" -> returnData = SourceController().getSource(parameters) - "/getSources" -> returnData = SourceController().sources - "/getBookshelf" -> returnData = BookshelfController().bookshelf + "/getSource" -> returnData = SourceController.getSource(parameters) + "/getSources" -> returnData = SourceController.sources + "/getBookshelf" -> returnData = BookshelfController.bookshelf "/getChapterList" -> - returnData = BookshelfController().getChapterList(parameters) + returnData = BookshelfController.getChapterList(parameters) "/getBookContent" -> - returnData = BookshelfController().getBookContent(parameters) + returnData = BookshelfController.getBookContent(parameters) } } } diff --git a/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt b/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt index 6bae8f8aa..dba45d4c1 100644 --- a/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt +++ b/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt @@ -1,15 +1,18 @@ package io.legado.app.web.controller import io.legado.app.App +import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.help.BookHelp -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook +import io.legado.app.service.help.ReadBook import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject +import io.legado.app.utils.getPrefInt import io.legado.app.web.utils.ReturnData import kotlinx.coroutines.runBlocking -class BookshelfController { +object BookshelfController { val bookshelf: ReturnData get() { @@ -17,7 +20,15 @@ class BookshelfController { val returnData = ReturnData() return if (books.isEmpty()) { returnData.setErrorMsg("还没有添加小说") - } else returnData.setData(books) + } else { + val data = when (App.INSTANCE.getPrefInt(PreferKey.bookshelfSort)) { + 1 -> books.sortedByDescending { it.latestChapterTime } + 2 -> books.sortedBy { it.name } + 3 -> books.sortedBy { it.order } + else -> books.sortedByDescending { it.durChapterTime } + } + returnData.setData(data) + } } fun getChapterList(parameters: Map>): ReturnData { @@ -47,12 +58,14 @@ class BookshelfController { } else { val content: String? = BookHelp.getContent(book, chapter) if (content != null) { + saveBookReadIndex(book, index) returnData.setData(content) } else { App.db.bookSourceDao().getBookSource(book.origin)?.let { source -> runBlocking { WebBook(source).getContentSuspend(book, chapter) }.let { + saveBookReadIndex(book, index) returnData.setData(it) } } ?: returnData.setErrorMsg("未找到书源") @@ -71,4 +84,19 @@ class BookshelfController { return returnData.setErrorMsg("格式不对") } + private fun saveBookReadIndex(book: Book, index: Int) { + if (index > book.durChapterIndex) { + book.durChapterIndex = index + book.durChapterTime = System.currentTimeMillis() + App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { + book.durChapterTitle = it.title + } + App.db.bookDao().update(book) + if (ReadBook.book?.bookUrl == book.bookUrl) { + ReadBook.book = book + ReadBook.durChapterIndex = index + } + } + } + } diff --git a/app/src/main/java/io/legado/app/web/controller/SourceController.kt b/app/src/main/java/io/legado/app/web/controller/SourceController.kt index cb066ddc8..a76fe31e9 100644 --- a/app/src/main/java/io/legado/app/web/controller/SourceController.kt +++ b/app/src/main/java/io/legado/app/web/controller/SourceController.kt @@ -9,7 +9,7 @@ import io.legado.app.utils.fromJsonArray import io.legado.app.utils.fromJsonObject import io.legado.app.web.utils.ReturnData -class SourceController { +object SourceController { val sources: ReturnData get() { diff --git a/app/src/main/java/io/legado/app/web/controller/SourceDebugWebSocket.kt b/app/src/main/java/io/legado/app/web/controller/SourceDebugWebSocket.kt index 77b4384c1..ae3172d2f 100644 --- a/app/src/main/java/io/legado/app/web/controller/SourceDebugWebSocket.kt +++ b/app/src/main/java/io/legado/app/web/controller/SourceDebugWebSocket.kt @@ -6,7 +6,7 @@ import fi.iki.elonen.NanoWSD import io.legado.app.App import io.legado.app.R import io.legado.app.model.Debug -import io.legado.app.model.WebBook +import io.legado.app.model.webBook.WebBook import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject import io.legado.app.utils.isJson diff --git a/app/src/main/java/io/legado/app/web/utils/ReturnData.kt b/app/src/main/java/io/legado/app/web/utils/ReturnData.kt index 682c4be15..7c460e5e2 100644 --- a/app/src/main/java/io/legado/app/web/utils/ReturnData.kt +++ b/app/src/main/java/io/legado/app/web/utils/ReturnData.kt @@ -4,32 +4,25 @@ package io.legado.app.web.utils class ReturnData { var isSuccess: Boolean = false + private set - var errorCode: Int = 0 + var errorMsg: String? = null + private set - private var errorMsg: String? = null - - private var data: Any? = null + var data: Any? = null + private set init { this.isSuccess = false this.errorMsg = "未知错误,请联系开发者!" } - fun getErrorMsg(): String? { - return errorMsg - } - fun setErrorMsg(errorMsg: String?): ReturnData { this.isSuccess = false this.errorMsg = errorMsg return this } - fun getData(): Any? { - return data - } - fun setData(data: Any): ReturnData { this.isSuccess = true this.errorMsg = "" diff --git a/app/src/main/res/color/selector_image.xml b/app/src/main/res/color/selector_image.xml index 60fb16ed2..081b03cf0 100644 --- a/app/src/main/res/color/selector_image.xml +++ b/app/src/main/res/color/selector_image.xml @@ -1,7 +1,7 @@ - - - - + + + + \ 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/bg_prefs_color.xml b/app/src/main/res/drawable/bg_prefs_color.xml index ff4a5de4e..beb057eab 100644 --- a/app/src/main/res/drawable/bg_prefs_color.xml +++ b/app/src/main/res/drawable/bg_prefs_color.xml @@ -1,10 +1,4 @@ - - - - - - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml deleted file mode 100644 index 3f2b1987a..000000000 --- a/app/src/main/res/drawable/ic_about.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ 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_back_last.xml b/app/src/main/res/drawable/ic_back_last.xml deleted file mode 100644 index f1355c878..000000000 --- a/app/src/main/res/drawable/ic_back_last.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_book_source_manage.xml b/app/src/main/res/drawable/ic_book_source_manage.xml deleted file mode 100644 index 11fb46c9e..000000000 --- a/app/src/main/res/drawable/ic_book_source_manage.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cfg_jz.xml b/app/src/main/res/drawable/ic_cfg_donate.xml similarity index 98% rename from app/src/main/res/drawable/ic_cfg_jz.xml rename to app/src/main/res/drawable/ic_cfg_donate.xml index 710bae7fd..9d5bb4788 100644 --- a/app/src/main/res/drawable/ic_cfg_jz.xml +++ b/app/src/main/res/drawable/ic_cfg_donate.xml @@ -1,12 +1,12 @@ - - - - + + + + diff --git a/app/src/main/res/drawable/ic_cfg_e_lnk.xml b/app/src/main/res/drawable/ic_cfg_e_lnk.xml deleted file mode 100644 index b94a8eb4d..000000000 --- a/app/src/main/res/drawable/ic_cfg_e_lnk.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_disclaimer.xml b/app/src/main/res/drawable/ic_disclaimer.xml deleted file mode 100644 index 6cc4ea597..000000000 --- a/app/src/main/res/drawable/ic_disclaimer.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_donate.xml b/app/src/main/res/drawable/ic_donate.xml deleted file mode 100644 index 1cf58b952..000000000 --- a/app/src/main/res/drawable/ic_donate.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ 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_export.xml b/app/src/main/res/drawable/ic_export.xml new file mode 100644 index 000000000..78d6a76dd --- /dev/null +++ b/app/src/main/res/drawable/ic_export.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_faq.xml b/app/src/main/res/drawable/ic_faq.xml deleted file mode 100644 index b6d620eb5..000000000 --- a/app/src/main/res/drawable/ic_faq.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_format_line_spacing.xml b/app/src/main/res/drawable/ic_format_line_spacing.xml deleted file mode 100644 index 3308038da..000000000 --- a/app/src/main/res/drawable/ic_format_line_spacing.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ 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_import.xml b/app/src/main/res/drawable/ic_import.xml index b9929a808..dc493329e 100644 --- a/app/src/main/res/drawable/ic_import.xml +++ b/app/src/main/res/drawable/ic_import.xml @@ -1,32 +1,15 @@ - + + android:viewportWidth="1280" + android:viewportHeight="1024"> + + android:fillColor="#000000" + android:pathData="M394 376V230.8c0-19.2 15.6-34.8 34.8-34.8h477.4c19.2 0 34.8 15.6 34.8 34.8v477.4c0 19.2-15.6 34.8-34.8 34.8H761c-18.8 0-34 15.2-34 34s15.2 34 34 34h178.9c38.2 0 69.1-31 69.1-69.1V197.1c0-38.2-31-69.1-69.1-69.1H395.1c-38.2 0-69.1 31-69.1 69.1V376c0 18.8 15.2 34 34 34s34-15.2 34-34z" /> - - - - + android:fillColor="#000000" + android:pathData="M678.9 618.8l-0.1-0.1L545.1 485c-13.3-13.3-34.8-13.3-48.1 0-13.3 13.3-13.3 34.8 0 48.1l75.9 75.9H360c-18.8 0-34 15.2-34 34s15.2 34 34 34h212.7L497 752.7c-13.3 13.3-13.3 34.8 0 48.1 13.3 13.3 34.8 13.3 48.1 0l131.6-131.6c7.5-6.2 12.3-15.6 12.3-26.1 0-9.6-3.9-18.1-10.1-24.3z" /> + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml deleted file mode 100644 index 4a1b0d561..000000000 --- a/app/src/main/res/drawable/ic_list.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_mail.xml b/app/src/main/res/drawable/ic_mail.xml deleted file mode 100644 index cbe92189a..000000000 --- a/app/src/main/res/drawable/ic_mail.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_camera.xml b/app/src/main/res/drawable/ic_menu_camera.xml deleted file mode 100644 index 634fe9221..000000000 --- a/app/src/main/res/drawable/ic_menu_camera.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_menu_gallery.xml b/app/src/main/res/drawable/ic_menu_gallery.xml deleted file mode 100644 index 03c77099f..000000000 --- a/app/src/main/res/drawable/ic_menu_gallery.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_manage.xml b/app/src/main/res/drawable/ic_menu_manage.xml deleted file mode 100644 index aeb047d02..000000000 --- a/app/src/main/res/drawable/ic_menu_manage.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_send.xml b/app/src/main/res/drawable/ic_menu_send.xml deleted file mode 100644 index fdf1c9009..000000000 --- a/app/src/main/res/drawable/ic_menu_send.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_menu_slideshow.xml b/app/src/main/res/drawable/ic_menu_slideshow.xml deleted file mode 100644 index 5e9e163a5..000000000 --- a/app/src/main/res/drawable/ic_menu_slideshow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - 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_outline_cloud_24.xml b/app/src/main/res/drawable/ic_outline_cloud_24.xml new file mode 100644 index 000000000..c8b092ead --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_cloud_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml index 3285a9327..2ca607005 100644 --- a/app/src/main/res/drawable/ic_scan.xml +++ b/app/src/main/res/drawable/ic_scan.xml @@ -1,5 +1,6 @@ + android:pathData="M725.333333 128h149.184C886.421333 128 896 137.578667 896 149.482667V298.666667a42.666667 42.666667 0 1 0 85.333333 0V149.482667A106.752 106.752 0 0 0 874.517333 42.666667H725.333333a42.666667 42.666667 0 1 0 0 85.333333z m170.666667 597.333333v149.184c0 11.904-9.578667 21.482667-21.482667 21.482667H725.333333a42.666667 42.666667 0 1 0 0 85.333333h149.184A106.752 106.752 0 0 0 981.333333 874.517333V725.333333a42.666667 42.666667 0 1 0-85.333333 0z m-597.333333 170.666667H149.482667A21.418667 21.418667 0 0 1 128 874.517333V725.333333a42.666667 42.666667 0 1 0-85.333333 0v149.184A106.752 106.752 0 0 0 149.482667 981.333333H298.666667a42.666667 42.666667 0 1 0 0-85.333333zM128 298.666667a42.666667 42.666667 0 1 1-85.333333 0V149.482667A106.752 106.752 0 0 1 149.482667 42.666667H298.666667a42.666667 42.666667 0 1 1 0 85.333333H149.482667C137.578667 128 128 137.578667 128 149.482667V298.666667zM85.333333 554.666667a42.666667 42.666667 0 1 1 0-85.333334h853.333334a42.666667 42.666667 0 1 1 0 85.333334H85.333333z" + tools:ignore="VectorPath" /> + \ No newline at end of file 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_settings.xml b/app/src/main/res/drawable/ic_settings.xml index 0424ce07a..1d4297767 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -1,5 +1,6 @@ + android:pathData="M13.735,20 L10.261,20 L10.12,18.065 C10.102,17.807,9.944,17.585,9.721,17.492 C9.472,17.387,9.214,17.429,9.024,17.592 L7.568,18.848 L5.113,16.391 L6.369,14.937 C6.531,14.749,6.573,14.49,6.477,14.258 C6.376,14.013,6.166,13.857,5.913,13.836 L4,13.697 L4,10.226 L5.916,10.085 C6.164,10.066,6.378,9.913,6.473,9.683 C6.574,9.438,6.536,9.185,6.371,8.992 L5.113,7.539 L7.573,5.094 L9.026,6.376 C9.222,6.544,9.495,6.605,9.703,6.522 L9.844,6.461 C10.026,6.273,10.107,6.141,10.119,5.974 L10.262,4 L13.734,4 L13.877,5.894 C13.895,6.139,14.041,6.335,14.277,6.43 C14.527,6.537,14.786,6.494,14.975,6.332 L16.427,5.073 L18.886,7.531 L17.629,8.984 C17.467,9.172,17.426,9.431,17.521,9.664 C17.622,9.909,17.833,10.066,18.085,10.085 L20,10.227 L20,13.698 L18.085,13.837 C17.837,13.858,17.623,14.011,17.528,14.242 C17.426,14.487,17.465,14.747,17.63,14.94 L18.886,16.391 L16.429,18.85 L14.975,17.594 C14.799,17.444,14.544,17.398,14.322,17.479 L14.295,17.493 C14.051,17.594,13.894,17.816,13.876,18.068 L13.735,20 Z M11.561,18.604 L12.437,18.604 L12.482,17.963 C12.538,17.216,13.011,16.538,13.691,16.232 L13.737,16.211 C14.526,15.883,15.308,16.04,15.886,16.54 L16.358,16.945 L16.98,16.321 L16.574,15.852 C16.064,15.26,15.933,14.446,16.231,13.725 C16.535,12.993,17.206,12.502,17.982,12.446 L18.604,12.399 L18.604,11.524 L17.984,11.477 C17.207,11.421,16.536,10.938,16.239,10.215 C15.935,9.485,16.065,8.664,16.574,8.07 L16.98,7.601 L16.358,6.979 L15.886,7.386 C15.308,7.885,14.472,8.022,13.761,7.729 C13.029,7.428,12.54,6.763,12.482,5.996 L12.438,5.397 L11.558,5.397 L11.514,6 C11.457,6.773,10.973,7.438,10.252,7.733 L10.091,7.789 C9.416,8.008,8.652,7.856,8.112,7.392 L7.639,6.982 L7.02,7.602 L7.426,8.075 C7.936,8.665,8.067,9.479,7.77,10.202 C7.467,10.931,6.796,11.422,6.02,11.477 L5.397,11.524 L5.397,12.399 L6.019,12.446 C6.796,12.503,7.465,12.987,7.762,13.71 C8.066,14.44,7.935,15.261,7.426,15.852 L7.02,16.32 L7.64,16.941 L8.112,16.535 C8.692,16.035,9.528,15.898,10.239,16.194 C10.958,16.488,11.458,17.181,11.515,17.961 L11.561,18.604 Z" + tools:ignore="VectorPath" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml index 0022d3b38..df884e817 100644 --- a/app/src/main/res/drawable/ic_share.xml +++ b/app/src/main/res/drawable/ic_share.xml @@ -1,14 +1,11 @@ - - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/ic_swap_outline_24dp.xml b/app/src/main/res/drawable/ic_swap_outline_24dp.xml deleted file mode 100644 index f9026522d..000000000 --- a/app/src/main/res/drawable/ic_swap_outline_24dp.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_theme.xml b/app/src/main/res/drawable/ic_theme.xml deleted file mode 100644 index 90da16498..000000000 --- a/app/src/main/res/drawable/ic_theme.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_time_add_24dp.xml b/app/src/main/res/drawable/ic_time_add_24dp.xml index b3960a060..66afd8f8f 100644 --- a/app/src/main/res/drawable/ic_time_add_24dp.xml +++ b/app/src/main/res/drawable/ic_time_add_24dp.xml @@ -1,4 +1,5 @@ + android:pathData="M698.235505586 166.666668C698.235505586 166.666668 869.372215288 309.497277476 869.372215288 309.497277476C869.372215288 309.497277476 821.823923241 366.518082099 821.823923241 366.518082099C821.823923241 366.518082099 650.575796871 223.76176429 650.575796871 223.76176429C650.575796871 223.76176429 698.235505586 166.666668 698.235505586 166.666668M301.727348247 166.666668C301.727348247 166.666668 349.349936128 223.724618456 349.349936128 223.724618456C349.349936128 223.724618456 178.176084759 366.518082099 178.176084759 366.518082099C178.176084759 366.518082099 130.627792712 309.460131642 130.627792712 309.460131642C130.627792712 309.460131642 301.727348247 166.666668 301.727348247 166.666668M499.981420667 248.018822817C315.360335856 248.018822817 165.657509659 397.721628182 165.657509659 582.342754659C165.657509659 766.963839469 315.360335856 916.666674 499.981420667 916.666674C684.602547143 916.666674 834.305381674 766.963839469 834.305381674 582.342754659C834.305381674 397.721628182 684.602547143 248.018822817 499.981420667 248.018822817C499.981420667 248.018822817 499.981420667 248.018822817 499.981420667 248.018822817M499.981420667 842.372465072C356.593619519 842.372465072 239.95171442 725.730547473 239.95171442 582.342754659C239.95171442 438.954920178 356.593619519 322.313027579 499.981420667 322.313027579C643.369255147 322.313027579 760.011172747 438.954920178 760.011172747 582.342754659C760.011172747 725.730547473 643.369255147 842.372465072 499.981420667 842.372465072C499.981420667 842.372465072 499.981420667 842.372465072 499.981420667 842.372465072M537.128545964 433.754336803C537.128545964 433.754336803 462.834337036 433.754336803 462.834337036 433.754336803C462.834337036 433.754336803 462.834337036 545.195629362 462.834337036 545.195629362C462.834337036 545.195629362 351.393023644 545.195629362 351.393023644 545.195629362C351.393023644 545.195629362 351.393023644 619.489838289 351.393023644 619.489838289C351.393023644 619.489838289 462.834337036 619.489838289 462.834337036 619.489838289C462.834337036 619.489838289 462.834337036 730.931172514 462.834337036 730.931172514C462.834337036 730.931172514 537.128545964 730.931172514 537.128545964 730.931172514C537.128545964 730.931172514 537.128545964 619.489838289 537.128545964 619.489838289C537.128545964 619.489838289 648.569838522 619.489838289 648.569838522 619.489838289C648.569838522 619.489838289 648.569838522 545.195629362 648.569838522 545.195629362C648.569838522 545.195629362 537.128545964 545.195629362 537.128545964 545.195629362C537.128545964 545.195629362 537.128545964 433.754336803 537.128545964 433.754336803" + tools:ignore="VectorPath" /> diff --git a/app/src/main/res/drawable/ic_top_source.xml b/app/src/main/res/drawable/ic_top_source.xml deleted file mode 100644 index 0b19c9cb4..000000000 --- a/app/src/main/res/drawable/ic_top_source.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_tune.xml b/app/src/main/res/drawable/ic_tune.xml deleted file mode 100644 index cdc9858e1..000000000 --- a/app/src/main/res/drawable/ic_tune.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_version.xml b/app/src/main/res/drawable/ic_version.xml deleted file mode 100644 index 551bee2c8..000000000 --- a/app/src/main/res/drawable/ic_version.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file 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/drawable/selector_btn_accent_bg.xml b/app/src/main/res/drawable/selector_btn_accent_bg.xml index a888e77e0..d06cfe139 100644 --- a/app/src/main/res/drawable/selector_btn_accent_bg.xml +++ b/app/src/main/res/drawable/selector_btn_accent_bg.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_radius_10dp.xml b/app/src/main/res/drawable/shape_radius_10dp.xml index 9e72a3a38..3680c9e7e 100644 --- a/app/src/main/res/drawable/shape_radius_10dp.xml +++ b/app/src/main/res/drawable/shape_radius_10dp.xml @@ -4,5 +4,5 @@ + android:color="@color/secondaryText" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_radius_1dp.xml b/app/src/main/res/drawable/shape_radius_1dp.xml index f032285f7..d8e0c3f87 100644 --- a/app/src/main/res/drawable/shape_radius_1dp.xml +++ b/app/src/main/res/drawable/shape_radius_1dp.xml @@ -4,5 +4,5 @@ + android:color="@color/secondaryText" /> \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_book_info.xml b/app/src/main/res/layout-land/activity_book_info.xml index 1dc7d789c..05a4b316a 100644 --- a/app/src/main/res/layout-land/activity_book_info.xml +++ b/app/src/main/res/layout-land/activity_book_info.xml @@ -19,7 +19,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" - app:title="@string/book_info" /> + app:title="@string/book_info" + app:themeMode="dark" /> + android:fitsSystemWindows="true" + tools:ignore="DisableBaselineAlignment"> @@ -71,6 +74,7 @@ @@ -110,7 +114,8 @@ android:layout_gravity="center" android:orientation="horizontal" android:paddingTop="3dp" - android:paddingBottom="3dp"> + android:paddingBottom="3dp" + tools:ignore="UseCompoundDrawables"> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/origin_format" /> + android:paddingBottom="3dp" + tools:ignore="UseCompoundDrawables"> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/read_dur_progress" /> @@ -285,10 +291,10 @@ android:includeFontPadding="false" android:paddingRight="6dp" android:singleLine="true" - android:text="@string/group_s" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/group_s" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/toc_s" /> + android:visibility="visible" + tools:ignore="RtlHardcoded,RtlSymmetry" /> - - + app:layout_constraintTop_toTopOf="parent" + app:themeMode="dark" /> + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@id/title_bar" /> + app:tint="@color/md_white_1000" /> + app:tint="@color/md_white_1000" /> + app:tint="@color/md_white_1000" /> + app:tint="@color/md_white_1000" /> + app:tint="@color/md_white_1000" /> + android:contentDescription="@string/chapter_list" + app:tint="@color/md_white_1000" /> diff --git a/app/src/main/res/layout/activity_book_info.xml b/app/src/main/res/layout/activity_book_info.xml index ee0d050bb..e2d35458f 100644 --- a/app/src/main/res/layout/activity_book_info.xml +++ b/app/src/main/res/layout/activity_book_info.xml @@ -26,7 +26,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" - app:title="@string/book_info" /> + app:title="@string/book_info" + app:themeMode="dark" /> + android:layout_height="78dp" + app:arcDirectionTop="true" + app:arcHeight="36dp" + app:bgColor="@color/background" /> + android:layout_height="wrap_content" + app:cardCornerRadius="5dp" + app:cardElevation="8dp"> + android:focusable="true"> @@ -121,18 +124,17 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:paddingBottom="8dp" - android:background="@color/background" android:layout_width="match_parent" android:layout_height="wrap_content"> - + android:paddingBottom="3dp" + tools:ignore="UseCompoundDrawables"> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> @@ -186,10 +188,10 @@ android:includeFontPadding="false" android:paddingRight="6dp" android:singleLine="true" - android:text="@string/origin_format" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/origin_format" /> @@ -210,16 +212,17 @@ android:gravity="center_vertical" android:orientation="horizontal" android:paddingTop="3dp" - android:paddingBottom="3dp"> + android:paddingBottom="3dp" + tools:ignore="UseCompoundDrawables"> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/read_dur_progress" /> @@ -249,10 +252,10 @@ + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/group_s" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" + tools:text="@string/toc_s" /> + android:visibility="visible" + tools:ignore="RtlHardcoded,RtlSymmetry" /> - - + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/activity_download.xml b/app/src/main/res/layout/activity_download.xml index 2f7ebd6f2..c499af731 100644 --- a/app/src/main/res/layout/activity_download.xml +++ b/app/src/main/res/layout/activity_download.xml @@ -9,7 +9,7 @@ android:id="@+id/title_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - app:title="@string/download_offline" /> + app:title="@string/offline_cache" /> @@ -53,13 +53,19 @@ tools:ignore="UnusedAttribute" /> - + android:layout_weight="1" + app:layout_constraintTop_toBottomOf="@id/lay_top" + app:layout_constraintBottom_toTopOf="@id/select_action_bar"> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_artivles.xml b/app/src/main/res/layout/activity_rss_artivles.xml index 39678114e..50e503a9e 100644 --- a/app/src/main/res/layout/activity_rss_artivles.xml +++ b/app/src/main/res/layout/activity_rss_artivles.xml @@ -1,28 +1,24 @@ - + android:layout_height="wrap_content" /> - - - + android:layout_height="wrap_content" + app:tabMode="scrollable" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index ea4131764..6eb28b4f8 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -2,6 +2,7 @@ + android:layout_height="wrap_content" + app:fitStatusBar="false" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_translucence.xml b/app/src/main/res/layout/activity_translucence.xml new file mode 100644 index 000000000..946439b5d --- /dev/null +++ b/app/src/main/res/layout/activity_translucence.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml index 2a94fce69..26334eab6 100644 --- a/app/src/main/res/layout/activity_welcome.xml +++ b/app/src/main/res/layout/activity_welcome.xml @@ -12,7 +12,7 @@ android:id="@+id/vw_title_line" android:layout_width="6dp" android:layout_height="0dp" - android:background="@color/colorAccent" + android:background="@color/accent" app:layout_constraintTop_toTopOf="@+id/tv_title" app:layout_constraintBottom_toBottomOf="@+id/tv_title" app:layout_constraintLeft_toLeftOf="@id/iv_book" /> @@ -22,7 +22,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="1" - android:text="悦读" + android:text="阅读" android:textSize="49sp" app:layout_constraintVertical_bias="0.3" app:layout_constraintRight_toLeftOf="@+id/tv_sub_title" @@ -60,7 +60,7 @@ android:id="@+id/tv_gzh" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="关注公众号[开源阅读软件]\n看文章点广告支持作者" + android:text="关注公众号[开源阅读]\n看文章点广告支持作者" android:layout_marginBottom="32dp" android:gravity="center_horizontal" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/dialog_auto_read.xml b/app/src/main/res/layout/dialog_auto_read.xml new file mode 100644 index 000000000..a8da725b4 --- /dev/null +++ b/app/src/main/res/layout/dialog_auto_read.xml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_book_group_picker.xml b/app/src/main/res/layout/dialog_book_group_picker.xml index 43d0c5d3b..95317f693 100644 --- a/app/src/main/res/layout/dialog_book_group_picker.xml +++ b/app/src/main/res/layout/dialog_book_group_picker.xml @@ -1,19 +1,21 @@ + android:layout_height="wrap_content" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" /> diff --git a/app/src/main/res/layout/dialog_change_cover.xml b/app/src/main/res/layout/dialog_change_cover.xml index cc7486b05..2dee2288c 100644 --- a/app/src/main/res/layout/dialog_change_cover.xml +++ b/app/src/main/res/layout/dialog_change_cover.xml @@ -9,8 +9,10 @@ android:id="@+id/tool_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/background_menu" android:elevation="5dp" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" app:displayHomeAsUp="false" app:fitStatusBar="false" /> diff --git a/app/src/main/res/layout/dialog_change_source.xml b/app/src/main/res/layout/dialog_change_source.xml index cc7486b05..d72fab249 100644 --- a/app/src/main/res/layout/dialog_change_source.xml +++ b/app/src/main/res/layout/dialog_change_source.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/background"> diff --git a/app/src/main/res/layout/dialog_download_choice.xml b/app/src/main/res/layout/dialog_download_choice.xml index 9eabee75b..4548ac329 100644 --- a/app/src/main/res/layout/dialog_download_choice.xml +++ b/app/src/main/res/layout/dialog_download_choice.xml @@ -20,7 +20,7 @@ android:layout_gravity="center_vertical" android:layout_marginRight="5dp" android:text="@string/chapter" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="16sp" tools:ignore="RtlHardcoded" /> @@ -38,7 +38,7 @@ android:paddingTop="4dp" android:paddingRight="5dp" android:paddingBottom="4dp" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textCursorDrawable="@drawable/shape_text_cursor" android:textSize="14sp" /> @@ -49,7 +49,7 @@ android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:text="@string/to" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/dialog_file_chooser.xml b/app/src/main/res/layout/dialog_file_chooser.xml index ab5974114..efe1db737 100644 --- a/app/src/main/res/layout/dialog_file_chooser.xml +++ b/app/src/main/res/layout/dialog_file_chooser.xml @@ -1,14 +1,18 @@ + android:layout_height="match_parent" + android:background="@color/background"> + android:layout_height="wrap_content" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + android:layout_height="wrap_content" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_page_key.xml b/app/src/main/res/layout/dialog_page_key.xml index fce43c2d0..f2e1c8a3a 100644 --- a/app/src/main/res/layout/dialog_page_key.xml +++ b/app/src/main/res/layout/dialog_page_key.xml @@ -1,5 +1,6 @@ + android:textColor="@color/primaryText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_file_association.xml b/app/src/main/res/layout/dialog_photo_view.xml similarity index 52% rename from app/src/main/res/layout/activity_file_association.xml rename to app/src/main/res/layout/dialog_photo_view.xml index 2a7adee89..410ac99e5 100644 --- a/app/src/main/res/layout/activity_file_association.xml +++ b/app/src/main/res/layout/dialog_photo_view.xml @@ -1,8 +1,15 @@ + android:background="@color/background"> + + + + diff --git a/app/src/main/res/layout/dialog_read_aloud.xml b/app/src/main/res/layout/dialog_read_aloud.xml index 504865171..8d5b33f43 100644 --- a/app/src/main/res/layout/dialog_read_aloud.xml +++ b/app/src/main/res/layout/dialog_read_aloud.xml @@ -31,7 +31,7 @@ android:paddingTop="10dp" android:paddingBottom="10dp" android:text="@string/previous_chapter" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="14sp" /> @@ -121,12 +121,13 @@ android:padding="6dp"> + android:max="180" /> @@ -243,22 +247,24 @@ android:paddingBottom="7dp"> @@ -273,28 +279,30 @@ 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"> @@ -315,22 +323,24 @@ android:paddingBottom="7dp"> diff --git a/app/src/main/res/layout/dialog_read_bg_text.xml b/app/src/main/res/layout/dialog_read_bg_text.xml index 6cea816cd..cbf9c4c02 100644 --- a/app/src/main/res/layout/dialog_read_bg_text.xml +++ b/app/src/main/res/layout/dialog_read_bg_text.xml @@ -1,10 +1,12 @@ @@ -25,14 +28,16 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_margin="6dp" - android:layout_weight="1" + android:layout_marginLeft="6dp" + android:layout_marginRight="6dp" + android:layout_weight="5" android:background="?attr/selectableItemBackground" - android:singleLine="true" android:gravity="center" android:padding="6dp" + android:singleLine="true" android:text="@string/text_color" android:textSize="14sp" + app:isBottomBackground="true" tools:ignore="HardcodedText" /> - - - - + android:contentDescription="@string/import_str" + android:src="@drawable/ic_import" + android:tooltipText="@string/import_str" + tools:ignore="UnusedAttribute" /> - + - + + + + \ No newline at end of file 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 4adff8b72..1d0fcd6a8 100644 --- a/app/src/main/res/layout/dialog_read_book_style.xml +++ b/app/src/main/res/layout/dialog_read_book_style.xml @@ -1,6 +1,7 @@ - + + + + @@ -144,6 +170,7 @@ android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" + app:isBottomBackground="true" app:max="100" app:title="@string/text_letter_spacing" /> @@ -153,6 +180,7 @@ android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" + app:isBottomBackground="true" app:max="20" app:title="@string/line_size" /> @@ -162,6 +190,7 @@ android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" + app:isBottomBackground="true" app:max="20" app:title="@string/paragraph_size" /> @@ -200,7 +229,8 @@ android:button="@null" android:gravity="center" android:singleLine="true" - android:text="@string/page_anim_cover" /> + android:text="@string/page_anim_cover" + app:isBottomBackground="true" /> + android:text="@string/page_anim_slide" + app:isBottomBackground="true" /> + android:text="@string/page_anim_simulation" + app:isBottomBackground="true" /> + android:text="@string/page_anim_scroll" + app:isBottomBackground="true" /> + android:text="@string/page_anim_none" + app:isBottomBackground="true" /> @@ -290,87 +324,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tool:listitem="@layout/item_read_style" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_read_padding.xml b/app/src/main/res/layout/dialog_read_padding.xml index d867c6814..c9be863dd 100644 --- a/app/src/main/res/layout/dialog_read_padding.xml +++ b/app/src/main/res/layout/dialog_read_padding.xml @@ -3,7 +3,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/background" android:padding="10dp" android:orientation="vertical"> diff --git a/app/src/main/res/layout/dialog_recycler_view.xml b/app/src/main/res/layout/dialog_recycler_view.xml index 5b1d900a9..e7ca72d9a 100644 --- a/app/src/main/res/layout/dialog_recycler_view.xml +++ b/app/src/main/res/layout/dialog_recycler_view.xml @@ -1,19 +1,21 @@ + android:layout_height="wrap_content" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" /> @@ -22,12 +24,32 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + - + + + android:layout_height="match_parent"> + android:background="@color/background"> + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/dialog_tip_config.xml b/app/src/main/res/layout/dialog_tip_config.xml new file mode 100644 index 000000000..9ef889a05 --- /dev/null +++ b/app/src/main/res/layout/dialog_tip_config.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_toc_regex.xml b/app/src/main/res/layout/dialog_toc_regex.xml index 43d0c5d3b..95317f693 100644 --- a/app/src/main/res/layout/dialog_toc_regex.xml +++ b/app/src/main/res/layout/dialog_toc_regex.xml @@ -1,19 +1,21 @@ + android:layout_height="wrap_content" + android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" + app:popupTheme="@style/AppTheme.PopupOverlay" /> diff --git a/app/src/main/res/layout/fragment_chapter_list.xml b/app/src/main/res/layout/fragment_chapter_list.xml index 8964c91c6..3d8943d79 100644 --- a/app/src/main/res/layout/fragment_chapter_list.xml +++ b/app/src/main/res/layout/fragment_chapter_list.xml @@ -37,7 +37,7 @@ android:paddingRight="10dp" android:singleLine="true" android:gravity="center_vertical" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/fragment_rss_articles.xml b/app/src/main/res/layout/fragment_rss_articles.xml new file mode 100644 index 000000000..5ca0c9574 --- /dev/null +++ b/app/src/main/res/layout/fragment_rss_articles.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_list.xml b/app/src/main/res/layout/fragment_search_list.xml new file mode 100644 index 000000000..79fefe11f --- /dev/null +++ b/app/src/main/res/layout/fragment_search_list.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_arrange_book.xml b/app/src/main/res/layout/item_arrange_book.xml index 4fdd7a19c..7323a53c7 100644 --- a/app/src/main/res/layout/item_arrange_book.xml +++ b/app/src/main/res/layout/item_arrange_book.xml @@ -24,7 +24,7 @@ android:padding="3dp" android:textSize="15sp" android:text="@string/book_name" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:singleLine="true" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@id/tv_group_s" diff --git a/app/src/main/res/layout/item_bg_image.xml b/app/src/main/res/layout/item_bg_image.xml index 57c74b182..4bf9cf5fe 100644 --- a/app/src/main/res/layout/item_bg_image.xml +++ b/app/src/main/res/layout/item_bg_image.xml @@ -1,7 +1,8 @@ @@ -10,8 +11,9 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" - android:scaleType="centerCrop" - android:contentDescription="@string/bg_image" /> + android:scaleType="fitCenter" + android:contentDescription="@string/bg_image" + tool:src="@drawable/ic_image" /> @@ -52,7 +52,7 @@ android:background="?attr/selectableItemBackgroundBorderless" android:padding="6dp" android:src="@drawable/ic_more_vert" - android:tint="@color/tv_text_default" + android:tint="@color/primaryText" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/item_bookmark.xml b/app/src/main/res/layout/item_bookmark.xml index bd0ec0e11..971d56416 100644 --- a/app/src/main/res/layout/item_bookmark.xml +++ b/app/src/main/res/layout/item_bookmark.xml @@ -3,18 +3,21 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" + android:padding="8dp" android:background="?android:attr/selectableItemBackground"> diff --git a/app/src/main/res/layout/item_bookshelf_grid.xml b/app/src/main/res/layout/item_bookshelf_grid.xml index 50bb20d12..114c3c58e 100644 --- a/app/src/main/res/layout/item_bookshelf_grid.xml +++ b/app/src/main/res/layout/item_bookshelf_grid.xml @@ -53,7 +53,6 @@ android:visibility="invisible" app:layout_constraintTop_toTopOf="@+id/bg_cover" app:layout_constraintRight_toRightOf="@+id/bg_cover" - app:loading_color="@color/colorAccent" app:loading_width="2dp" tools:ignore="RtlHardcoded" /> @@ -61,15 +60,15 @@ 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" android:lines="2" android:ellipsize="end" android:text="@string/book_name" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="12sp" app:layout_constraintLeft_toLeftOf="@+id/bg_cover" app:layout_constraintRight_toRightOf="@+id/bg_cover" diff --git a/app/src/main/res/layout/item_bookshelf_list.xml b/app/src/main/res/layout/item_bookshelf_list.xml index b381aaa90..f6e0a694d 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,13 +56,14 @@ 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" + android:textColor="@color/primaryText" android:textSize="16sp" app:layout_constraintBottom_toTopOf="@+id/tv_author" app:layout_constraintLeft_toRightOf="@+id/iv_cover" @@ -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_change_source.xml b/app/src/main/res/layout/item_change_source.xml index 81625c642..db773e7fc 100644 --- a/app/src/main/res/layout/item_change_source.xml +++ b/app/src/main/res/layout/item_change_source.xml @@ -11,6 +11,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:singleLine="true" + android:textColor="@color/primaryText" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/iv_checked" /> @@ -20,6 +21,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:singleLine="true" + android:textColor="@color/secondaryText" app:layout_constraintTop_toBottomOf="@+id/tv_origin" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" @@ -32,7 +34,7 @@ android:layout_margin="8dp" android:src="@drawable/ic_check" android:visibility="invisible" - app:tint="@color/tv_text_default" + app:tint="@color/primaryText" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/item_chapter_list.xml b/app/src/main/res/layout/item_chapter_list.xml index bd678b4fe..285af05ba 100644 --- a/app/src/main/res/layout/item_chapter_list.xml +++ b/app/src/main/res/layout/item_chapter_list.xml @@ -1,5 +1,6 @@ - + android:singleLine="true" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@+id/iv_checked" /> + android:singleLine="true" + app:layout_constraintTop_toBottomOf="@+id/tv_chapter_name" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@+id/iv_checked" /> - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_cover.xml b/app/src/main/res/layout/item_cover.xml index 9956e4bd1..1b88c854a 100644 --- a/app/src/main/res/layout/item_cover.xml +++ b/app/src/main/res/layout/item_cover.xml @@ -25,7 +25,7 @@ android:gravity="center" android:singleLine="true" android:text="@string/book_source" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="16sp" tools:ignore="RtlHardcoded,RtlSymmetry" /> diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml index 8413da785..a82f4f767 100644 --- a/app/src/main/res/layout/item_download.xml +++ b/app/src/main/res/layout/item_download.xml @@ -38,12 +38,12 @@ android:layout_height="36dp" android:padding="6dp" android:background="?attr/selectableItemBackgroundBorderless" - android:tint="@color/tv_text_default" android:src="@drawable/ic_play_24dp" android:contentDescription="@string/start" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintRight_toLeftOf="@+id/tv_export" /> + app:layout_constraintRight_toLeftOf="@+id/tv_export" + app:tint="@color/primaryText" /> diff --git a/app/src/main/res/layout/item_find_book.xml b/app/src/main/res/layout/item_find_book.xml index d689630d2..5ee810333 100644 --- a/app/src/main/res/layout/item_find_book.xml +++ b/app/src/main/res/layout/item_find_book.xml @@ -23,7 +23,8 @@ android:id="@+id/tv_name" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_weight="1" /> + android:layout_weight="1" + android:textColor="@color/primaryText" /> diff --git a/app/src/main/res/layout/item_font.xml b/app/src/main/res/layout/item_font.xml index 24c9be27e..0e08f17bc 100644 --- a/app/src/main/res/layout/item_font.xml +++ b/app/src/main/res/layout/item_font.xml @@ -14,7 +14,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:padding="5dp" - android:textColor="@color/tv_text_default" /> + android:textColor="@color/primaryText" /> + app:tint="@color/primaryText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_group_select.xml b/app/src/main/res/layout/item_group_select.xml index 4c04efbf0..c6c08fbec 100644 --- a/app/src/main/res/layout/item_group_select.xml +++ b/app/src/main/res/layout/item_group_select.xml @@ -14,7 +14,7 @@ android:layout_gravity="center" android:layout_weight="1" android:singleLine="true" - android:textColor="@color/tv_text_default" /> + android:textColor="@color/primaryText" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_import_book.xml b/app/src/main/res/layout/item_import_book.xml index 1ad9742bd..3e0ca0f0c 100644 --- a/app/src/main/res/layout/item_import_book.xml +++ b/app/src/main/res/layout/item_import_book.xml @@ -28,7 +28,7 @@ android:scaleType="center" android:visibility="invisible" android:src="@drawable/ic_folder" - app:tint="@color/tv_text_default" /> + app:tint="@color/primaryText" /> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_read_style.xml b/app/src/main/res/layout/item_read_style.xml new file mode 100644 index 000000000..1df8bd29e --- /dev/null +++ b/app/src/main/res/layout/item_read_style.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_replace_rule.xml b/app/src/main/res/layout/item_replace_rule.xml index 1233922e0..f2d2f98e1 100644 --- a/app/src/main/res/layout/item_replace_rule.xml +++ b/app/src/main/res/layout/item_replace_rule.xml @@ -32,7 +32,7 @@ android:contentDescription="@string/edit" android:padding="6dp" android:src="@drawable/ic_edit" - android:tint="@color/tv_text_default" /> + android:tint="@color/primaryText" /> 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..508350eb3 100644 --- a/app/src/main/res/layout/item_rss_article.xml +++ b/app/src/main/res/layout/item_rss_article.xml @@ -13,7 +13,7 @@ android:maxLines="2" android:ellipsize="end" android:text="@string/app_name" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="16sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/tv_pub_date" @@ -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..32fc80cea --- /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..83a935c26 --- /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/item_rss_source.xml b/app/src/main/res/layout/item_rss_source.xml index d0ecd8788..6ec24421d 100644 --- a/app/src/main/res/layout/item_rss_source.xml +++ b/app/src/main/res/layout/item_rss_source.xml @@ -15,7 +15,7 @@ android:layout_gravity="center" android:layout_weight="1" android:singleLine="true" - android:textColor="@color/tv_text_default" /> + android:textColor="@color/primaryText" /> + android:tint="@color/primaryText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_search.xml b/app/src/main/res/layout/item_search.xml index 421082d13..eb0289863 100644 --- a/app/src/main/res/layout/item_search.xml +++ b/app/src/main/res/layout/item_search.xml @@ -34,7 +34,7 @@ android:layout_margin="8dp" android:singleLine="true" android:text="@string/app_name" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@id/bv_originCount" app:layout_constraintStart_toEndOf="@+id/iv_cover" @@ -57,7 +57,7 @@ android:ellipsize="end" android:lines="1" android:text="@string/author" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/item_search_list.xml b/app/src/main/res/layout/item_search_list.xml new file mode 100644 index 000000000..2dcaf1436 --- /dev/null +++ b/app/src/main/res/layout/item_search_list.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_source_import.xml b/app/src/main/res/layout/item_source_import.xml new file mode 100644 index 000000000..a4bf3fbd6 --- /dev/null +++ b/app/src/main/res/layout/item_source_import.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_text.xml b/app/src/main/res/layout/item_text.xml index 357efdde3..b8d2bcc52 100644 --- a/app/src/main/res/layout/item_text.xml +++ b/app/src/main/res/layout/item_text.xml @@ -10,6 +10,6 @@ android:padding="5dp" android:maxLines="1" android:scrollbars="none" - android:textColor="@color/tv_text_default" + android:textColor="@color/primaryText" android:textSize="14sp" tools:ignore="UnusedAttribute" /> diff --git a/app/src/main/res/layout/item_theme_config.xml b/app/src/main/res/layout/item_theme_config.xml new file mode 100644 index 000000000..008f70d15 --- /dev/null +++ b/app/src/main/res/layout/item_theme_config.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_toc_regex.xml b/app/src/main/res/layout/item_toc_regex.xml index 6872044c4..650d59d4d 100644 --- a/app/src/main/res/layout/item_toc_regex.xml +++ b/app/src/main/res/layout/item_toc_regex.xml @@ -3,7 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/background" android:padding="8dp" android:gravity="center_vertical" android:orientation="horizontal"> @@ -30,7 +29,7 @@ android:padding="6dp" android:tooltipText="@string/edit" android:src="@drawable/ic_edit" - android:tint="@color/tv_text_default" + android:tint="@color/primaryText" tools:ignore="UnusedAttribute" /> \ No newline at end of file diff --git a/app/src/main/res/layout/popup_action_menu.xml b/app/src/main/res/layout/popup_action_menu.xml index 0f6b6e0e5..ab9a681b8 100644 --- a/app/src/main/res/layout/popup_action_menu.xml +++ b/app/src/main/res/layout/popup_action_menu.xml @@ -27,7 +27,7 @@ android:layout_height="24dp" android:background="?attr/selectableItemBackgroundBorderless" android:src="@drawable/ic_more_vert" - android:tint="@color/tv_text_default" + android:tint="@color/primaryText" android:visibility="gone" android:contentDescription="@string/more_menu" /> diff --git a/app/src/main/res/layout/view_book_page.xml b/app/src/main/res/layout/view_book_page.xml index 3f09664a3..95fd4f1f3 100644 --- a/app/src/main/res/layout/view_book_page.xml +++ b/app/src/main/res/layout/view_book_page.xml @@ -1,38 +1,74 @@ - - + + + android:gravity="center_vertical"> - + + + + + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - + - + android:gravity="center_vertical"> - + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@+id/tv_footer_right" + app:layout_constraintTop_toTopOf="parent" /> - + + + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_detail_seek_bar.xml b/app/src/main/res/layout/view_detail_seek_bar.xml index af87a83bb..a63c1b8dd 100644 --- a/app/src/main/res/layout/view_detail_seek_bar.xml +++ b/app/src/main/res/layout/view_detail_seek_bar.xml @@ -21,7 +21,7 @@ android:contentDescription="@string/reduce" android:src="@drawable/ic_remove" android:background="?android:attr/selectableItemBackgroundBorderless" - android:tint="@color/tv_text_default" /> + android:tint="@color/primaryText" /> + android:tint="@color/primaryText" /> diff --git a/app/src/main/res/layout/view_preference.xml b/app/src/main/res/layout/view_preference.xml index 87b10ef5b..0fb402876 100644 --- a/app/src/main/res/layout/view_preference.xml +++ b/app/src/main/res/layout/view_preference.xml @@ -7,10 +7,11 @@ android:paddingLeft="16dp" android:paddingTop="10dp" android:paddingRight="16dp" - android:paddingBottom="4dp" + android:paddingBottom="10dp" android:minHeight="42dp" android:clickable="true" android:orientation="horizontal" + android:gravity="center_vertical" android:focusable="true"> + android:layout_height="wrap_content" + android:layout_marginTop="8dp" /> + @@ -12,13 +13,14 @@ + android:textColor="@color/accent" + tools:ignore="RtlHardcoded,RtlSymmetry" /> - - - - + + + android:layout_height="match_parent"> + + - + + + @@ -222,7 +229,7 @@ + + + + + + + + + + @@ -257,7 +306,7 @@ @@ -292,7 +344,7 @@ @@ -327,7 +382,7 @@ diff --git a/app/src/main/res/layout/view_select_action_bar.xml b/app/src/main/res/layout/view_select_action_bar.xml index 2e8d733ab..5370a932d 100644 --- a/app/src/main/res/layout/view_select_action_bar.xml +++ b/app/src/main/res/layout/view_select_action_bar.xml @@ -17,8 +17,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:padding="5dp" - android:text="@string/select_all_count" - android:textColor="@color/tv_text_default" /> + android:text="@string/select_all_count" /> diff --git a/app/src/main/res/layout/view_title_bar.xml b/app/src/main/res/layout/view_title_bar.xml index 3b7641388..b19cdc8c9 100644 --- a/app/src/main/res/layout/view_title_bar.xml +++ b/app/src/main/res/layout/view_title_bar.xml @@ -5,4 +5,5 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarStyle" + app:titleTextAppearance="@style/ToolbarTitle" app:popupTheme="@style/AppTheme.PopupOverlay"/> diff --git a/app/src/main/res/layout/view_title_bar_dark.xml b/app/src/main/res/layout/view_title_bar_dark.xml new file mode 100644 index 000000000..72e7fa3b3 --- /dev/null +++ b/app/src/main/res/layout/view_title_bar_dark.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/menu/book_group_manage.xml b/app/src/main/res/menu/book_group_manage.xml index 94ad26c3d..8c65f9f8d 100644 --- a/app/src/main/res/menu/book_group_manage.xml +++ b/app/src/main/res/menu/book_group_manage.xml @@ -10,25 +10,36 @@ app:showAsAction="always" tools:ignore="AlwaysShowAction" /> - + - + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/book_info.xml b/app/src/main/res/menu/book_info.xml index fc88821c1..ff060d6bd 100644 --- a/app/src/main/res/menu/book_info.xml +++ b/app/src/main/res/menu/book_info.xml @@ -19,4 +19,9 @@ android:checkable="true" app:showAsAction="never" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/book_source.xml b/app/src/main/res/menu/book_source.xml index 649060e9f..ed20f9b16 100644 --- a/app/src/main/res/menu/book_source.xml +++ b/app/src/main/res/menu/book_source.xml @@ -23,27 +23,47 @@ - - - - - - + android:checkableBehavior="single"> + + + + + + + + + + + + + + + @@ -76,4 +96,11 @@ android:title="@string/import_by_qr_code" app:showAsAction="never" /> + + + 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..02a6178ba 100644 --- a/app/src/main/res/menu/book_source_sel.xml +++ b/app/src/main/res/menu/book_source_sel.xml @@ -12,6 +12,16 @@ android:title="@string/disable_selection" 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..6303b5b9d 100644 --- a/app/src/main/res/menu/download.xml +++ b/app/src/main/res/menu/download.xml @@ -1,11 +1,38 @@ - + app:showAsAction="always" + tools:ignore="AlwaysShowAction" /> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/import_source.xml b/app/src/main/res/menu/import_source.xml new file mode 100644 index 000000000..37c34f4de --- /dev/null +++ b/app/src/main/res/menu/import_source.xml @@ -0,0 +1,17 @@ + + + + + + + + \ 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..9dc1ebf5c 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" /> + + - + diff --git a/app/src/main/res/menu/rss_articles.xml b/app/src/main/res/menu/rss_articles.xml index 03d187bcd..055efd8c7 100644 --- a/app/src/main/res/menu/rss_articles.xml +++ b/app/src/main/res/menu/rss_articles.xml @@ -12,4 +12,9 @@ android:title="@string/clear" app:showAsAction="never" /> + + \ 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/speak_engine.xml b/app/src/main/res/menu/speak_engine.xml new file mode 100644 index 000000000..9ca269d05 --- /dev/null +++ b/app/src/main/res/menu/speak_engine.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file 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/menu/theme_list.xml b/app/src/main/res/menu/theme_list.xml new file mode 100644 index 000000000..bd7f6194d --- /dev/null +++ b/app/src/main/res/menu/theme_list.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..879ce0647 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -1,18 +1,19 @@ - @color/md_blue_grey_600 - @color/md_blue_grey_700 - @color/md_deep_orange_800 + @color/md_blue_grey_600 + @color/md_blue_grey_700 + @color/md_deep_orange_800 @color/md_grey_900 @color/md_grey_850 @color/md_grey_800 - #AA111111 + #10303030 #69000000 #10ffffff #30ffffff + #50ffffff #363636 @@ -25,8 +26,8 @@ #737373 #565656 - #EEEEEE - #D5D5D5 + #ffffffff + #b3ffffff #B3B3B3 #b7b7b7 diff --git a/app/src/main/res/values-night/strings.xml b/app/src/main/res/values-night/strings.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/app/src/main/res/values-night/strings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/arrays.xml b/app/src/main/res/values-zh-rHK/arrays.xml new file mode 100644 index 000000000..4e75577e4 --- /dev/null +++ b/app/src/main/res/values-zh-rHK/arrays.xml @@ -0,0 +1,51 @@ + + + + + 跟隨系統 + 亮色主題 + 暗色主題 + E-Ink(墨水屏) + + + + 自動 + 黑色 + 白色 + 跟隨背景 + + + + 默認 + 1分鐘 + 2分鐘 + 3分鐘 + 常亮 + + + + 關閉 + 繁體轉簡體 + 簡體轉繁體 + + + + 系統默認字體 + 系統襯線字體 + 系統等寬字體 + + + + 正常 + 粗體 + 細體 + + + + 跟随系统 + 简体中文 + 繁体中文 + 英文 + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000..c585ffb97 --- /dev/null +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,766 @@ + + + 閲讀 + 閲讀·搜尋 + 閲讀需要訪問存儲卡權限,請前往「設定」—「應用程式權限」—開啟所需要的權限 + + + 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.play.release + + Version %s + 自動刷新 + 打開程式時自動更新書輯 + 自動下載最新章節 + 更新書輯時自動下載最新章節 + 備份與還原 + WebDav 設定 + WebDav 設定/還原舊版本數據 + 備份 + 還原 + 備份請給予存儲權限 + 還原請給予存儲權限 + 確認 + 取消 + 確認備份嗎? + 新備份會覆蓋原有備份。\n備份路徑YueDu + 確認還原嗎? + 還原成功會覆蓋原有書架。 + 備份成功 + 備份失敗 + 正在還原 + 還原成功 + 還原失敗 + 屏幕方向 + 跟隨傳感器 + 橫向 + 豎向 + 跟隨系統 + 免責聲明 + 共%d章 + 介面 + 亮度 + 目錄 + 下一章 + 上一章 + 隱藏狀態欄 + 閲讀介面隱藏狀態欄 + 朗讀 + 正在朗讀 + 點擊打開閲讀介面 + 播放 + 正在播放 + 點擊打開播放介面 + 播放暫停 + 返回 + 刷新 + 開始 + 停止 + 暫停 + 繼續 + 定時 + 朗讀暫停 + 正在朗讀(剩餘 %d 分鐘) + 閲讀介面隱藏導航欄 + 隱藏導航欄 + 導航欄顏色 + GitHub + 評分 + 發送電子郵件 + 無法打開 + 分享失敗 + 無章節 + 添加網址 + 添加書輯網址 + 背景 + 作者 + 作者: %s + 朗讀停止 + 清理緩存 + 成功清理緩存 + 保存 + 編輯源 + 編輯書源 + 禁用書源 + 新建書源 + 新建訂閲源 + 添加書輯 + 掃描 + 拷貝源 + 粘帖源 + 源規則説明 + 檢查更新 + 掃描 QR Code + 掃描本地圖片 + 規則説明 + 分享 + 應用程式分享 + 跟隨系統 + 添加 + 導入書源 + 本地導入 + 網絡導入 + 替換淨化 + 替換規則編輯 + 替換規則 + 替換為 + 封面 + + 音量鍵翻頁 + 點擊翻頁 + 點擊總是翻下一頁 + 翻頁動畫 + 屏幕超時 + 返回 + 菜單 + 調節 + 滾動條 + 清除緩存會刪除所有已保存的章節,確認是否清除? + 書源共享 + 規則替換名稱 + 替換規則為空或不滿足正則表達式要求 + 選擇操作 + 全選 + 全選 (%1$d/%2$d) + 取消 (%1$d/%2$d) + 深色模式 + 啟動頁 + 開始下載 + 取消下載 + 暫無任務 + 已下載 %1$d/%2$d + 導入選擇書輯 + 更新/搜尋線程數,太多會卡頓 + 切換圖標 + 刪除書輯 + 開始閲讀 + 數據載入中… + 載入失敗,點擊重試 + 內容簡介 + 簡介: %s + 打開外部書籍 + 來源: %s + 本地導入 + 導入在線規則 + 檢查更新間隔 + 按閲讀時間 + 按更新時間 + 按書名 + 手動排序 + 閲讀方式 + 排版 + 刪除所選 + 是否確認刪除? + 默認字體 + 發現 + 發現管理 + 沒有內容,去書源裏自定義吧! + 刪除所有 + 搜索歷史 + 清除 + 正文顯示標題 + 書源同步 + 無最新章節信息 + 顯示時間和電量 + 顯示分隔線 + 深色狀態欄圖標 + 內容 + 拷貝內容 + 一鍵緩存 + 這是一段測試文字\n\u3000\u3000只是讓你看看效果的 + 文字顏色和背景(長按自定義) + 沉浸式狀態欄 + 還剩 %d 章未下載 + 沒有選擇 + 長按輸入顏色值 + 加載中… + 追更區 + 養肥區 + 書籤 + 添加書籤 + 刪除 + 加載超時 + 關注: %s + 已拷貝 + 整理書架 + 這將會刪除所有書籍,請謹慎操作。 + 搜索書源 + 搜索訂閲源 + 搜索(共 %d 個書源) + 目錄(%d) + 加粗 + 字體 + 文字 + 軟件主頁 + + + + + 邊距 + 上邊距 + 下邊距 + 左邊距 + 右邊距 + 校驗書源 + 校驗所選 + 進度 %1$d/%2$d + 請安裝並選擇中文 TTS! + TTS 初始化失敗! + 簡繁轉換 + 關閉 + 簡轉繁 + 繁轉簡 + 翻頁模式 + %1$d 項 + 存儲卡: + 加入書架 + 加入書架 (%1$d) + 成功添加 %1$d 本書 + 請將字體文件放到 SD 根目錄 Fonts 文件夾下重新選擇 + 默認字體 + 選擇字體 + 字號 + 行距 + 段距 + 置頂 + 自動展開發現 + 默認展開第一組發現 + 當前線程數 %s + 朗讀語速 + 自動翻頁 + 停止自動翻頁 + 自動翻頁間隔 + 書籍信息 + 書籍信息編輯 + 默認打開書架 + 自動跳轉最近閲讀 + 替換範圍,選填書名或者書源url + 分組 + 內容緩存路徑 + 系統文件選擇器 + 新版本 + 下載更新 + 朗讀時音量鍵翻頁 + Tip 邊距跟隨邊距調整 + 允許更新 + 禁止更新 + 反選 + 搜索書名、作者 + 書名、作者、URL + 常見問題 + 顯示所有發現 + 關閉則只顯示勾選源的發現 + 更新目錄 + Txt目錄正則 + 設置編碼 + 倒序-順序 + 排序 + 智能排序 + 手動排序 + 拼音排序 + 滾動到頂部 + 滾動到底部 + 已讀: %s + 追更 + 養肥 + 完結 + 所有書籍 + 追更書籍 + 養肥書籍 + 完結書籍 + 本地書籍 + 狀態欄顏色透明 + 導航欄變色 + 導航欄根據夜間模式變化 + 放入書架 + 繼續閲讀 + 封面地址 + 覆蓋 + 滑動 + 仿真 + 滾動 + 無動畫 + 此書源使用了高級功能,請到捐贈裏點擊支付寶紅包搜索碼領取紅包開啟。 + 後台更新換源最新章節 + 開啟則會在軟件打開 1 分鐘後開始更新 + 書架 ToolBar 自動隱藏 + 滾動書架時 ToolBar 自動隱藏與顯示 + 登錄 + 登錄 %s + 成功 + 當前源沒有配置登陸地址 + 沒有上一頁 + 沒有下一頁 + + + 源名稱 (sourceName) + 源URL (sourceUrl) + 源分組 (sourceGroup) + 分類 Url + 登錄 URL(loginUrl) + 源注釋(sourceComment) + 搜索地址 (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 + 默認 + 主菜單 + 點擊授予權限 + 閲讀需要訪問存儲卡權限,請點擊下方的"授予權限"按鈕,或前往「設定」—「應用程式權限」—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的「選擇文件夾」,使用系統文件夾選擇器。 + 全文朗讀中不能朗讀選中文字 + 擴展到劉海 + 更新目錄中 + 全程響應耳機按鍵 + 即使退出軟件也響應耳機按鍵 + 開發人員 + 聯繫我們 + 開源許可 + 關注公衆號 + WeChat + 你的支持是我更新的動力 + 公众号[开源阅读] + 正在自動換源 + 點擊加入 + + 信息 + 切換佈局 + + + 主色調 + 強調色 + 背景色 + 導航欄顏色 + 白天 + 白天,主色調 + 白天,強調色 + 白天,背景色 + 白天,導航欄顏色 + 夜間 + 夜間,主色調 + 夜間,強調色 + 夜間,背景色 + 夜間,導航欄顏色 + 隱藏頁眉 + 隱藏頁脚 + 自動換源 + 置底 + 文字兩端對齊 + 自動翻頁速度 + 地址排序 + 文章字體轉換 + 請選擇備份路徑 + 其它 + legado-top + 本地和WebDav壹起備份 + 優先從WebDav恢復,長按從本地恢復 + 選擇舊版備份文件夾 + 已啓用 + 已禁用 + 文字底部對齊 + 正在啟動下載 + 該書已在下載列表 + 點擊打開 + 關注[legado-top]點擊廣告支持我 + 微信赞赏码 + 支付寶 + 支付寶紅包搜索碼 + 537954522 點擊複製 + 支付寶紅包二維碼 + 支付寶收款二維碼 + QQ收款二維碼 + gedoor,Invinciblelee等,详情请在github中查看 + 清除已下載書籍和字體緩存 + 默認封面 + 恢復忽略列表 + 恢復時忽略一些內容不恢復,方便不同手機配置不同 + 閱讀界面設置 + 图片样式(imageStyle) + 替换规则(replaceRegex) + 分組名稱 + 備註內容 + E-Ink模式下只有白纸黑字,不显示其它背景 + 默认启用替换净化 + 新加入书架的书是否启用替换净化 + 选择恢复文件 + 白天背景不能太暗 + 白天底栏不能太暗 + 夜间背景不能太亮 + 夜间底栏不能太亮 + 强调色不能和背景颜色相似 + 强调色不能和文字颜色相似 + 格式不对 + 错误 + 显示亮度调节控件 + 语言 + 导入订阅源 + 您的支持是我更新的动力 + 公众号[开源阅读软件] + 阅读记录 + 阅读时间记录 + 本地TTS + 线程数 + 总阅读时间 + 全不选 + 删除所有 + 导入 + 导出 + 保存主题配置 + 保存白天主题配置以共调用和分享 + 保存夜间主题配置以共调用和分享 + 主题列表 + 使用保存主题,导入,分享主题 + 切換默認主題 + 分享選中書源 + 時間排序 + 搜索 + + 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..23909ad85 --- /dev/null +++ b/app/src/main/res/values-zh-rTW/arrays.xml @@ -0,0 +1,107 @@ + + + + @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/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..006b4ff45 --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,767 @@ + + + 閱讀 + 閱讀·搜尋 + 閱讀需要存取記憶卡權限,請前往“設定”—“應用程式權限”—打開所需權限 + + + 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.play.release + + 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 + 朗讀語速 + 自動翻頁 + 停止自動翻頁 + 自動翻頁間隔 + 書籍訊息 + 書籍訊息編輯 + 預設打開書架 + 自動跳轉最近閱讀 + 取代範圍,選填書名或者書源url + 分組 + 內容快取路徑 + 系統檔案選擇器 + 新版本 + 下載更新 + 朗讀時音量鍵翻頁 + Tip邊距跟隨邊距調整 + 允許更新 + 禁止更新 + 反選 + 搜尋書名、作者 + 書名、作者、URL + 常見問題 + 顯示所有發現 + 關閉則只顯示勾選源的發現 + 更新目錄 + Txt目錄正則 + 設定編碼 + 倒序-順序 + 排序 + 智慧排序 + 手動排序 + 拼音排序 + 滾動到頂部 + 滾動到底部 + 已讀: %s + 追更 + 養肥 + 完結 + 所有書籍 + 追更書籍 + 養肥書籍 + 完結書籍 + 本機書籍 + 狀態欄顏色透明 + 導航欄變色 + 導航欄根據夜間模式變化 + 放入書架 + 繼續閱讀 + 封面地址 + 覆蓋 + 滑動 + 模擬 + 滾動 + 無動畫 + 此書源使用了進階功能,請到捐贈裡點擊支付寶紅包搜尋碼領取紅包開啟。 + 後台更新換源最新章節 + 開啟則會在軟體打開1分鐘後開始更新 + 書架ToolBar自動隱藏 + 滾動書架時ToolBar自動隱藏與顯示 + 登入 + 登入%s + 成功 + 目前源沒有配置登入地址 + 沒有上一頁 + 沒有下一頁 + + + 源名稱(sourceName) + 源URL(sourceUrl) + 源分組(sourceGroup) + 分類Url + 登入URL(loginUrl) + 搜尋地址(url) + 發現地址規則(url) + 源注釋(sourceComment) + 書籍列表規則(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 復原,長按從本機復原 + 選擇舊版備份資料夾 + 已啟用 + 已禁用 + 文字底部對齊 + 正在啟動下載 + 該書已在下載列表 + 點擊打開 + 關注[legado-top]點擊廣告支持我 + 微信贊賞碼 + 支付寶 + 支付寶紅包搜尋碼 + 537954522 點擊複製 + 支付寶紅包二維碼 + 支付寶收款二維碼 + QQ收款二維碼 + gedoor,Invinciblelee等,詳情請在github中查看 + 清除已下載書籍和字體快取 + 預設封面 + 復原忽略列表 + 復原時忽略一些內容不復原,方便不同手機配置不同 + 閱讀介面設定 + 圖片樣式(imageStyle) + 取代規則(replaceRegex) + 分組名稱 + 備註內容 + E-Ink模式下只有白紙黑字,不顯示其它背景 + 預設啟用取代淨化 + 新加入書架的書是否啟用取代淨化 + 選擇復原文件 + 白天背景不能太暗 + 白天底欄不能太暗 + 夜間背景不能太亮 + 夜間底欄不能太亮 + 強調色不能和背景顏色相似 + 強調色不能和文字顏色相似 + 格式不對 + 錯誤 + 顯示亮度調節控制項 + 語言 + 匯入訂閱源 + 您的支援是我更新的動力 + 公眾號[開源閱讀軟體] + 閱讀記錄 + 閱讀時間記錄 + 本機TTS + 執行緒數 + 總閱讀時間 + 全不選 + 刪除所有 + 导入 + 导出 + 保存主题配置 + 保存白天主题配置以共调用和分享 + 保存夜间主题配置以共调用和分享 + 主题列表 + 使用保存主题,导入,分享主题 + 切換默認主題 + 分享選中書源 + 時間排序 + 搜索 + + + diff --git a/app/src/main/res/values-zh/arrays.xml b/app/src/main/res/values-zh/arrays.xml new file mode 100644 index 000000000..5fdf42b1d --- /dev/null +++ b/app/src/main/res/values-zh/arrays.xml @@ -0,0 +1,107 @@ + + + + @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/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 + + + + 关闭 + 繁体转简体 + 简体转繁体 + + + + 系统默认字体 + 系统衬线字体 + 系统等宽字体 + + + + + 标题 + 时间 + 电量 + 页数 + 进度 + 页数及进度 + + + + 正常 + 粗体 + 细体 + + + + 跟随系统 + 简体中文 + 繁体中文 + 英文 + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml new file mode 100644 index 000000000..78487a2be --- /dev/null +++ b/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,766 @@ + + + 阅读 + 阅读·搜索 + 阅读需要访问存储卡权限,请前往“设置”—“应用权限”—打开所需权限 + + + Home + 恢复 + 导入阅读数据 + 创建子文件夹 + 创建legado文件夹作为备份文件夹 + 备份路径 + 请选择备份路径 + 导入旧版数据 + 导入Github数据 + 净化替换 + Send + + 提示 + 取消 + 确定 + 去设置 + 无法跳转至设置界面 + + 点击重试 + 正在加载 + 提醒 + 编辑 + 删除 + 删除所有 + 替换 + 替换净化 + 配置替换净化规则 + 暂无 + 启用 + 替换净化-搜索 + 书架 + 收藏夹 + 收藏 + 已收藏 + 未收藏 + 订阅 + 全部 + 最近阅读 + 最后阅读 + 更新日志 + 书架还空着,先去添加吧! + 搜索 + 下载 + 列表 + 网格三列 + 网格四列 + 网格五列 + 网格六列 + 书架布局 + 视图 + 书城 + 添加本地 + 书源 + 书源管理 + 新建/导入/编辑/管理书源 + 设置 + 主题设置 + 与界面/颜色相关的一些设置 + 其它设置 + 与功能相关的一些设置 + 关于 + 捐赠 + 退出 + 尚未保存,是否继续编辑 + 阅读样式设置 + 版本 + 本地 + 搜索 + 来源: %s + 最近: %s + 书名 + 最新: %s + 是否将《%s》放入书架? + 共%s个Text文件 + 加载中… + 重试 + Web 服务 + 启用Web服务 + web编辑书源 + http://%1$s:%2$d + 离线缓存 + 离线缓存 + 缓存选择的章节到本地 + 换源 + + \u3000\u3000这是一款使用Kotlin全新开发的开源的阅读软件,欢迎您的加入。关注公众号[开源阅读]! + + + 阅读3.0下载地址:\nhttps://www.coolapk.com/apk/256030 + + 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 + 朗读语速 + 自动翻页 + 停止自动翻页 + 自动翻页间隔 + 书籍信息 + 书籍信息编辑 + 默认打开书架 + 自动跳转最近阅读 + 替换范围,选填书名或者书源url + 分组 + 内容缓存路径 + 系统文件选择器 + 新版本 + 下载更新 + 朗读时音量键翻页 + Tip边距跟随边距调整 + 允许更新 + 禁止更新 + 反选 + 搜索书名、作者 + 书名、作者、URL + 常见问题 + 显示所有发现 + 关闭则只显示勾选源的发现 + 更新目录 + Txt目录正则 + 设置编码 + 倒序-顺序 + 排序 + 智能排序 + 手动排序 + 拼音排序 + 滚动到顶部 + 滚动到底部 + 已读: %s + 追更 + 养肥 + 完结 + 所有书籍 + 追更书籍 + 养肥书籍 + 完结书籍 + 本地书籍 + 状态栏颜色透明 + 导航栏变色 + 导航栏根据夜间模式变化 + 放入书架 + 继续阅读 + 封面地址 + 覆盖 + 滑动 + 仿真 + 滚动 + 无动画 + 此书源使用了高级功能,请到捐赠里点击支付宝红包搜索码领取红包开启。 + 后台更新换源最新章节 + 开启则会在软件打开1分钟后开始更新 + 书架ToolBar自动隐藏 + 滚动书架时ToolBar自动隐藏与显示 + 登录 + 登录%s + 成功 + 当前源没有配置登陆地址 + 没有上一页 + 没有下一页 + + + 源名称(sourceName) + 源URL(sourceUrl) + 源分组(sourceGroup) + 分类Url + 登录URL(loginUrl) + 源注释(sourceComment) + 搜索地址(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 + 默认 + 主菜单 + 点击授予权限 + 阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。 + 全文朗读中不能朗读选中文字 + 扩展到刘海 + 更新目录中 + 全程响应耳机按键 + 即使退出软件也响应耳机按键 + 开发人员 + 联系我们 + 开源许可 + 其它 + 开源阅读 + 关注公众号 + 微信 + 您的支持是我更新的动力 + 公众号[开源阅读] + 正在自动换源 + 点击加入 + + 信息 + 隐藏页眉 + 隐藏页脚 + 切换布局 + 文章字重切换 + + + 主色调 + 强调色 + 背景色 + 底部操作栏颜色 + 白天 + 白天,主色调 + 白天,强调色 + 白天,背景色 + 白天,底栏色 + 夜间 + 夜间,主色调 + 夜间,强调色 + 夜间,背景色 + 夜间,底栏色 + 自动换源 + 文字两端对齐 + 文字底部对齐 + 自动翻页速度 + 地址排序 + 本地和WebDav一起备份 + 优先从WebDav恢复,长按从本地恢复 + 选择旧版备份文件夹 + 已启用 + 已禁用 + 正在启动下载 + 该书已在下载列表 + 点击打开 + 关注[开源阅读]点击广告支持我 + 微信赞赏码 + 支付宝 + 支付宝红包搜索码 + 537954522 点击复制 + 支付宝红包二维码 + 支付宝收款二维码 + QQ收款二维码 + gedoor,Invinciblelee等,详情请在github中查看 + 清除已下载书籍和字体缓存 + 默认封面 + 恢复忽略列表 + 恢复时忽略一些内容不恢复,方便不同手机配置不同 + 阅读界面设置 + 图片样式(imageStyle) + 替换规则(replaceRegex) + 分组名称 + 备注内容 + E-Ink模式下只有白纸黑字,不显示其它背景 + 默认启用替换净化 + 新加入书架的书是否启用替换净化 + 选择恢复文件 + 白天背景不能太暗 + 白天底栏不能太暗 + 夜间背景不能太亮 + 夜间底栏不能太亮 + 强调色不能和背景颜色相似 + 强调色不能和文字颜色相似 + 格式不对 + 错误 + 显示亮度调节控件 + 语言 + 导入订阅源 + 您的支持是我更新的动力 + 公众号[开源阅读软件] + 阅读记录 + 阅读时间记录 + 本地TTS + 线程数 + 总阅读时间 + 全不选 + 导入 + 导出 + 保存主题配置 + 保存白天主题配置以共调用和分享 + 保存夜间主题配置以共调用和分享 + 主题列表 + 使用保存主题,导入,分享主题 + 切换默认主题 + 时间排序 + 搜索 + + diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml index 40bc6684e..a545e3641 100644 --- a/app/src/main/res/values/array_values.xml +++ b/app/src/main/res/values/array_values.xml @@ -23,21 +23,7 @@ 0 1 2 - - - - 0 - 1 3 - 4 - 5 - 103 - 106 - 110 - 111 - 11 - 6 - 9 @@ -47,4 +33,11 @@ 3 + + auto + zh + tw + en + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 39c85cae3..5847f7765 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,99 +1,107 @@ - - @string/book_type_text - @string/book_type_audio - + + @string/book_type_text + @string/book_type_audio + - - 度小美 - 度小宇 - 度逍遥 - 度丫丫 - 度小娇 - 度米朵 - 度博文 - 度小童 - 度小萌 - 百度骚男 - 百度评书 - 百度主持 - + + @string/indent_0 + @string/indent_1 + @string/indent_2 + @string/indent_3 + @string/indent_4 + - - @string/indent_0 - @string/indent_1 - @string/indent_2 - @string/indent_3 - @string/indent_4 - + + .txt + .json + .xml + - - .txt - .json - .xml - + + @string/jf_convert_o + @string/jf_convert_j + @string/jf_convert_f + - - @string/jf_convert_o - @string/jf_convert_j - @string/jf_convert_f - + + Follow system + Bright mode + Dark mode + E-Ink mode + - - 跟随系统 - 亮色主题 - 暗色主题 - + + Auto + Black + White + Following + - - 自动 - 黑色 - 白色 - 跟随背景 - + + Default + 1 min + 2 min + 3 min + Always + - - 默认 - 1分钟 - 2分钟 - 3分钟 - 常亮 - + + @string/sys_folder_picker + @string/app_folder_picker + - - @string/default_path - @string/sys_folder_picker - @string/app_folder_picker - + + @string/screen_unspecified + @string/screen_portrait + @string/screen_landscape + @string/screen_sensor + - - @string/screen_unspecified - @string/screen_portrait - @string/screen_landscape - @string/screen_sensor - + + iconMain + icon1 + icon2 + icon3 + icon4 + icon5 + icon6 + - - iconMain - icon1 - icon2 - icon3 - icon4 - icon5 - icon6 - + + Off + Traditional to Simplified + Simplified to Traditional + - - 关闭 - 繁体转简体 - 简体转繁体 - + + Default font + Serif font + Monospaced font + - - 系统默认字体 - 系统衬线字体 - 系统等宽字体 - + + Blank + Heading + Time + Battery + Pages + Progress + Pages and progress + + + + Normal + Bold + Light + + + + 跟随系统 + 简体中文 + 繁体中文 + 英文 + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 9e920088b..bdb8fd0b1 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -2,124 +2,132 @@ + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - - - - - + + + + + + + - - - - + + + + - + - - - - + + + + @@ -161,6 +169,7 @@ + @@ -174,16 +183,29 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + \ 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..30e85cda3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,12 +1,11 @@ - @color/md_light_blue_600 - @color/md_light_blue_700 - @color/md_pink_800 + @color/md_light_blue_600 + @color/md_light_blue_700 + @color/md_pink_800 #66666666 #FF578FCC - #FF212227 #eb4333 #439b53 @@ -16,11 +15,12 @@ @color/md_grey_50 @color/md_grey_100 @color/md_grey_200 - #AAFFFFFF + #7fffffff #00000000 #10000000 #30000000 + #50000000 #d3321b @@ -31,8 +31,8 @@ #737373 #adadad - #0E0E0E - #2C2C2C + #de000000 + #8a000000 #8A2C2C2C #dfdfdf #383838 diff --git a/app/src/main/res/values/pref_key_value.xml b/app/src/main/res/values/pref_key_value.xml index 0c535ad67..42058ce0c 100644 --- a/app/src/main/res/values/pref_key_value.xml +++ b/app/src/main/res/values/pref_key_value.xml @@ -1,23 +1,23 @@ - auto_refresh - list_screen_direction - full_screen - threads_num - user_agent - bookshelf_px - read_type - expandGroupFind - defaultToRead - autoDownload - downloadPath - 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://alanskycn.gitee.io/teachme/ + 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 65deb8edd..893230b30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,387 +1,393 @@ - 悦读 - Open navigation drawer - Close navigation drawer - Android Studio - android.studio@android.com - Navigation header - Settings + + Legado + Legado·search + Legado needs storage access to find and read books. please go "App Settings" to allow "Storage permission". + Home - 恢复 - 导入阅读数据 - 导入Github数据 - 净化替换 + Restore + Import Legado data + Create a subfolder + Create a folder named Legado as the backup folder. + Backup to + Please select a backup path. + Import legacy data + Import github data + Replacement Send - 提示 - 取消 - 确定 - 去设置 + Prompt + Cancel + Confirm + Go to Settings + Cannot jump to Settings. - 无法跳转至设置界面 - 悦读需要访问存储卡权限,请前往“设置”—“应用权限”—打开所需权限 - - 点击重试 - 正在加载 - 提醒 - 编辑 - 删除 - 替换 - 替换净化 - 配置替换净化规则 - 暂无 - 启用 - 替换净化-搜索 - 书架 - 收藏夹 - 收藏 - 已收藏 - 未收藏 - 订阅 - 全部 - 最近阅读 - 最后阅读 - 更新日志 - 悦读·搜索 - 书架还空着,先去添加吧! - 搜索 - 下载 - 列表 - 网格三列 - 网格四列 - 网格五列 - 网格六列 - 书架布局 - 视图 - 书城 - 添加本地 - 书源 - 书源管理 - 新建/导入/编辑/管理书源 - 设置 - 主题设置 - 与界面/颜色相关的一些设置 - 其它设置 - 与功能相关的一些设置 - 关于 - 捐赠 - 退出 - 尚未保存,是否继续编辑 - 阅读样式设置 - 版本 - 本地 - 搜索 - 来源: %s - 最近: %s - 书名 - 最新: %s - 是否将《%s》放入书架? - 共%s个Text文件 - 加载中… - 重试 - Web 服务 - 启用Web服务 - web编辑书源 + Retry + Loading + Warning + Edit + Delete + Delete all + Replace + Replacement + Configure replacement rules + Not available now + Enable + Search Replacement + Bookshelf + Favorites + Favorite + in Favorites + Not in Favorites + Subscription + All + Recent reading + Last reading + What\'s new + Still empty,please add it first. + Search + Download + List + Grid-3 + Grid-4 + Grid-5 + Grid-6 + Layout + View + Library + Import books + Book sources + Sources management + Create/Import/Edit/Manage Book sources + Settings + Theme settings + Some settings related to interface or color + Other settings + Some function-related settings + About + Donations + Exit + It has not been saved. Do you want to continue editing? + Book styles + Version + Local + Search + Origin: %s + Origin: %s + Title + Latest: %s + Would you like to add %s to your Bookshelf? + %s text file(s) in total + Loading… + Retry + Web service + Enable web service + Edit book sources on the web http://%1$s:%2$d - 离线下载 - 离线下载 - 下载选择的章节到本地 - 换源 + Offline cache + Offline cache + cache the selected chapter(s) to Storage + Change Origin - \u3000\u3000这是一款使用Kotlin全新开发的开源的阅读软件,欢迎您的加入。关注公众号[开源阅读软件]! + \u3000\u3000 This is an open source reading software newly developed by Kotlin, welcome to join us. Follow the WeChat Official Account [开源阅读]! - 阅读3.0下载地址:\nhttps://play.google.com/store/apps/details?id=io.legado.app + Legado (YueDu 3.0) download link:\n https://play.google.com/store/apps/details?id=io.legado.play.release Version %s - 自动刷新 - 打开软件时自动更新书籍 - 自动下载最新章节 - 更新书籍时自动下载最新章节 - 备份与恢复 - WebDav设置 - WebDav设置/导入旧版本数据 - 备份 - 恢复 - 备份请给与存储权限 - 恢复请给与存储权限 - 确认 - 取消 - 确认备份吗? - 新备份会替换原有备份。\n备份文件夹YueDu - 确认恢复吗? - 恢复书架会覆盖现有书架。 - 备份成功 - 备份失败 - 正在恢复 - 恢复成功 - 恢复失败 - 屏幕方向 - 跟随传感器 - 横向 - 竖向 - 跟随系统 - 免责声明 - 共%d章 - 界面 - 亮度 - 目录 - 下一章 - 上一章 - 隐藏状态栏 - 阅读界面隐藏状态栏 - 朗读 - 正在朗读 - 点击打开阅读界面 - 播放 - 正在播放 - 点击打开播放界面 - 播放暂停 - 返回 - 刷新 - 开始 - 停止 - 暂停 - 继续 - 定时 - 朗读暂停 - 正在朗读(还剩%d分钟) - 阅读界面隐藏虚拟按键 - 隐藏导航栏 - 导航栏颜色 + Auto-refresh + Update books automatically when opening the software + Auto-download + Download the latest chapters automatically when updating books + Backup and restore + WebDav settings + WebDav settings/Import legacy data + Backup + Restore + Backup needs storage permission + Restore needs storage permission + OK + Cancel + Backup confirmation + The new backup files will replace the original.\n Backup folder: YueDu + Restore confirmation + Restoring the bookshelf data will overwrite the current Bookshelf. + Backup succeed + Backup failed + Restoring + Restore succeed + Backup failed + Screen orientation + Auto(sensor) + Landscape + Portrait + Follow system + Disclaimer + %d chapters + Interface + Brightness + Chapters + Next + Prior + Hide status bar + Hide system navigation bar when reading + Speech + Speaking + Click to open the Reading + Play + Playing + Click to open the Playing + Pause + Back + Refresh + Start + Stop + Pause + Resume + Timer + Speak Paused + Speaking(%d min left) + Hide virtual buttons when reading + Hide navigation bar + Navigation bar color 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 - 成功 - 当前源没有配置登陆地址 + Rating + Email + Open failed + Share failed + No chapters + Add url + Add book url + Background + Author + Author: %s + Speak Stopped + Clear cache + Cache cleared + Save + Edit source + Edit Book source + Disable Book source + Add Book source + Add subscription source + Add books + Scan + Copy source + Paste source + Source rules description + Check for Updates + Scan QR code + Scan local images + Rules description + Share + Share to + Follow system + Add + Import book sources + Import local sources + Import online sources + Replacement + Edit replacement rule + Pattern + Replacement + Cover + Book + Volume keys to turn page + Tap screen to turn page + Tap screen always to next page + Flip animation + Keep screen awake + Back + Menu + Adjust + Scroll bar + Clearing the cache will delete all saved chapters. Are you sure to delete it? + Book sources sharing + Rule name + Pattern rule is empty or does not conform the regex specification. + Selection action + Select all + Select all(%1$d/%2$d) + Cancel(%1$d/%2$d) + Dark mode + Welcome page + Download start + Download cancel + No download + Downloaded %1$d/%2$d + Import selected book(s) + Number of concurrent tasks + Change icon + Remove + Start reading + Loading… + Load failed, tap to retry + Book description + Description:%s + Open external book + Origin: %s + Import local rules + Import online rules + Check interval for updates + By recent list + By update time + By book title + By sort manually + Reading strategy + Typesetting + Delete selected + Are you sure to delete? + Default font + Discovery + Discovery + No content.Go to Sources management to add it! + Delete all + Search history + Clear + Display book title on text + Book Sources sync + No latest chapter. + Display time and battery + Display divider + Darken the status bar\'s icon color + Content + Copy + Download all + This is a test text, \n\u3000\u3000 just to show you the effect + Color and background (long tap to customize) + Immersive status bar + %d chapter(s) left + No selected + Long tap to input color value + Loading… + Awaiting + Awaiting more + Bookmarks + Add to Bookmarks + Delete + Loading timeout + Follow:%s + Copied successfully + Bookshelf arrangement + It will delete all books. Be careful,please. + Search book sources + Search subscription sources + Search( %d sources in total) + Chapters(%d) + Bold + Font + Text + Home page + Right + Left + Bottom + Top + Padding + Padding top + Padding bottom + Padding left + Padding right + Check book sources + Check the selected source + Progress %1$d/%2$d + Please install and select Chinese TTS! + TTS initialization failed! + Simplified conversion + Off + Simplified to traditional + Traditional to simplified + Flipping mode + %1$d items + Storage: + Add to Bookshelf + Add to Bookshelf(%1$d) + %1$d books added successfully + Please put the font files in the Fonts folder of the storage root directory and reselect + Default font + Select fonts + Text size + Line spacing + Paragraph spacing + To Top + To Bottom + Auto expand Discovery + Default expand the first Discovery. + Current threads %s + Speech rate + Auto scroll + Stop Auto scroll + Auto scroll speed + Book infomation + Edit book infomation + Use Bookshelf as start page + Auto jump to Recent list + Replacement object. Book name or source url is available + Groups + Cache path + System file picker + New version + Download updates + Volume keys to turn page when reading + Margin adjustment + Enable update + Disable update + Inverse + Search book name/author + Book name,Author,URL + FAQ + Display all Discovery + Display the selected origin\'s Discovery if closed + Update chapters + Txt Chapters Regex + Text encoding + Ascending/Descending order + Sort + Sort automatically + Sort manually + Sort phonetically + Scroll to the top + Scroll to the bottom + Read: %s + Awaiting update + Awaiting more + Finished + All + Awaiting update books + Awaiting more chapters books + Finished books + Local books + The status bar color becomes transparent + Discolored navigation bar + The navigation bar changes color following the dark mode + Add to Bookshelf + Continue reading + Cover path + Cover + Slide + Simulation + Scroll + None + This book source uses advanced features, please go to the Donations and tap the Alipay red envelope search code to receive the red envelope,then you can use it. + Update the latest chapter after changed origin in the background + if enabled,the update will start 1 minute later when the software is run + Auto hide ToolBar + The toolbar will be hidden automatically when scroll the Bookshelf + Login + Login%s + Success + The current source has not configured with a login address + No prior page + No next page 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 分类Url 登录URL(loginUrl) + 源注释(sourceComment) + 请求头(header) 搜索地址(url) 发现地址规则(url) 书籍列表规则(bookList) @@ -401,11 +407,13 @@ 章节名称规则(ChapterName) 章节URL规则(chapterUrl) VIP标识(isVip) - 章节信息(ChapterInfo) + 更新时间(ChapterInfo) 正文规则(content) 正文下一页URL规则(nextContentUrl) webJs 资源正则(sourceRegex) + 替换规则(replaceRegex) + 图片样式(imageStyle) 图标(sourceIcon) 列表规则(ruleArticles) @@ -417,238 +425,344 @@ 描述规则(ruleDescription) 图片url规则(ruleImage) 内容规则(ruleContent) + 样式(style) 链接规则(ruleLink) - 没有书源 - 书籍信息获取失败 - 内容获取失败 - 目录获取失败 - 访问网站失败:%s - 文件读取失败 - 加载目录失败 - 获取数据失败! - 加载失败\n%s - 没有网络 - 网络连接超时 - 数据解析失败 + No source + Failed to obtain book information + Failed to obtain content + Failed to obtain chapters list + Failed to access website:%s + Failed to read file + Failed to load chapters list + Failed to get data + Failed to load\n%s + No network + Network connection timeout + Data parsing failed - 请求头(header) - 调试源 - 二维码导入 - 扫描二维码 - 选中时点击可弹出菜单 - 主题 - 主题模式 - 选择主题模式 - 默认主题 - 恢复主题为默认配色 - 加入QQ群 - 获取背景图片需存储权限 - 输入书源网址 - 删除文件 - 删除文件成功 - 确定删除文件吗? - 手机目录 - 智能导入 - 发现 - 切换显示样式 - 导入本地书籍需存储权限 - 夜间模式 - E-Ink 模式 - 电子墨水屏模式 - 本软件需要存储权限来存储备份书籍信息 - 再按一次退出程序 - 导入本地书籍需存储权限 - 网络连接不可用 - - - 确认 - 是否确认删除? - 是否删除全部书籍? - 是否同时删除已下载的书籍目录? - 扫描二维码需相机权限 - 朗读正在运行,不能自动翻页 - 输入编码 - TXT目录规则 - 打开外部书籍需获取存储权限 - 未获取到书名 - 输入替换规则网址 - 搜索列表获取成功%d - 书源名称和URL不能为空 - 图库 - 领支付宝红包 - 没有获取到更新地址 - 正在打开首页,成功自动返回主界面 - 登录成功后请点击右上角图标进行首页访问测试 - - - 使用正则表达式 - 缩进 - 无缩进 - 一字符缩进 - 二字符缩进 - 三字符缩进 - 四字符缩进 - 选择文件夹 - 选择文件 - 没有发现,可以在书源里添加。 - 恢复默认 - 自定义缓存路径需要存储权限 - 黑色 - 文章内容为空 - 正在换源请等待… - 目录列表为空 - 字距 - 基本 - 搜索 - 发现 - 详情 - 目录 - 正文 + Debug source + Import from QR code + Scan QR code + Tap to display Menu when selected + Theme + Theme mode + Select a theme you want + Join QQ group + Set the background image requires storage permission + Input book source address + Delete file + Deleted file + Are you sure to delete this file? + Directory + Intelligent import + Discovery + Switch display styles + Import local books requires storage permission + Night Theme + E-Ink + Optimization for E-ink devices + This software requires storage permission to store the book information to be backed up + Tap again to exit the program + Import local books requires storage permission + Network connection is not available + Yes + No + OK + Are you sure to delete it? + Are you sure to delete all books? + Do you want to delete the downloaded book chapters at the same time? + Scan QR code requires Camera permissions + Speech is running, cannot turn pages automatically + Input encoding + Txt Chapters Regex + Open local books requires storage permission + No bookName + Input replacement rule URL + Search list obtained successfully%d + Source name and URL cannot be empty + Gallery + get AliPay red envelopes + No update address + Opening the homepage, it will return to start page automatically after success + After successful login, please tap the icon on the upper right corner to test the homepage access + Chapter + To + Using Regex + Indent + None + Indent with 1 chars + Indent with 2 chars + Indent with 3 chars + Indent with 4 chars + Select a folder + Select a file + No Discovery, you can add it in BookSource + Restore default + Custom cache path requires Storage permission + Black + No content + Changing source, wait please + Chapters is empty + Word spacing - E-Ink 模式 - 去除动画,优化电纸书使用体验 - Web服务 - web端口 - 当前端口 %s - 二维码分享 - 字符串分享 - wifi分享 - 请给于存储权限 - 减速 - 加速 - 上一个 - 下一个 - 音乐 - 音频 - 启用 - 启用JS - 加载BaseUrl - 全部书源 - 输入不能为空 - 清空发现缓存 - 编辑发现 - 切换软件显示在桌面的图标 - 帮助 - 我的 - 阅读 + Basic + Search + Discovery + Information + Chapters + Content + + E-Ink mode + Remove animations and optimize the experience of using E-paper books + Web service + Web port + Current port %s + QR code sharing + Strings sharing + Wifi sharing + Please grant Storage Permission + Speed down + Speed up + Prior + Next + Music + Audio + Enable + Enable js + Load BaseUrl + All Sources + The input content cannot be empty + Clear Discovery cache + Edit Discovery + Switch the software icon displayed on the desktop + Help + Me + Read %d%% - %d分钟 - 自动亮度%s - 按页朗读 - 在线朗读 - 背景图片 - 背景颜色 - 文字颜色 - 选择图片 - 分组管理 - 分组选择 - 编辑分组 - 移入分组 - 添加分组 - 新建替换 - 分组 - 分组: %s - 目录: %s - 启用发现 - 禁用发现 - 启用所选 - 禁用所选 - 导出所选 - 导出 - 加载目录 + %d min + Auto-Brightness %s + Speak by pages + Speak Engine + Background images + Background color + Text color + Select a picture + Group management + Group selection + Group editing + Move to group + Add to Groups + Remove from Groups + New replacement + Group + Group: %s + Chapters: %s + Enable Discovery + Disable Discovery + Enable selected + Disable selected + Export selected + Export + Load chapters TTS - 输入你的WebDav授权密码 - 输入你的服务器地址 - 输入你的WebDav账号 - 订阅源 - 编辑订阅源 - 筛选 - 筛选发现 - 当前位置: - 精准搜索 - 正在启动服务 - - 文件选择 - 文件夹选择 - 我是有底线的 - Uri转Path失败 - 刷新封面 - 封面换源 - 选择本地图片 - 类型: - 文本 - 音频 - 后台 - 正在导入 - 正在导出 - 自定义翻页按键 - 上一页按键 - 下一页按键 - 先将书籍加入书架 - 未分组 - 上一句 - 下一句 - 其它目录 - 文字太多,生成二维码失败 - 分享RSS源 - 分享书源 - 自动切换夜间模式 - 夜间模式跟随系统 - 上级 - 在线朗读音色 + WebDav password + Input you WebDav authorized password + Input you server address + WebDav server address + WebDav account + Input your WebDav account + Subscription source + Edit Subscription source + Filter + Search Discovery sources + Current location: + Precise search + Starting service + Empty + File selection + Folder selection + I AM OVER! + Uri To Path failed + Refresh cover + Change origin + Local image + Type: + Text + Audio + Background + Importing + Exporting + Set page-turning buttons + Page up button + Page down button + Add this book to Bookshelf first + Ungrouped + Prior sentence + Next sentence + Other folder + There are too many words to create a QR code + Subscription sources sharing + Book sources sharing + Automatic switching dark mode + Following system dark mode + Go back + Online Speech tone (%1$d/%2$d) - 显示订阅 - 服务已停止 - 正在启动服务\n具体信息查看通知栏 - 默认路径 - 系统文件夹选择器 - 自带选择器\n(Android10以上因权限限制可能无法使用) - Android10以上因权限限制可能无法读写文件 - 长按文字在操作菜单中显示悦读·搜索 - 文字操作显示搜索 - 记录日志 - 中文简繁体转换 - 图标为矢量图标,Android8.0以前不支持 - 朗读设置 - 主界面 - 长按选择文本 - 页眉 - 正文 - 页脚 - 文本选择结束位置 - 文本选择开始位置 - 共用布局 - 浏览器 - 导入默认规则 - 名称 - 正则 - 更多菜单 - - - 系统内置字体样式 - 删除源文件 - 预设一 - 预设二 - 预设三 - 标题 - 靠左 - 居中 - 隐藏 - 加入分组 - 保存图片 - 没有默认路径 - 设置分组 - 查看目录 - 导航栏阴影 - 当前阴影大小(elevation): %s - 默认 - 主菜单 - 点击授予权限 - 阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。 - 全文朗读中不能朗读选中文字 - + Display Subscription + Service stopped + Starting service\nChecking notification bar for details + Default path + System folder picker + App folder picker\n(Android 10+ May not be available due to permission restrictions) + Android 10+ unable to read and write file due to permission restrictions + Long tap to display Legado·Search in the operation menu + Text operation display Search + Log + Log + Simplified conversion + The icon is a vector icon, which was not supported before Android 8.0 + Speech settings + Start page + Long Tap to select text + Header + Content + Footer + Select end + Select start + Shared layout + Browser + Import default rules + Name + Regex + More menu + Minus + Plus + System typeface + Delete source file + Default-1 + Default-2 + Default-3 + Title + Left + Center + Hide + Add to Group + Save image + No default path + Group settings + View Chapters + Navigation bar shadow + Current shadow size(elevation): %s + Default + Main menu + Tap to grant permission + Legado needs Storage permission, please tap the "Grant Permission" button below, or go to "Settings"-"Application Permissions"-to open the required permission. If the permission is still not work, please tap "Select Folder" in the upper right corner to use the system folder picker. + The selected text cannot be spoken in full text speech + Extend to cutout + Updating Chapters + Headset buttons are always available + Headset buttons are available even exit the app. + Contributors + Contact + License + Other + 开源阅读 + Follow WeChat Official Accounts + WeChat + Supporting me will be appreciated + Official Accounts[开源阅读] + Changing source + Tap to join + Middle + Information + Hide header + Hide footer + Switch Layout + Text font weight switching + + + Primary + Accent + Background color + NavBar color + Day + Day,Primary + Day,Accent + Day,Background color + Day,NavBar color + Night + Night,Primary + Night,Accent + Night,Background color + Night,NavBar color + Change source automatically + Text justified + Text align bottom + Auto scroll speed + Sort by URL + Backup the local and WebDav simultaneously + Restore from WebDAV first, Restore form the local backup on long click + Select a legacy backup folder + Enabled + Disabled + + Starting download + This book is already in Download list + click to open + Follow [开源阅读] to support me by clicking on ads + WeChat Tipping Code + AliPay + AliPay red envelope search code + 537954522 Click to copy + AliPay red envelope QR code + AliPay QR code + QQ Collection QR code + gedoor,Invinciblelee etc. Checking in github for details + Clear the cache of the downloaded books and fonts + Default cover + Bypass list + Ignore some contents while restoring + Reading interface settings + Group name + Remarks section + Only white page black text in E-Ink mode + Enable replace rule by default + For new added books + Select restore file + Switch default theme + Day background can not be too dark! + Day bottom can not be too dark! + Night background can not be too bright! + Night bottom can not be too bright! + Need Difference between accent and background color + Need Difference between accent and text color + Wrong format + Error + Show brightness widget + Language + Import rss source + Your donation makes this app better + Wechat official account [开源阅读软件] + Read record + Read record summary + Local TTS + Thread count + Total read time + Unselect all + Import str + Export str + Save theme config + Save day theme config + Save night theme config + Theme list + Save, Import, Share theme + Share selected sources + Sort by update time + Search + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4cc3907e0..b01529784 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -25,9 +25,9 @@ + + //**************************************************************Widget Style******************************************************************************// diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 2970ab12b..89bc507eb 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -4,8 +4,8 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> @@ -26,28 +26,28 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> @@ -56,15 +56,15 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index cdcf27e1b..779108dbf 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -6,4 +6,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_config_aloud.xml b/app/src/main/res/xml/pref_config_aloud.xml index c3d4f20cc..95d5193d0 100644 --- a/app/src/main/res/xml/pref_config_aloud.xml +++ b/app/src/main/res/xml/pref_config_aloud.xml @@ -4,6 +4,8 @@ - - - diff --git a/app/src/main/res/xml/pref_config_backup.xml b/app/src/main/res/xml/pref_config_backup.xml index 4f635e310..b16c6f32b 100644 --- a/app/src/main/res/xml/pref_config_backup.xml +++ b/app/src/main/res/xml/pref_config_backup.xml @@ -4,34 +4,34 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> @@ -39,28 +39,39 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 560692986..0e3930546 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -2,12 +2,19 @@ + + + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + @@ -35,29 +47,38 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + - + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + + + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + + + + + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + + + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> + + + app:iconSpaceReserved="false" + app:isBottomBackground="true" /> \ No newline at end of file diff --git a/app/src/main/res/xml/pref_config_theme.xml b/app/src/main/res/xml/pref_config_theme.xml index bf90dd7ab..673332a25 100644 --- a/app/src/main/res/xml/pref_config_theme.xml +++ b/app/src/main/res/xml/pref_config_theme.xml @@ -4,13 +4,13 @@ + android:title="@string/change_icon" + app:iconSpaceReserved="false" + app:icons="@array/icons" /> + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + @@ -85,44 +93,50 @@ + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml index 826a68b8d..e4e3d3cee 100644 --- a/app/src/main/res/xml/pref_main.xml +++ b/app/src/main/res/xml/pref_main.xml @@ -2,9 +2,8 @@ + app:allowDividerBelow="false"> - - + app:allowDividerBelow="false" + app:iconSpaceReserved="false"> + app:iconSpaceReserved="false" + app:layout="@layout/view_preference_category"> + + diff --git a/build.gradle b/build.gradle index 839736b35..8846d3f5a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,18 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.70' + ext.kotlin_version = '1.4.10' repositories { google() jcenter() + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://s3.amazonaws.com/fabric-artifacts/public' } + maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } maven { url 'https://maven.fabric.io/public' } maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:4.0.1' 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' @@ -21,6 +24,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } maven { url "https://jitpack.io" } maven { url 'https://maven.google.com/' } maven { url 'https://github.com/psiegman/mvn-repo/raw/master/releases' } @@ -29,4 +33,4 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20b544410..c1e56316f 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.3-all.zip