Merge remote-tracking branch 'origin/master'
This commit is contained in:
+15
-9
@@ -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,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" />
|
||||
|
||||
@@ -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
|
||||
```
|
||||
* @param data 被摘要数据
|
||||
* @param algorithm 签名算法 MD5/SHA1/SHA256/SHA512
|
||||
java.digestHex(data: String,algorithm: String,): String?
|
||||
java.digestHex(data: String, algorithm: String,): String?
|
||||
|
||||
java.digestBase64Str(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*
|
||||
```
|
||||
java.HMacHex(data: String, algorithm: String, key: String): String
|
||||
|
||||
java.HMacBase64(data: String, algorithm: String, key: String): String
|
||||
```
|
||||
* md5
|
||||
```
|
||||
|
||||
@@ -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目录识别,取目录数量最多的规则
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
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 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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.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,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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user