Эх сурвалжийг харах

Merge remote-tracking branch 'origin/master'

kunfei 4 жил өмнө
parent
commit
8be93d7504
33 өөрчлөгдсөн 605 нэмэгдсэн , 201 устгасан
  1. 15 9
      app/build.gradle
  2. 1 2
      app/src/main/AndroidManifest.xml
  3. 30 15
      app/src/main/assets/help/jsHelp.md
  4. 3 1
      app/src/main/assets/updateLog.md
  5. 0 0
      app/src/main/assets/web/bookshelf/css/detail.ac2331a7.css
  6. 2 2
      app/src/main/assets/web/bookshelf/index.html
  7. 0 0
      app/src/main/assets/web/bookshelf/js/about.74ccd735.js
  8. 0 0
      app/src/main/assets/web/bookshelf/js/app.7cd09a57.js
  9. 0 0
      app/src/main/assets/web/bookshelf/js/chunk-vendors.d7fcc132.js
  10. 0 0
      app/src/main/assets/web/bookshelf/js/detail.bb04bd6b.js
  11. 0 0
      app/src/main/assets/web/bookshelf/js/detail.d5b3eb30.js
  12. 3 2
      app/src/main/java/io/legado/app/constant/AppConst.kt
  13. 152 60
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  14. 1 1
      app/src/main/java/io/legado/app/lib/prefs/ColorPreference.kt
  15. 1 1
      app/src/main/java/io/legado/app/lib/prefs/SwitchPreference.kt
  16. 27 9
      app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt
  17. 11 4
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  18. 3 3
      app/src/main/java/io/legado/app/lib/theme/TintHelper.kt
  19. 23 1
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  20. 2 1
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt
  21. 3 2
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt
  22. 1 4
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt
  23. 1 1
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt
  24. 20 15
      app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt
  25. 1 2
      app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt
  26. 62 0
      app/src/main/java/io/legado/app/utils/DigestUtils.kt
  27. 185 9
      app/src/main/java/io/legado/app/utils/EncoderUtils.kt
  28. 1 23
      app/src/main/java/io/legado/app/utils/MD5Utils.kt
  29. 13 27
      build.gradle
  30. 7 3
      epublib/build.gradle
  31. 5 2
      gradle.properties
  32. 2 2
      gradle/wrapper/gradle-wrapper.properties
  33. 30 0
      settings.gradle

+ 15 - 9
app/build.gradle

@@ -1,9 +1,11 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-parcelize'
-apply plugin: 'kotlin-kapt'
-apply plugin: 'de.timfreiheit.resourceplaceholders'
-apply plugin: 'com.google.gms.google-services'
+plugins {
+    id "com.android.application"
+    id 'org.jetbrains.kotlin.android'
+    id 'kotlin-parcelize'
+    id 'kotlin-kapt'
+    id "de.timfreiheit.resourceplaceholders"
+    id "com.google.gms.google-services"
+}
 apply from: 'download.gradle'
 
 static def releaseTime() {
@@ -16,11 +18,12 @@ def gitCommits = Integer.parseInt('git rev-list HEAD --count'.execute().text.tri
 
 android {
     compileSdkVersion 32
-    buildToolsVersion '32.0.0'
+    buildToolsVersion "32.0.0"
+    namespace 'io.legado.app'
     kotlinOptions {
         jvmTarget = "11"
     }
-    namespace 'io.legado.app'
+
     signingConfigs {
         if (project.hasProperty("RELEASE_STORE_FILE")) {
             myConfig {
@@ -114,6 +117,9 @@ android {
         // Adds exported schema location as test app assets.
         androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
     }
+    lint {
+        checkDependencies true
+    }
     tasks.withType(JavaCompile) {
         //options.compilerArgs << "-Xlint:unchecked"
     }
@@ -249,7 +255,7 @@ dependencies {
     implementation('com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core:0.2.3')
 
     //加解密类库
-    implementation('cn.hutool:hutool-crypto:5.8.1')
+    //implementation('cn.hutool:hutool-crypto:5.8.1')
 
     //renderscriptToolkit
     implementation('com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3')

+ 1 - 2
app/src/main/AndroidManifest.xml

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="io.legado.app">
+    xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

+ 30 - 15
app/src/main/assets/help/jsHelp.md

@@ -54,7 +54,11 @@ java.put(key, value)
 java.ajax(urlStr): String
 java.ajaxAll(urlList: Array<String>): Array<StrResponse?>
 //返回Response 方法body() code() message() header() raw() toString() 
-java.connect(urlStr): Response<String>
+java.connect(urlStr): StrResponse
+
+java.post(url: String, body: String, headerMap: Map<String, String>): Connection.Response
+java.get(url: String, headerMap: Map<String, String>): Connection.Response
+java.head(url: String, headerMap: Map<String, String>): Connection.Response
 
 * 使用webView访问网络
 * @param html 直接用webView载入的html, 如果html为空直接访问url
@@ -128,13 +132,11 @@ deleteFile(path: String)
 ```
 ****
 > [常见加密解密算法介绍](https://www.yijiyong.com/algorithm/encryption/01-intro.html)
-> [相关概念](https://blog.csdn.net/OrangeJack/article/details/82913804)  
+> [相关概念](https://blog.csdn.net/OrangeJack/article/details/82913804)
+> [Android支持的transformation](https://developer.android.google.cn/reference/kotlin/javax/crypto/Cipher?hl=en)
 * AES
+> transformation默认实现AES/ECB/PKCS5Padding
 ```
-* @param data 传入的原始数据
-* @param key AES加密的key
-* @param transformation AES加密的方式 例如AES/ECB/PKCS5Padding
-* @param iv ECB模式的偏移向量
 java.aesDecodeToString(str: String, key: String, transformation: String, iv: String)
 
 java.aesBase64DecodeToString(str: String, key: String, transformation: String, iv: String)
@@ -143,24 +145,37 @@ java.aesEncodeToString(str: String, key: String, transformation: String, iv: Str
 
 java.aesEncodeToBase64String(str: String, key: String, transformation: String, iv: String)
 ```
+* DES
+> transformation默认实现DES/ECB/PKCS5Padding
+```
+java.desDecodeToString(str: String, key: String, transformation: String, iv: String)
+
+java.desBase64DecodeToString(str: String, key: String, transformation: String, iv: String)
+
+java.desEncodeToString(str: String, key: String, transformation: String, iv: String)
+
+java.desEncodeToBase64String(str: String, key: String, transformation: String, iv: String)
+```
 * 3DES
+> tansformation默认实现DESede/ECB/PKCS5Padding
 ```
-* @param data 被加密的字符串
-* @param key 密钥
-* @param mode 模式 ECB/CBC/CFB/OFB/CTR
-* @param padding 补码方式 NoPadding/PKCS5Padding/
-* @param iv 加盐
 java.tripleDESEncodeBase64Str(data: String,key: String,mode: String,padding: String,iv: String): String?
 
 java.tripleDESDecodeStr(data: String,key: String,mode: String,padding: String,iv: String): String?
 ```
 * 摘要
+> algorithm支持MD5 SHA-1 SHA-224 SHA-256 SHA-384 SHA-512
+```
+java.digestHex(data: String, algorithm: String,): String?
+
+java.digestBase64Str(data: String, algorithm: String,): String?
+```
+* HMac(散列消息鉴别码)
+> algorithm支持DESMAC DESMAC/CFB8 DESedeMAC DESedeMAC/CFB8 DESedeMAC64 DESwithISO9797 HmacMD5 HmacSHA* ISO9797ALG3MAC PBEwithSHA*
 ```
-* @param data 被摘要数据
-* @param algorithm 签名算法 MD5/SHA1/SHA256/SHA512
-java.digestHex(data: String,algorithm: String,): String?
+java.HMacHex(data: String, algorithm: String, key: String): String
 
-java.digestBase64Str(data: String,algorithm: String,): String?
+java.HMacBase64(data: String, algorithm: String, key: String): String
 ```
 * md5
 ```

+ 3 - 1
app/src/main/assets/updateLog.md

@@ -15,6 +15,8 @@
 
 * 修复部分txt章节结尾乱码bug
 * 优化翻页流畅度
+* js添加des HMac
+* 登录ui用户信息默认加密方式改变,需要重新登录
 
 **2022/05/21**
 
@@ -322,4 +324,4 @@ eval(String(java.cacheFile(url)))
 **2022/01/01**
 
 * 修复本地txt问题,不在拷贝到私有目录,可以正常打开
-* 优化txt目录识别,取目录数量最多的规则
+* 优化txt目录识别,取目录数量最多的规则

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
app/src/main/assets/web/bookshelf/css/detail.ac2331a7.css


+ 2 - 2
app/src/main/assets/web/bookshelf/index.html

@@ -1,3 +1,3 @@
-<!DOCTYPE html><html lang="zh-CN" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="../favicon.ico" type="image/x-icon"><link rel="shortcut icon" href="../favicon.ico" type="image/x-icon"><title>Legado Bookshelf</title><link href="css/about.65a00131.css" rel="prefetch"><link href="css/detail.12a39aa7.css" rel="prefetch"><link href="js/about.cee6f6d7.js" rel="prefetch"><link href="js/detail.bb04bd6b.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.bd1373b6.css" rel="preload" as="style"><link href="js/app.aa604b87.js" rel="preload" as="script"><link href="js/chunk-vendors.ca94fdd0.js" rel="preload" as="script"><link href="css/chunk-vendors.bd1373b6.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"></head><style>body::-webkit-scrollbar {
+<!DOCTYPE html><html lang="zh-CN" style="padding: 0;height:100%"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="../favicon.ico" type="image/x-icon"><link rel="shortcut icon" href="../favicon.ico" type="image/x-icon"><title>Legado Bookshelf</title><link href="css/about.65a00131.css" rel="prefetch"><link href="css/detail.ac2331a7.css" rel="prefetch"><link href="js/about.74ccd735.js" rel="prefetch"><link href="js/detail.d5b3eb30.js" rel="prefetch"><link href="css/app.e4c919b7.css" rel="preload" as="style"><link href="css/chunk-vendors.bd1373b6.css" rel="preload" as="style"><link href="js/app.7cd09a57.js" rel="preload" as="script"><link href="js/chunk-vendors.d7fcc132.js" rel="preload" as="script"><link href="css/chunk-vendors.bd1373b6.css" rel="stylesheet"><link href="css/app.e4c919b7.css" rel="stylesheet"></head><style>body::-webkit-scrollbar {
         display: none;
-    }</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.ca94fdd0.js"></script><script src="js/app.aa604b87.js"></script></body></html>
+    }</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.d7fcc132.js"></script><script src="js/app.7cd09a57.js"></script></body></html>

+ 0 - 0
app/src/main/assets/web/bookshelf/js/about.cee6f6d7.js → app/src/main/assets/web/bookshelf/js/about.74ccd735.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
app/src/main/assets/web/bookshelf/js/app.7cd09a57.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
app/src/main/assets/web/bookshelf/js/chunk-vendors.d7fcc132.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
app/src/main/assets/web/bookshelf/js/detail.bb04bd6b.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
app/src/main/assets/web/bookshelf/js/detail.d5b3eb30.js


+ 3 - 2
app/src/main/java/io/legado/app/constant/AppConst.kt

@@ -5,7 +5,6 @@ import android.content.pm.PackageManager
 import android.provider.Settings
 import com.script.javascript.RhinoScriptEngine
 import io.legado.app.BuildConfig
-import io.legado.app.R
 import splitties.init.appCtx
 import java.text.SimpleDateFormat
 
@@ -57,7 +56,9 @@ object AppConst {
     )
 
     @SuppressLint("PrivateResource")
-    val sysElevation = appCtx.resources.getDimension(R.dimen.design_appbar_elevation).toInt()
+    val sysElevation =
+        appCtx.resources.getDimension(com.google.android.material.R.dimen.design_appbar_elevation)
+            .toInt()
 
     val androidId: String by lazy {
         Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) ?: "null"

+ 152 - 60
app/src/main/java/io/legado/app/help/JsExtensions.kt

@@ -3,9 +3,6 @@ package io.legado.app.help
 import android.net.Uri
 import android.util.Base64
 import androidx.annotation.Keep
-import cn.hutool.crypto.digest.DigestUtil
-import cn.hutool.crypto.symmetric.AES
-import cn.hutool.crypto.symmetric.DESede
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst.dateFormat
 import io.legado.app.constant.AppLog
@@ -299,14 +296,6 @@ interface JsExtensions {
         return EncoderUtils.base64Encode(str, flags)
     }
 
-    fun md5Encode(str: String): String {
-        return MD5Utils.md5Encode(str)
-    }
-
-    fun md5Encode16(str: String): String {
-        return MD5Utils.md5Encode16(str)
-    }
-
     /**
      * 格式化时间
      */
@@ -602,6 +591,13 @@ interface JsExtensions {
         return UUID.randomUUID().toString()
     }
 
+    fun androidId(): String {
+        return AppConst.androidId
+    }
+
+//******************对称加密解密************************//
+
+    /////AES
     /**
      * AES 解码为 ByteArray
      * @param str 传入的AES加密的数据
@@ -640,6 +636,31 @@ interface JsExtensions {
         return aesDecodeToByteArray(str, key, transformation, iv)?.let { String(it) }
     }
 
+    /**
+     * AES解码为String,算法参数经过Base64加密
+     *
+     * @param data 加密的字符串
+     * @param key Base64后的密钥
+     * @param mode 模式
+     * @param padding 补码方式
+     * @param iv Base64后的加盐
+     * @return 解密后的字符串
+     */
+    fun aesDecodeArgsBase64Str(
+        data: String,
+        key: String,
+        mode: String,
+        padding: String,
+        iv: String
+    ): String? {
+        return EncoderUtils.decryptAES(
+            data.encodeToByteArray(),
+            Base64.decode(key, Base64.NO_WRAP),
+            "AES/${mode}/${padding}",
+            Base64.decode(iv, Base64.NO_WRAP)
+        )?.let { String(it) }
+    }
+
     /**
      * 已经base64的AES 解码为 ByteArray
      * @param str 传入的AES Base64加密的数据
@@ -753,35 +774,78 @@ interface JsExtensions {
         return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) }
     }
 
-    fun androidId(): String {
-        return AppConst.androidId
-    }
 
     /**
-     * AES解密,算法参数经过Base64加密
+     * AES加密并转为Base64,算法参数经过Base64加密
      *
-     * @param data 加密的字符串
+     * @param data 被加密的字符串
      * @param key Base64后的密钥
      * @param mode 模式
      * @param padding 补码方式
      * @param iv Base64后的加盐
-     * @return 解密后的字符串
+     * @return 加密后的Base64
      */
-    fun aesDecodeArgsBase64Str(
+    fun aesEncodeArgsBase64Str(
         data: String,
         key: String,
         mode: String,
         padding: String,
         iv: String
     ): String? {
-        return AES(
-            mode,
-            padding,
+        return EncoderUtils.encryptAES2Base64(
+            data.encodeToByteArray(),
             Base64.decode(key, Base64.NO_WRAP),
+            "AES/${mode}/${padding}",
             Base64.decode(iv, Base64.NO_WRAP)
-        ).decryptStr(data)
+        )?.let { String(it) }
+    }
+
+    /////DES
+    fun desDecodeToString(
+        data: String, key: String, transformation: String, iv: String
+    ): String? {
+        return EncoderUtils.decryptDES(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            transformation,
+            iv.encodeToByteArray()
+        )?.let { String(it) }
+    }
+
+    fun desBase64DecodeToString(
+       data: String, key: String, transformation: String, iv: String
+    ): String? {
+        return EncoderUtils.decryptBase64DES(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            transformation,
+            iv.encodeToByteArray()
+        )?.let { String(it) }
+    }
+
+    fun desEncodeToString(
+        data: String, key: String, transformation: String, iv: String
+    ): String? {
+        return EncoderUtils.encryptDES(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            transformation,
+            iv.encodeToByteArray()
+        )?.let { String(it) }
+    }
+
+    fun desEncodeToBase64String(
+        data: String, key: String, transformation: String, iv: String
+    ): String? {
+        return EncoderUtils.encryptDES2Base64(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            transformation,
+            iv.encodeToByteArray()
+        )?.let { String(it) }
     }
 
+    //////3DES
     /**
      * 3DES解密
      *
@@ -799,7 +863,12 @@ interface JsExtensions {
         padding: String,
         iv: String
     ): String? {
-        return DESede(mode, padding, key.toByteArray(), iv.toByteArray()).decryptStr(data)
+        return EncoderUtils.decryptDESede(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            "DESede/${mode}/${padding}",
+            iv.encodeToByteArray()
+        )?.let { String(it) }
     }
 
     /**
@@ -819,38 +888,14 @@ interface JsExtensions {
         padding: String,
         iv: String
     ): String? {
-        return DESede(
-            mode,
-            padding,
+        return EncoderUtils.decryptDESede(
+            data.encodeToByteArray(),
             Base64.decode(key, Base64.NO_WRAP),
+            "DESede/${mode}/${padding}",
             Base64.decode(iv, Base64.NO_WRAP)
-        ).decryptStr(data)
+        )?.let { String(it) }
     }
 
-    /**
-     * AES加密并转为Base64,算法参数经过Base64加密
-     *
-     * @param data 被加密的字符串
-     * @param key Base64后的密钥
-     * @param mode 模式
-     * @param padding 补码方式
-     * @param iv Base64后的加盐
-     * @return 加密后的Base64
-     */
-    fun aesEncodeArgsBase64Str(
-        data: String,
-        key: String,
-        mode: String,
-        padding: String,
-        iv: String
-    ): String? {
-        return AES(
-            mode,
-            padding,
-            Base64.decode(key, Base64.NO_WRAP),
-            Base64.decode(iv, Base64.NO_WRAP)
-        ).encryptBase64(data)
-    }
 
     /**
      * 3DES加密并转为Base64
@@ -869,7 +914,12 @@ interface JsExtensions {
         padding: String,
         iv: String
     ): String? {
-        return DESede(mode, padding, key.toByteArray(), iv.toByteArray()).encryptBase64(data)
+        return EncoderUtils.encryptDESede2Base64(
+            data.encodeToByteArray(),
+            key.encodeToByteArray(),
+            "DESede/${mode}/${padding}",
+            iv.encodeToByteArray()
+        )?.let { String(it) }
     }
 
     /**
@@ -889,14 +939,16 @@ interface JsExtensions {
         padding: String,
         iv: String
     ): String? {
-        return DESede(
-            mode,
-            padding,
+        return EncoderUtils.encryptDESede2Base64(
+            data.encodeToByteArray(),
             Base64.decode(key, Base64.NO_WRAP),
+            "DESede/${mode}/${padding}",
             Base64.decode(iv, Base64.NO_WRAP)
-        ).encryptBase64(data)
+        )?.let { String(it) }
     }
 
+//******************消息摘要/散列消息鉴别码************************//
+
     /**
      * 生成摘要,并转为16进制字符串
      *
@@ -907,8 +959,8 @@ interface JsExtensions {
     fun digestHex(
         data: String,
         algorithm: String,
-    ): String? {
-        return DigestUtil.digester(algorithm).digestHex(data)
+    ): String {
+        return DigestUtils.getDigest(algorithm, data)
     }
 
     /**
@@ -921,8 +973,48 @@ interface JsExtensions {
     fun digestBase64Str(
         data: String,
         algorithm: String,
-    ): String? {
-        return Base64.encodeToString(DigestUtil.digester(algorithm).digest(data), Base64.NO_WRAP)
+    ): String {
+        return Base64.encodeToString(DigestUtils.getDigest(algorithm, data.toByteArray()), Base64.NO_WRAP)
+    }
+
+    /**
+     * 生成散列消息鉴别码,并转为16进制字符串
+     *
+     * @param data 被摘要数据
+     * @param algorithm 签名算法
+     * @param key 密钥
+     * @return 16进制字符串
+     */
+    fun HMacHex(
+        data: String,
+        algorithm: String,
+        key: String
+    ): String {
+        return DigestUtils.getHMac(algorithm, key, data)
+    }
+
+    /**
+     * 生成散列消息鉴别码,并转为Base64字符串
+     *
+     * @param data 被摘要数据
+     * @param algorithm 签名算法
+     * @param key 密钥
+     * @return Base64字符串
+     */
+    fun HMacBase64(
+        data: String,
+        algorithm: String,
+        key: String
+    ): String {
+        return Base64.encodeToString(DigestUtils.getHMac(algorithm, key.toByteArray(), data.toByteArray()), Base64.NO_WRAP)
+    }
+
+    fun md5Encode(str: String): String {
+        return MD5Utils.md5Encode(str)
+    }
+
+    fun md5Encode16(str: String): String {
+        return MD5Utils.md5Encode16(str)
     }
 
 }

+ 1 - 1
app/src/main/java/io/legado/app/lib/prefs/ColorPreference.kt

@@ -120,7 +120,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex
     override fun onBindViewHolder(holder: PreferenceViewHolder) {
         val v = io.legado.app.lib.prefs.Preference.bindView<ColorPanelView>(
             context, holder, icon, title, summary, widgetLayoutResource,
-            io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30
+            com.jaredrummler.android.colorpicker.R.id.cpv_preference_preview_color_panel, 30, 30
         )
         if (v is ColorPanelView) {
             v.color = mColor

+ 1 - 1
app/src/main/java/io/legado/app/lib/prefs/SwitchPreference.kt

@@ -30,7 +30,7 @@ class SwitchPreference(context: Context, attrs: AttributeSet) :
             title,
             summary,
             widgetLayoutResource,
-            R.id.switchWidget,
+            androidx.preference.R.id.switchWidget,
             isBottomBackground = isBottomBackground
         )
         if (v is SwitchCompat && !v.isInEditMode) {

+ 27 - 9
app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt

@@ -19,34 +19,52 @@ import io.legado.app.utils.dpToPx
 @ColorInt
 fun Context.getPrimaryTextColor(dark: Boolean): Int {
     return if (dark) {
-        ContextCompat.getColor(this, R.color.primary_text_default_material_light)
-    } else ContextCompat.getColor(this, R.color.primary_text_default_material_dark)
+        ContextCompat.getColor(this, androidx.appcompat.R.color.primary_text_default_material_light)
+    } else ContextCompat.getColor(
+        this,
+        androidx.appcompat.R.color.primary_text_default_material_dark
+    )
 }
 
 @ColorInt
 fun Context.getSecondaryTextColor(dark: Boolean): Int {
     return if (dark) {
-        ContextCompat.getColor(this, R.color.secondary_text_default_material_light)
+        ContextCompat.getColor(
+            this,
+            androidx.appcompat.R.color.secondary_text_default_material_light
+        )
     } else {
-        ContextCompat.getColor(this, R.color.secondary_text_default_material_dark)
+        ContextCompat.getColor(
+            this,
+            androidx.appcompat.R.color.secondary_text_default_material_dark
+        )
     }
 }
 
 @ColorInt
 fun Context.getPrimaryDisabledTextColor(dark: Boolean): Int {
     return if (dark) {
-        ContextCompat.getColor(this, R.color.primary_text_disabled_material_light)
+        ContextCompat.getColor(
+            this,
+            androidx.appcompat.R.color.primary_text_disabled_material_light
+        )
     } else {
-        ContextCompat.getColor(this, R.color.primary_text_disabled_material_dark)
+        ContextCompat.getColor(this, androidx.appcompat.R.color.primary_text_disabled_material_dark)
     }
 }
 
 @ColorInt
 fun Context.getSecondaryDisabledTextColor(dark: Boolean): Int {
     return if (dark) {
-        ContextCompat.getColor(this, R.color.secondary_text_disabled_material_light)
+        ContextCompat.getColor(
+            this,
+            androidx.appcompat.R.color.secondary_text_disabled_material_light
+        )
     } else {
-        ContextCompat.getColor(this, R.color.secondary_text_disabled_material_dark)
+        ContextCompat.getColor(
+            this,
+            androidx.appcompat.R.color.secondary_text_disabled_material_dark
+        )
     }
 }
 
@@ -124,7 +142,7 @@ val Context.elevation: Float
             ThemeUtils.resolveFloat(
                 this,
                 android.R.attr.elevation,
-                resources.getDimension(R.dimen.design_appbar_elevation)
+                resources.getDimension(com.google.android.material.R.dimen.design_appbar_elevation)
             )
         } else {
             AppConfig.elevation.toFloat().dpToPx()

+ 11 - 4
app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt

@@ -9,7 +9,6 @@ import androidx.annotation.CheckResult
 import androidx.annotation.ColorInt
 import androidx.annotation.ColorRes
 import androidx.core.content.ContextCompat
-import io.legado.app.R
 import io.legado.app.utils.ColorUtils
 import splitties.init.appCtx
 
@@ -189,7 +188,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
         fun primaryColor(context: Context = appCtx): Int {
             return prefs(context).getInt(
                 ThemeStorePrefKeys.KEY_PRIMARY_COLOR,
-                ThemeUtils.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64"))
+                ThemeUtils.resolveColor(
+                    context,
+                    androidx.appcompat.R.attr.colorPrimary,
+                    Color.parseColor("#455A64")
+                )
             )
         }
 
@@ -200,7 +203,7 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
                 ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK,
                 ThemeUtils.resolveColor(
                     context,
-                    R.attr.colorPrimaryDark,
+                    androidx.appcompat.R.attr.colorPrimaryDark,
                     Color.parseColor("#37474F")
                 )
             )
@@ -211,7 +214,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
         fun accentColor(context: Context = appCtx): Int {
             return prefs(context).getInt(
                 ThemeStorePrefKeys.KEY_ACCENT_COLOR,
-                ThemeUtils.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238"))
+                ThemeUtils.resolveColor(
+                    context,
+                    androidx.appcompat.R.attr.colorAccent,
+                    Color.parseColor("#263238")
+                )
             )
         }
 

+ 3 - 3
app/src/main/java/io/legado/app/lib/theme/TintHelper.kt

@@ -31,9 +31,9 @@ object TintHelper {
         // Light ripple is actually translucent black, and vice versa
         return ContextCompat.getColor(
             context, if (useDarkRipple)
-                R.color.ripple_material_light
+                androidx.appcompat.R.color.ripple_material_light
             else
-                R.color.ripple_material_dark
+                androidx.appcompat.R.color.ripple_material_dark
         )
     }
 
@@ -170,7 +170,7 @@ object TintHelper {
                 val rd = view.background as RippleDrawable
                 @SuppressLint("PrivateResource") val unchecked = ContextCompat.getColor(
                     view.context,
-                    if (isDark) R.color.ripple_material_dark else R.color.ripple_material_light
+                    if (isDark) androidx.appcompat.R.color.ripple_material_dark else androidx.appcompat.R.color.ripple_material_light
                 )
                 val checked = ColorUtils.adjustAlpha(color, 0.4f)
                 val sl = ColorStateList(

+ 23 - 1
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -219,6 +219,8 @@ object LocalBook {
         fileName: String,
         source: BaseSource? = null,
     ): Uri {
+        AppConfig.defaultBookTreeUri
+            ?: throw NoStackTraceException("没有设置书籍保存位置!")
         val bytes = when {
             str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray()
             str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT)
@@ -241,7 +243,7 @@ object LocalBook {
         return type ?: fileType
     }
 
-    private fun saveBookFile(
+    fun saveBookFile(
         bytes: ByteArray,
         fileName: String
     ): Uri {
@@ -269,6 +271,26 @@ object LocalBook {
         }
     }
 
+    fun isOnBookShelf(
+        fileName: String
+    ): Boolean {
+        val defaultBookTreeUri = AppConfig.defaultBookTreeUri
+        if (defaultBookTreeUri.isNullOrBlank()) throw NoStackTraceException("没有设置书籍保存位置!")
+        val treeUri = Uri.parse(defaultBookTreeUri)
+        var bookUrl: String = ""
+        if (treeUri.isContentScheme()) {
+            val treeDoc = DocumentFile.fromTreeUri(appCtx, treeUri)
+            var doc = treeDoc!!.findFile(fileName) ?: return false
+            bookUrl = doc.uri.toString()
+        } else {
+            val treeFile = File(treeUri.path!!)
+            val file = treeFile.getFile(fileName)
+            if (!file.exists()) return false
+            bookUrl = file.absolutePath
+        }
+        return appDb.bookDao.getBook(bookUrl) != null
+    }
+
     //文件类书源 合并在线书籍信息 在线 > 本地
     fun mergeBook(localBook: Book, onLineBook: Book?): Book {
         onLineBook ?: return localBook

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt

@@ -5,5 +5,6 @@ data class RemoteBook(
     val urlName: String,
     val size: Long,
     val contentType: String,
-    val lastModify: Long
+    val lastModify: Long,
+    val isOnBookShelf: Boolean
 )

+ 3 - 2
app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt

@@ -2,11 +2,12 @@ package io.legado.app.ui.book.remote
 
 import android.content.Context
 import android.view.ViewGroup
-import cn.hutool.core.date.LocalDateTimeUtil
 import io.legado.app.base.adapter.ItemViewHolder
 import io.legado.app.base.adapter.RecyclerAdapter
 import io.legado.app.databinding.ItemRemoteBookBinding
 import io.legado.app.utils.ConvertUtils
+import java.text.SimpleDateFormat
+import java.util.Date
 
 
 /**
@@ -38,7 +39,7 @@ class RemoteBookAdapter (context: Context, val callBack: CallBack) :
             tvName.text = item.filename.substringBeforeLast(".")
             tvContentType.text = item.contentType
             tvSize.text = ConvertUtils.formatFileSize(item.size)
-            tvDate.text = LocalDateTimeUtil.format(LocalDateTimeUtil.of(item.lastModify), "yyyy-MM-dd")
+            tvDate.text = SimpleDateFormat("yyyy-MM-dd").format(Date(item.lastModify))
         }
     }
 

+ 1 - 4
app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt

@@ -2,11 +2,8 @@ package io.legado.app.ui.book.remote
 
 import android.net.Uri
 
-
-
 abstract class RemoteBookManager {
     protected val remoteBookFolder : String = "books"
-    protected val contentTypeList: ArrayList<String> = arrayListOf("epub","txt")
     abstract suspend fun initRemoteContext()
     abstract suspend fun getRemoteBookList(): MutableList<RemoteBook>
     abstract suspend fun upload(localBookUri: Uri): Boolean
@@ -15,5 +12,5 @@ abstract class RemoteBookManager {
     /**
      * @return String:下载到本地的路径
      */
-    abstract suspend fun getRemoteBook(remoteBook: RemoteBook): String?
+    abstract suspend fun getRemoteBook(remoteBook: RemoteBook): Uri?
 }

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt

@@ -73,7 +73,7 @@ class RemoteBookViewModel(application: Application): BaseViewModel(application){
         execute {
             val downloadBookPath = RemoteBookWebDav.getRemoteBook(remoteBook)
             downloadBookPath?.let {
-                LocalBook.importFile(Uri.parse(it))
+                LocalBook.importFile(it)
             }
         }.onFinally {
             finally.invoke()

+ 20 - 15
app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt

@@ -3,11 +3,13 @@ package io.legado.app.ui.book.remote.manager
 
 import android.net.Uri
 import io.legado.app.constant.PreferKey
+import io.legado.app.constant.AppPattern.bookFileRegex
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.AppWebDav
 import io.legado.app.help.config.AppConfig
 import io.legado.app.lib.webdav.WebDav
 import io.legado.app.lib.webdav.WebDavFile
+import io.legado.app.model.localBook.LocalBook
 import io.legado.app.ui.book.remote.RemoteBook
 import io.legado.app.ui.book.remote.RemoteBookManager
 import io.legado.app.utils.*
@@ -17,7 +19,6 @@ import java.io.File
 
 object RemoteBookWebDav : RemoteBookManager() {
     private val remoteBookUrl get() = "${rootWebDavUrl}${remoteBookFolder}"
-    private val localSaveFolder get() = "${appCtx.externalFiles.absolutePath}${File.separator}${remoteBookFolder}"
 
     init {
         runBlocking {
@@ -68,31 +69,35 @@ object RemoteBookWebDav : RemoteBookManager() {
                 val fileExtension = webDavFileName.substringAfterLast(".")
 
                 //扩展名符合阅读的格式则认为是书籍
-                if (contentTypeList.contains(fileExtension)) {
-                    remoteBooks.add(RemoteBook(webDavFileName,webDavUrlName,webDavFile.size,fileExtension,webDavFile.lastModify))
+                if (bookFileRegex.matches(webDavFileName)) {
+                    val isOnBookShelf = LocalBook.isOnBookShelf(webDavFileName)
+                    remoteBooks.add(
+                        RemoteBook(
+                            webDavFileName, webDavUrlName, webDavFile.size,
+                            fileExtension, webDavFile.lastModify, isOnBookShelf
+                        )
+                    )
                 }
             }
         } ?: throw NoStackTraceException("webDav没有配置")
         return remoteBooks
     }
 
-    override suspend fun getRemoteBook(remoteBook: RemoteBook): String? {
-        val saveFilePath= "${localSaveFolder}${File.separator}${remoteBook.filename}"
-        kotlin.runCatching {
+    override suspend fun getRemoteBook(remoteBook: RemoteBook): Uri? {
+        return kotlin.runCatching {
             AppWebDav.authorization?.let {
-                FileUtils.createFolderIfNotExist(localSaveFolder).run {
-                    val webdav = WebDav(
-                        remoteBook.urlName,
-                        it
-                    )
-                    webdav.downloadTo(saveFilePath, true)
+                val webdav = WebDav(
+                    remoteBook.urlName,
+                    it
+                )
+                webdav.download().let { bytes ->
+                    LocalBook.saveBookFile(bytes, remoteBook.filename)
                 }
             }
         }.onFailure {
             it.printStackTrace()
-            return null
-        }
-        return saveFilePath
+            null
+        }.getOrNull()
     }
 
     /**

+ 1 - 2
app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt

@@ -17,7 +17,6 @@ import android.widget.ImageView
 import android.widget.OverScroller
 import android.widget.Scroller
 import androidx.appcompat.widget.AppCompatImageView
-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
@@ -917,7 +916,7 @@ class PhotoView @JvmOverloads constructor(
         var viewParent: ViewParent = target.parent
         while (viewParent is View) {
             val view: View = viewParent
-            if (view.id == R.id.content) return
+            if (view.id == androidx.constraintlayout.widget.R.id.content) return
             position[0] -= view.scrollX
             position[1] -= view.scrollY
             position[0] += view.left

+ 62 - 0
app/src/main/java/io/legado/app/utils/DigestUtils.kt

@@ -0,0 +1,62 @@
+package io.legado.app.utils
+
+import java.security.MessageDigest
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+object DigestUtils {
+
+    /**
+     * 消息摘要
+     * 支持MD5 SHA-1 SHA-224 SHA-256 SHA-384 SHA-512
+     * https://developer.android.google.cn/reference/java/security/MessageDigest?hl=en
+     */
+    fun getDigest(
+        algorithm: String,
+        data: String?
+    ): String {
+        data ?: return ""
+        val bytes = getDigest(algorithm, data.toByteArray())
+        return StringUtils.byteToHexString(bytes)
+    }
+
+    fun getDigest(
+        algorithm: String,
+        data: ByteArray
+    ): ByteArray {
+        return kotlin.runCatching {
+            val messageDigest = MessageDigest.getInstance(algorithm)
+            messageDigest.digest(data)
+        }.getOrThrow()
+    }
+
+    /**
+     * 散列消息鉴别码
+     * 支持DESMAC DESMAC/CFB8 DESedeMAC DESedeMAC/CFB8 DESedeMAC64 DESwithISO9797 HmacMD5 HmacSHA* ISO9797ALG3MAC PBEwithSHA*
+     * https://developer.android.google.cn/reference/kotlin/javax/crypto/Mac?hl=en
+     */
+    fun getHMac(
+        algorithm: String,
+        key: String,
+        data: String?
+    ): String {
+        data ?: return ""
+        val bytes = getHMac(algorithm, key.toByteArray(), data.toByteArray())
+        return StringUtils.byteToHexString(bytes)
+    }
+
+    fun getHMac(
+        algorithm: String,
+        key: ByteArray,
+        data: ByteArray
+    ): ByteArray {
+        return kotlin.runCatching {
+            val mac= Mac.getInstance(algorithm)
+            val keySpec = SecretKeySpec(key, algorithm)
+            mac.init(keySpec)
+            mac.update(data)
+            mac.doFinal()
+        }.getOrThrow()
+    }
+
+}

+ 185 - 9
app/src/main/java/io/legado/app/utils/EncoderUtils.kt

@@ -6,6 +6,9 @@ import javax.crypto.Cipher
 import javax.crypto.spec.IvParameterSpec
 import javax.crypto.spec.SecretKeySpec
 
+/**
+ * transformations https://developer.android.google.cn/reference/kotlin/javax/crypto/Cipher?hl=en
+ */
 @Suppress("unused")
 object EncoderUtils {
 
@@ -47,7 +50,7 @@ object EncoderUtils {
      * @param data           The data.
      * @param key            The key.
      * @param transformation The name of the transformation,
-     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * 加密算法/加密模式/填充类型, *AES/CBC/PKCS5Padding*.
      * @param iv             The buffer with the IV. The contents of the
      * buffer are copied to protect against subsequent modification.
      * @return the Base64-encode bytes of AES encryption
@@ -56,7 +59,7 @@ object EncoderUtils {
     fun encryptAES2Base64(
         data: ByteArray?,
         key: ByteArray?,
-        transformation: String? = "DES/ECB/PKCS5Padding",
+        transformation: String? = "AES/ECB/PKCS5Padding",
         iv: ByteArray? = null
     ): ByteArray? {
         return Base64.encode(encryptAES(data, key, transformation, iv), Base64.NO_WRAP)
@@ -68,7 +71,7 @@ object EncoderUtils {
      * @param data           The data.
      * @param key            The key.
      * @param transformation The name of the transformation,
-     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * 加密算法/加密模式/填充类型, *AES/CBC/PKCS5Padding*.
      * @param iv             The buffer with the IV. The contents of the
      * buffer are copied to protect against subsequent modification.
      * @return the bytes of AES encryption
@@ -77,7 +80,7 @@ object EncoderUtils {
     fun encryptAES(
         data: ByteArray?,
         key: ByteArray?,
-        transformation: String? = "DES/ECB/PKCS5Padding",
+        transformation: String? = "AES/ECB/PKCS5Padding",
         iv: ByteArray? = null
     ): ByteArray? {
         return symmetricTemplate(data, key, "AES", transformation!!, iv, true)
@@ -90,7 +93,7 @@ object EncoderUtils {
      * @param data           The data.
      * @param key            The key.
      * @param transformation The name of the transformation,
-     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * 加密算法/加密模式/填充类型, *AES/CBC/PKCS5Padding*.
      * @param iv             The buffer with the IV. The contents of the
      * buffer are copied to protect against subsequent modification.
      * @return the bytes of AES decryption for Base64-encode bytes
@@ -99,7 +102,7 @@ object EncoderUtils {
     fun decryptBase64AES(
         data: ByteArray?,
         key: ByteArray?,
-        transformation: String = "DES/ECB/PKCS5Padding",
+        transformation: String = "AES/ECB/PKCS5Padding",
         iv: ByteArray? = null
     ): ByteArray? {
         return decryptAES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv)
@@ -111,7 +114,7 @@ object EncoderUtils {
      * @param data           The data.
      * @param key            The key.
      * @param transformation The name of the transformation,
-     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * 加密算法/加密模式/填充类型, *AES/CBC/PKCS5Padding*.
      * @param iv             The buffer with the IV. The contents of the
      * buffer are copied to protect against subsequent modification.
      * @return the bytes of AES decryption
@@ -120,12 +123,185 @@ object EncoderUtils {
     fun decryptAES(
         data: ByteArray?,
         key: ByteArray?,
-        transformation: String = "DES/ECB/PKCS5Padding",
+        transformation: String = "AES/ECB/PKCS5Padding",
         iv: ByteArray? = null
     ): ByteArray? {
         return symmetricTemplate(data, key, "AES", transformation, iv, false)
     }
 
+    //////////DES Start
+
+    /**
+     * Return the Base64-encode bytes of DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the Base64-encode bytes of AES encryption
+     */
+    @Throws(Exception::class)
+    fun encryptDES2Base64(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String? = "DES/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return Base64.encode(encryptDES(data, key, transformation, iv), Base64.NO_WRAP)
+    }
+
+    /**
+     * Return the bytes of DES encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES encryption
+     */
+    @Throws(Exception::class)
+    fun encryptDES(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String? = "DES/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return symmetricTemplate(data, key, "DES", transformation!!, iv, true)
+    }
+
+
+    /**
+     * Return the bytes of DES decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption for Base64-encode bytes
+     */
+    @Throws(Exception::class)
+    fun decryptBase64DES(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String = "DES/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return decryptDES(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv)
+    }
+
+    /**
+     * Return the bytes of DES decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DES/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption
+     */
+    @Throws(Exception::class)
+    fun decryptDES(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String = "DES/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return symmetricTemplate(data, key, "DES", transformation, iv, false)
+    }
+
+    //////////DESede Start
+
+    /**
+     * Return the Base64-encode bytes of DESede encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DESede/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the Base64-encode bytes of AES encryption
+     */
+    @Throws(Exception::class)
+    fun encryptDESede2Base64(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String? = "DESede/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return Base64.encode(encryptDESede(data, key, transformation, iv), Base64.NO_WRAP)
+    }
+
+    /**
+     * Return the bytes of DESede encryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DESede/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES encryption
+     */
+    @Throws(Exception::class)
+    fun encryptDESede(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String? = "DESede/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return symmetricTemplate(data, key, "DESede", transformation!!, iv, true)
+    }
+
+
+    /**
+     * Return the bytes of DESede decryption for Base64-encode bytes.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DESede/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption for Base64-encode bytes
+     */
+    @Throws(Exception::class)
+    fun decryptBase64DESede(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String = "DESede/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return decryptDESede(Base64.decode(data, Base64.NO_WRAP), key, transformation, iv)
+    }
+
+    /**
+     * Return the bytes of DESede decryption.
+     *
+     * @param data           The data.
+     * @param key            The key.
+     * @param transformation The name of the transformation,
+     * 加密算法/加密模式/填充类型, *DESede/CBC/PKCS5Padding*.
+     * @param iv             The buffer with the IV. The contents of the
+     * buffer are copied to protect against subsequent modification.
+     * @return the bytes of AES decryption
+     */
+    @Throws(Exception::class)
+    fun decryptDESede(
+        data: ByteArray?,
+        key: ByteArray?,
+        transformation: String = "DESede/ECB/PKCS5Padding",
+        iv: ByteArray? = null
+    ): ByteArray? {
+        return symmetricTemplate(data, key, "DESede", transformation, iv, false)
+    }
 
     /**
      * Return the bytes of symmetric encryption or decryption.
@@ -166,4 +342,4 @@ object EncoderUtils {
     }
 
 
-}
+}

+ 1 - 23
app/src/main/java/io/legado/app/utils/MD5Utils.kt

@@ -1,9 +1,5 @@
 package io.legado.app.utils
 
-
-import java.security.MessageDigest
-import java.security.NoSuchAlgorithmException
-
 /**
  * 将字符串转化为MD5
  */
@@ -11,25 +7,7 @@ import java.security.NoSuchAlgorithmException
 object MD5Utils {
 
     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()
-            for (b in bytes) {
-                val bt: Int = b.toInt() and 0xff
-                if (bt < 16) {
-                    stringBuffer.append(0)
-                }
-                stringBuffer.append(Integer.toHexString(bt))
-            }
-            reStr = stringBuffer.toString()
-        } catch (e: NoSuchAlgorithmException) {
-            e.printOnDebug()
-        }
-
-        return reStr
+        return DigestUtils.getDigest("MD5", str)
     }
 
     fun md5Encode16(str: String): String {

+ 13 - 27
build.gradle

@@ -1,36 +1,22 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.6.10'
-    repositories {
-        //原仓库
-        google()
-        mavenCentral()
-        maven { url 'https://plugins.gradle.org/m2/' }
-        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改
-        //maven { url 'https://maven.aliyun.com/repository/google' }
-        //maven { url 'https://maven.aliyun.com/repository/public' }
-        //maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:7.2.0'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.4'
-        classpath 'de.undercouch:gradle-download-task:5.0.1'
-        classpath 'com.google.gms:google-services:4.3.10'
+    ext{
+        kotlin_version = '1.6.10'
+        agp_version='7.2.0'
     }
 }
 
-allprojects {
-    repositories {
-        //原仓库
-        google()
-        mavenCentral()
-        maven { url 'https://jitpack.io' }
-        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改
-        //maven { url 'https://maven.aliyun.com/repository/google' }
-        //maven { url 'https://maven.aliyun.com/repository/public' }
-    }
+
+
+plugins {
+    id 'com.android.application' version "$agp_version" apply false
+    id 'com.android.library' version "$agp_version" apply false
+    id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
+    id "de.timfreiheit.resourceplaceholders" version "0.4" apply false
+    id "de.undercouch.download" version "5.1.0" apply false
+    id "com.google.gms.google-services" version "4.3.10" apply false
+
 }
 
 task clean(type: Delete) {

+ 7 - 3
epublib/build.gradle

@@ -3,12 +3,12 @@ plugins {
 }
 
 android {
-    compileSdkVersion 31
-    buildToolsVersion '31.0.0'
+    compileSdkVersion 32
+    buildToolsVersion '32.0.0'
     namespace 'me.ag2s.epublib'
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 31
+        targetSdkVersion 32
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles "consumer-rules.pro"
@@ -21,11 +21,15 @@ android {
         }
         android {
         }
+
     }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8
     }
+    lint {
+        checkDependencies true
+    }
 }
 
 dependencies {

+ 5 - 2
gradle.properties

@@ -19,8 +19,11 @@ android.useAndroidX=true
 android.enableJetifier=true
 # Kotlin code style for this project: "official" or "obsolete":
 kotlin.code.style=official
-
 android.enableResourceOptimizations=true
-
+android.enableNewResourceShrinker=true
+android.experimental.enableNewResourceShrinker.preciseShrinking=true
+# enables namespacing of each library's R class so that its R class includes only the resources declared in the library itself
+# and none from the library's dependencies, thereby reducing the size of the R class for that library.
+android.nonTransitiveRClass=true
 #https://chromiumdash.appspot.com/releases?platform=Android
 CronetVersion=101.0.4951.61

+ 2 - 2
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
-#Fri May 07 15:24:46 CST 2021
+#Mon May 23 22:07:39 CST 2022
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME

+ 30 - 0
settings.gradle

@@ -1 +1,31 @@
+pluginManagement {
+    repositories {
+        //原仓库
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改
+        //maven {url"https://maven-central-asia.storage-download.googleapis.com/maven2/"}
+        //maven { url 'https://maven.aliyun.com/repository/google' }
+        //maven { url 'https://maven.aliyun.com/repository/public' }
+        //maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
+    }
+}
+
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        //原仓库
+        google()
+        mavenCentral()
+        maven { url 'https://jitpack.io' }
+        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改
+        //maven {url"https://maven-central-asia.storage-download.googleapis.com/maven2/"}
+        //maven { url 'https://maven.aliyun.com/repository/google' }
+        //maven { url 'https://maven.aliyun.com/repository/public' }
+
+    }
+}
+rootProject.name = 'legado'
+
 include ':app',':epublib'

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно