Merge remote-tracking branch 'origin/master'

pull/1905/head
kunfei 3 years ago
commit 53f0340098
  1. 24
      app/build.gradle
  2. 3
      app/src/main/AndroidManifest.xml
  3. 43
      app/src/main/assets/help/jsHelp.md
  4. 2
      app/src/main/assets/updateLog.md
  5. 2
      app/src/main/assets/web/bookshelf/css/detail.ac2331a7.css
  6. 4
      app/src/main/assets/web/bookshelf/index.html
  7. 0
      app/src/main/assets/web/bookshelf/js/about.74ccd735.js
  8. 2
      app/src/main/assets/web/bookshelf/js/app.7cd09a57.js
  9. 8
      app/src/main/assets/web/bookshelf/js/chunk-vendors.d7fcc132.js
  10. 1
      app/src/main/assets/web/bookshelf/js/detail.bb04bd6b.js
  11. 1
      app/src/main/assets/web/bookshelf/js/detail.d5b3eb30.js
  12. 5
      app/src/main/java/io/legado/app/constant/AppConst.kt
  13. 212
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  14. 2
      app/src/main/java/io/legado/app/lib/prefs/ColorPreference.kt
  15. 2
      app/src/main/java/io/legado/app/lib/prefs/SwitchPreference.kt
  16. 36
      app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt
  17. 15
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  18. 6
      app/src/main/java/io/legado/app/lib/theme/TintHelper.kt
  19. 24
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  20. 3
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBook.kt
  21. 5
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookAdapter.kt
  22. 5
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookManager.kt
  23. 2
      app/src/main/java/io/legado/app/ui/book/remote/RemoteBookViewModel.kt
  24. 35
      app/src/main/java/io/legado/app/ui/book/remote/manager/RemoteBookWebDav.kt
  25. 3
      app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt
  26. 62
      app/src/main/java/io/legado/app/utils/DigestUtils.kt
  27. 192
      app/src/main/java/io/legado/app/utils/EncoderUtils.kt
  28. 24
      app/src/main/java/io/legado/app/utils/MD5Utils.kt
  29. 40
      build.gradle
  30. 10
      epublib/build.gradle
  31. 7
      gradle.properties
  32. 4
      gradle/wrapper/gradle-wrapper.properties
  33. 30
      settings.gradle

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

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

@ -54,7 +54,11 @@ java.put(key, value)
java.ajax(urlStr): String java.ajax(urlStr): String
java.ajaxAll(urlList: Array<String>): Array<StrResponse?> java.ajaxAll(urlList: Array<String>): Array<StrResponse?>
//返回Response 方法body() code() message() header() raw() toString() //返回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访问网络 * 使用webView访问网络
* @param html 直接用webView载入的html, 如果html为空直接访问url * @param html 直接用webView载入的html, 如果html为空直接访问url
@ -129,12 +133,10 @@ deleteFile(path: String)
**** ****
> [常见加密解密算法介绍](https://www.yijiyong.com/algorithm/encryption/01-intro.html) > [常见加密解密算法介绍](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 * 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.aesDecodeToString(str: String, key: String, transformation: String, iv: String)
java.aesBase64DecodeToString(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) 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 * 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.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? 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 被摘要数据 java.HMacHex(data: String, algorithm: String, key: String): String
* @param algorithm 签名算法 MD5/SHA1/SHA256/SHA512
java.digestHex(data: String,algorithm: String,): String?
java.digestBase64Str(data: String,algorithm: String,): String? java.HMacBase64(data: String, algorithm: String, key: String): String
``` ```
* md5 * md5
``` ```

@ -15,6 +15,8 @@
* 修复部分txt章节结尾乱码bug * 修复部分txt章节结尾乱码bug
* 优化翻页流畅度 * 优化翻页流畅度
* js添加des HMac
* 登录ui用户信息默认加密方式改变,需要重新登录
**2022/05/21** **2022/05/21**

@ -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; 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -5,7 +5,6 @@ import android.content.pm.PackageManager
import android.provider.Settings import android.provider.Settings
import com.script.javascript.RhinoScriptEngine import com.script.javascript.RhinoScriptEngine
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
import io.legado.app.R
import splitties.init.appCtx import splitties.init.appCtx
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -57,7 +56,9 @@ object AppConst {
) )
@SuppressLint("PrivateResource") @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 { val androidId: String by lazy {
Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) ?: "null" Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) ?: "null"

@ -3,9 +3,6 @@ package io.legado.app.help
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.annotation.Keep 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
import io.legado.app.constant.AppConst.dateFormat import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
@ -299,14 +296,6 @@ interface JsExtensions {
return EncoderUtils.base64Encode(str, flags) 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() return UUID.randomUUID().toString()
} }
fun androidId(): String {
return AppConst.androidId
}
//******************对称加密解密************************//
/////AES
/** /**
* AES 解码为 ByteArray * AES 解码为 ByteArray
* @param str 传入的AES加密的数据 * @param str 传入的AES加密的数据
@ -640,6 +636,31 @@ interface JsExtensions {
return aesDecodeToByteArray(str, key, transformation, iv)?.let { String(it) } 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 * 已经base64的AES 解码为 ByteArray
* @param str 传入的AES Base64加密的数据 * @param str 传入的AES Base64加密的数据
@ -753,35 +774,78 @@ interface JsExtensions {
return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) } 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 key Base64后的密钥
* @param mode 模式 * @param mode 模式
* @param padding 补码方式 * @param padding 补码方式
* @param iv Base64后的加盐 * @param iv Base64后的加盐
* @return 解密后的字符串 * @return 加密后的Base64
*/ */
fun aesDecodeArgsBase64Str( fun aesEncodeArgsBase64Str(
data: String, data: String,
key: String, key: String,
mode: String, mode: String,
padding: String, padding: String,
iv: String iv: String
): String? { ): String? {
return AES( return EncoderUtils.encryptAES2Base64(
mode, data.encodeToByteArray(),
padding,
Base64.decode(key, Base64.NO_WRAP), Base64.decode(key, Base64.NO_WRAP),
"AES/${mode}/${padding}",
Base64.decode(iv, Base64.NO_WRAP) 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解密 * 3DES解密
* *
@ -799,7 +863,12 @@ interface JsExtensions {
padding: String, padding: String,
iv: String iv: String
): 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, padding: String,
iv: String iv: String
): String? { ): String? {
return DESede( return EncoderUtils.decryptDESede(
mode, data.encodeToByteArray(),
padding,
Base64.decode(key, Base64.NO_WRAP), Base64.decode(key, Base64.NO_WRAP),
"DESede/${mode}/${padding}",
Base64.decode(iv, Base64.NO_WRAP) 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 * 3DES加密并转为Base64
@ -869,7 +914,12 @@ interface JsExtensions {
padding: String, padding: String,
iv: String iv: String
): 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, padding: String,
iv: String iv: String
): String? { ): String? {
return DESede( return EncoderUtils.encryptDESede2Base64(
mode, data.encodeToByteArray(),
padding,
Base64.decode(key, Base64.NO_WRAP), Base64.decode(key, Base64.NO_WRAP),
"DESede/${mode}/${padding}",
Base64.decode(iv, Base64.NO_WRAP) Base64.decode(iv, Base64.NO_WRAP)
).encryptBase64(data) )?.let { String(it) }
} }
//******************消息摘要/散列消息鉴别码************************//
/** /**
* 生成摘要并转为16进制字符串 * 生成摘要并转为16进制字符串
* *
@ -907,8 +959,8 @@ interface JsExtensions {
fun digestHex( fun digestHex(
data: String, data: String,
algorithm: String, algorithm: String,
): String? { ): String {
return DigestUtil.digester(algorithm).digestHex(data) return DigestUtils.getDigest(algorithm, data)
} }
/** /**
@ -921,8 +973,48 @@ interface JsExtensions {
fun digestBase64Str( fun digestBase64Str(
data: String, data: String,
algorithm: String, algorithm: String,
): String? { ): String {
return Base64.encodeToString(DigestUtil.digester(algorithm).digest(data), Base64.NO_WRAP) 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)
} }
} }

@ -120,7 +120,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
val v = io.legado.app.lib.prefs.Preference.bindView<ColorPanelView>( val v = io.legado.app.lib.prefs.Preference.bindView<ColorPanelView>(
context, holder, icon, title, summary, widgetLayoutResource, 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) { if (v is ColorPanelView) {
v.color = mColor v.color = mColor

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

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

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

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

@ -219,6 +219,8 @@ object LocalBook {
fileName: String, fileName: String,
source: BaseSource? = null, source: BaseSource? = null,
): Uri { ): Uri {
AppConfig.defaultBookTreeUri
?: throw NoStackTraceException("没有设置书籍保存位置!")
val bytes = when { val bytes = when {
str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray() str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray()
str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT) str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT)
@ -241,7 +243,7 @@ object LocalBook {
return type ?: fileType return type ?: fileType
} }
private fun saveBookFile( fun saveBookFile(
bytes: ByteArray, bytes: ByteArray,
fileName: String fileName: String
): Uri { ): 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 { fun mergeBook(localBook: Book, onLineBook: Book?): Book {
onLineBook ?: return localBook onLineBook ?: return localBook

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

@ -2,11 +2,12 @@ package io.legado.app.ui.book.remote
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import cn.hutool.core.date.LocalDateTimeUtil
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.databinding.ItemRemoteBookBinding import io.legado.app.databinding.ItemRemoteBookBinding
import io.legado.app.utils.ConvertUtils 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(".") tvName.text = item.filename.substringBeforeLast(".")
tvContentType.text = item.contentType tvContentType.text = item.contentType
tvSize.text = ConvertUtils.formatFileSize(item.size) 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))
} }
} }

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

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

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

@ -17,7 +17,6 @@ import android.widget.ImageView
import android.widget.OverScroller import android.widget.OverScroller
import android.widget.Scroller import android.widget.Scroller
import androidx.appcompat.widget.AppCompatImageView 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.Info
import io.legado.app.ui.widget.image.photo.OnRotateListener import io.legado.app.ui.widget.image.photo.OnRotateListener
import io.legado.app.ui.widget.image.photo.RotateGestureDetector import io.legado.app.ui.widget.image.photo.RotateGestureDetector
@ -917,7 +916,7 @@ class PhotoView @JvmOverloads constructor(
var viewParent: ViewParent = target.parent var viewParent: ViewParent = target.parent
while (viewParent is View) { while (viewParent is View) {
val view: View = viewParent 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[0] -= view.scrollX
position[1] -= view.scrollY position[1] -= view.scrollY
position[0] += view.left position[0] += view.left

@ -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()
}
}

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

@ -1,9 +1,5 @@
package io.legado.app.utils package io.legado.app.utils
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
/** /**
* 将字符串转化为MD5 * 将字符串转化为MD5
*/ */
@ -11,25 +7,7 @@ import java.security.NoSuchAlgorithmException
object MD5Utils { object MD5Utils {
fun md5Encode(str: String?): String { fun md5Encode(str: String?): String {
if (str == null) return "" return DigestUtils.getDigest("MD5", str)
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
} }
fun md5Encode16(str: String): String { fun md5Encode16(str: String): String {

@ -1,36 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.6.10' ext{
repositories { kotlin_version = '1.6.10'
// agp_version='7.2.0'
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'
} }
} }
allprojects {
repositories {
// plugins {
google() id 'com.android.application' version "$agp_version" apply false
mavenCentral() id 'com.android.library' version "$agp_version" apply false
maven { url 'https://jitpack.io' } id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
//,, id "de.timfreiheit.resourceplaceholders" version "0.4" apply false
//maven { url 'https://maven.aliyun.com/repository/google' } id "de.undercouch.download" version "5.1.0" apply false
//maven { url 'https://maven.aliyun.com/repository/public' } id "com.google.gms.google-services" version "4.3.10" apply false
}
} }
task clean(type: Delete) { task clean(type: Delete) {

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

@ -19,8 +19,11 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.enableResourceOptimizations=true 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 #https://chromiumdash.appspot.com/releases?platform=Android
CronetVersion=101.0.4951.61 CronetVersion=101.0.4951.61

@ -1,6 +1,6 @@
#Fri May 07 15:24:46 CST 2021 #Mon May 23 22:07:39 CST 2022
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

@ -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' include ':app',':epublib'

Loading…
Cancel
Save