Merge branch 'dev' into 'master'

master
fengyuecanzhu 2 years ago
commit b6485fab8b
  1. 18
      .github/workflows/build.yml
  2. 32
      .idea/assetWizardSettings.xml
  3. 14
      .idea/misc.xml
  4. 6
      app/build.gradle
  5. 14
      app/release.md
  6. 8
      app/src/main/assets/updatelog.fy
  7. 7
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  8. 3
      app/src/main/java/xyz/fycz/myreader/common/URLCONST.java
  9. 2
      app/src/main/java/xyz/fycz/myreader/entity/PluginConfig.kt
  10. 23
      app/src/main/java/xyz/fycz/myreader/greendao/service/CacheManager.kt
  11. 11
      app/src/main/java/xyz/fycz/myreader/greendao/service/ChapterService.java
  12. 496
      app/src/main/java/xyz/fycz/myreader/model/third3/analyzeRule/JsEncodeUtils.kt
  13. 272
      app/src/main/java/xyz/fycz/myreader/model/third3/analyzeRule/JsExtensions.kt
  14. 4
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  15. 10
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookMarkAdapter.java
  16. 29
      app/src/main/java/xyz/fycz/myreader/ui/adapter/ChapterTitleAdapter.java
  17. 12
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/CatalogHolder.java
  18. 15
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java
  19. 7
      app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
  20. 3
      app/src/main/java/xyz/fycz/myreader/ui/presenter/BookMarkPresenter.java
  21. 3
      app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java
  22. 2
      app/src/main/java/xyz/fycz/myreader/util/utils/ACache.kt
  23. 4
      app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java
  24. 14
      app/src/main/java/xyz/fycz/myreader/util/utils/PluginUtils.kt
  25. 31
      app/src/main/java/xyz/fycz/myreader/util/utils/StringExtensions.kt
  26. 84
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/Third3FindCrawler.kt
  27. BIN
      app/src/main/res/drawable-xhdpi/ic_item_category_download.png
  28. BIN
      app/src/main/res/drawable-xhdpi/ic_item_category_normal.png
  29. 33
      app/src/main/res/drawable/ic_cloud_download.xml
  30. 25
      app/src/main/res/drawable/selector_category_load.xml
  31. 25
      app/src/main/res/drawable/selector_category_unload.xml
  32. 79
      app/src/main/res/layout/item_chapter.xml
  33. 9
      app/src/main/res/layout/layout_book_detail_content.xml
  34. 46
      app/src/main/res/layout/listview_chapter_title_item.xml
  35. 2
      app/version_code.properties
  36. 2
      dynamic/build.gradle
  37. 1
      dynamic/src/main/java/xyz/fycz/dynamic/AppLoadImpl.kt
  38. 2
      dynamic/src/main/java/xyz/fycz/dynamic/fix/App246Fix.kt
  39. 3
      dynamic/src/main/java/xyz/fycz/dynamic/fix/App246Fix5.kt
  40. 92
      dynamic/src/main/java/xyz/fycz/dynamic/fix/App250Fix.kt

@ -10,10 +10,10 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
# 获取打包秘钥 # 获取打包秘钥
- name: Checkout Android Keystore - name: Checkout Android Keystore
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
repository: fengyuecanzhu/Key repository: fengyuecanzhu/Key
token: ${{ secrets.KEY_TOKEN }} # 连接仓库的token,需要单独配置 token: ${{ secrets.KEY_TOKEN }} # 连接仓库的token,需要单独配置
@ -27,18 +27,18 @@ jobs:
version=$VERSION_CODE version=$VERSION_CODE
versionN=v${version:0:1}.${version:1:1}.${version:2:1} versionN=v${version:0:1}.${version:1:1}.${version:2:1}
echo ::set-output name=need_create_release::"$CREATE_RELEASE" echo need_create_release="$CREATE_RELEASE" >> $GITHUB_OUTPUT
echo ::set-output name=version_name::"$versionN" echo version_name="$versionN" >> $GITHUB_OUTPUT
echo need_create_release=$CREATE_RELEASE echo need_create_release=$CREATE_RELEASE
echo version_name=$versionN echo version_name=$versionN
if [ $CREATE_RELEASE == 'true' -a ${{ github.ref }} == 'refs/heads/master' ];then if [ $CREATE_RELEASE == 'true' -a ${{ github.ref }} == 'refs/heads/master' ];then
echo ::set-output name=lanzou_folder_id::"1608604" echo lanzou_folder_id="1608604" >> $GITHUB_OUTPUT
echo ::set-output name=lanzou_share_url::"https://fycz.lanzoui.com/b00ngso7e" echo lanzou_share_url="https://fycz.lanzoui.com/b00ngso7e" >> $GITHUB_OUTPUT
else else
echo ::set-output name=lanzou_folder_id::"2226473" echo lanzou_folder_id="2226473" >> $GITHUB_OUTPUT
echo ::set-output name=lanzou_share_url::"https://fycz.lanzoui.com/b00nu1f8d" echo lanzou_share_url="https://fycz.lanzoui.com/b00nu1f8d" >> $GITHUB_OUTPUT
fi fi
# 编译打包 # 编译打包
- name: Build With Gradle - name: Build With Gradle
@ -59,7 +59,7 @@ jobs:
if [[ ${{ steps.config.outputs.need_create_release }} != 'true' ]];then if [[ ${{ steps.config.outputs.need_create_release }} != 'true' ]];then
path="$GITHUB_WORKSPACE/app/build/outputs/apk/debug" path="$GITHUB_WORKSPACE/app/build/outputs/apk/debug"
fi fi
echo ::set-output name=file_path::"$path" echo file_path="$path" >> $GITHUB_OUTPUT
- name: Upload Lanzou - name: Upload Lanzou
run: | run: |
echo "上传APP至蓝奏云" echo "上传APP至蓝奏云"

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_cloud_download" />
<entry key="sourceFile" value="F:\SVG图标\cloud.svg" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

@ -3,21 +3,35 @@
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
<map> <map>
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_bookstore.xml" value="0.1" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_check.xml" value="0.118" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_cloud_download.xml" value="0.118" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_download.xml" value="0.118" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_download_line.xml" value="0.118" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/selector_category_load.xml" value="0.118" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_about.xml" value="0.2296195652173913" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_about.xml" value="0.2296195652173913" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_ad_setting.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_ad_setting.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_book_detail.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_donate.xml" value="0.264" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_donate.xml" value="0.264" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.2" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.2" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_read.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_read.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_user_info.xml" value="0.2" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_user_info.xml" value="0.2" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_catalog.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_private_bookcase.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_private_bookcase.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_proxy_setting.xml" value="0.22826086956521738" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_proxy_setting.xml" value="0.22826086956521738" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_webdav_setting.xml" value="0.2" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_webdav_setting.xml" value="0.2" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_book_source.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/item_book_source.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_change_source.xml" value="0.22826086956521738" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/item_change_source.xml" value="0.22826086956521738" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_chapter.xml" value="0.2296195652173913" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_find_source.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/item_find_source.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_book.xml" value="0.109375" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_word1.xml" value="0.2296195652173913" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_source_edit.xml" value="0.22826086956521738" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/item_source_edit.xml" value="0.22826086956521738" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_subscribe_source.xml" value="0.2" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/item_subscribe_source.xml" value="0.2" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/layout_about_content.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/layout_about_content.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/layout_book_detail_content.xml" value="0.2296195652173913" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/listview_chapter_title_item.xml" value="0.2296195652173913" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/toolbar.xml" value="0.2296195652173913" />
</map> </map>
</option> </option>
</component> </component>

@ -293,7 +293,11 @@ dependencies {
} }
//https://github.com/fengyuecanzhu/Maple //https://github.com/fengyuecanzhu/Maple
implementation("me.fycz.maple:maple:1.9") implementation("me.fycz.maple:maple:2.0")
//,使
//noinspection GradleDependency,GradlePackageUpdate
implementation('cn.hutool:hutool-crypto:5.8.10')
} }
greendao { greendao {

@ -1,9 +1,5 @@
* 1、修复阅读界面概率性闪退的问题 * 1、[书籍详情界面]取消书籍简介展开时最大行数限制
* 2、关于界面新增插件加载结果 * 2、修复从数据库中读取章节时部分数据项缺失的bug
* 3、修复书源订阅失败的问题 * 3、目录列表添加更新时间显示
* 4、修复字体下载失败的问题 * 4、更新部分书源接口
* 5、\[设置-缓存设置\]新增清除广告文件 * 5、修复插件加载bug
* 6、修复检查更新失败的问题
* 7、修复获取更新链接失败的问题
* 8、更新订阅书源链接
* 9、修复其他已知bug

@ -1,3 +1,11 @@
2022.12.26
风月读书v2.5.1
1、[书籍详情界面]取消书籍简介展开时最大行数限制
2、修复从数据库中读取章节时部分数据项缺失的bug
3、目录列表添加更新时间显示
4、更新部分书源接口
5、修复插件加载bug
2022.08.02 2022.08.02
风月读书v2.5.0 风月读书v2.5.0
1、修复阅读界面概率性闪退的问题 1、修复阅读界面概率性闪退的问题

@ -18,6 +18,7 @@
package xyz.fycz.myreader.common; package xyz.fycz.myreader.common;
import android.annotation.SuppressLint;
import android.os.Environment; import android.os.Environment;
import android.provider.Settings; import android.provider.Settings;
@ -31,13 +32,15 @@ import xyz.fycz.myreader.util.utils.FileUtils;
import java.io.File; import java.io.File;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
@SuppressLint("SimpleDateFormat")
public class APPCONST { public class APPCONST {
public static String publicKey = "fyds2.0";//服务端公钥 public static String publicKey = "fyds2.0";//服务端公钥
@ -163,6 +166,8 @@ public class APPCONST {
public static final String androidId = getAndroidId(); public static final String androidId = getAndroidId();
public static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
public static String getAndroidId() { public static String getAndroidId() {
return Settings.System.getString(App.getmContext().getContentResolver(), Settings.Secure.ANDROID_ID); return Settings.System.getString(App.getmContext().getContentResolver(), Settings.Secure.ANDROID_ID);
} }

@ -56,7 +56,8 @@ public class URLCONST {
public static final String QUOTATION = "https://v1.hitokoto.cn/?encode=json&charset=utf-8"; public static final String QUOTATION = "https://v1.hitokoto.cn/?encode=json&charset=utf-8";
public static final String DEFAULT_PLUGIN_CONFIG_URL = "https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/Plugin/release/config_FYReader.json"; //public static final String DEFAULT_PLUGIN_CONFIG_URL = "https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/Plugin/release/config_FYReader.json";
public static final String DEFAULT_PLUGIN_CONFIG_URL = "http://101.43.83.105:3000/fengyue/FYReader-Res/raw/branch/main/Plugin/release/config_FYReader.json";
public static String getDefaultDomain() { public static String getDefaultDomain() {
return SharedPreUtils.getInstance().getString("domain", "fycz.me"); return SharedPreUtils.getInstance().getString("domain", "fycz.me");

@ -31,5 +31,5 @@ data class PluginConfig(
val changelog: String val changelog: String
) { ) {
constructor(name: String, versionCode: Int) : constructor(name: String, versionCode: Int) :
this(name, versionCode, "", "", "", "") this(name, versionCode, "", "", "", "插件加载失败,当前为默认插件")
} }

@ -19,6 +19,7 @@
package xyz.fycz.myreader.greendao.service package xyz.fycz.myreader.greendao.service
import android.database.Cursor import android.database.Cursor
import androidx.collection.LruCache
import xyz.fycz.myreader.application.App import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.greendao.DbManager import xyz.fycz.myreader.greendao.DbManager
import xyz.fycz.myreader.greendao.entity.Cache import xyz.fycz.myreader.greendao.entity.Cache
@ -31,6 +32,7 @@ import java.lang.Exception
object CacheManager { object CacheManager {
private val queryTTFMap = hashMapOf<String, Pair<Long, QueryTTF>>() private val queryTTFMap = hashMapOf<String, Pair<Long, QueryTTF>>()
private val memoryLruCache = object : LruCache<String, String>(100) {}
/** /**
* saveTime 单位为秒 * saveTime 单位为秒
@ -49,6 +51,19 @@ object CacheManager {
} }
} }
fun putMemory(key: String, value: String) {
memoryLruCache.put(key, value)
}
//从内存中获取数据 使用lruCache
fun getFromMemory(key: String): String? {
return memoryLruCache.get(key)
}
fun deleteMemory(key: String) {
memoryLruCache.remove(key)
}
fun get(key: String): String? { fun get(key: String): String? {
var str: String? = null var str: String? = null
try { try {
@ -94,6 +109,14 @@ object CacheManager {
return null return null
} }
fun putFile(key: String, value: String, saveTime: Int = 0) {
ACache.get().put(key, value, saveTime)
}
fun getFile(key: String): String? {
return ACache.get().getAsString(key)
}
fun delete(key: String) { fun delete(key: String) {
DbManager.getDaoSession().cacheDao.deleteByKey(key) DbManager.getDaoSession().cacheDao.deleteByKey(key)
ACache.get(App.getmContext()).remove(key) ACache.get(App.getmContext()).remove(key)

@ -66,6 +66,7 @@ public class ChapterService extends BaseService {
chapter.setContent(cursor.getString(8)); chapter.setContent(cursor.getString(8));
chapter.setStart(cursor.getInt(9)); chapter.setStart(cursor.getInt(9));
chapter.setEnd(cursor.getInt(10)); chapter.setEnd(cursor.getInt(10));
chapter.setVariable(cursor.getString(11));
chapters.add(chapter); chapters.add(chapter);
} }
} catch (Exception e) { } catch (Exception e) {
@ -95,10 +96,14 @@ public class ChapterService extends BaseService {
if (StringHelper.isEmpty(bookId)) return new ArrayList<>(); if (StringHelper.isEmpty(bookId)) return new ArrayList<>();
return DbManager.getDaoSession().getChapterDao()
.queryBuilder()
.where(ChapterDao.Properties.BookId.eq(bookId))
.orderAsc(ChapterDao.Properties.Number)
.list();
/*String sql = "select * from chapter where book_id = ? order by number";
String sql = "select * from chapter where book_id = ? order by number"; return findChapters(sql, new String[]{bookId});*/
return findChapters(sql, new String[]{bookId});
} }
/** /**

@ -0,0 +1,496 @@
/*
* This file is part of FYReader.
* FYReader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FYReader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FYReader. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 - 2022 fengyuecanzhu
*/
package xyz.fycz.myreader.model.third3.analyzeRule
import android.util.Base64
import cn.hutool.crypto.digest.DigestUtil
import cn.hutool.crypto.digest.HMac
import cn.hutool.crypto.symmetric.SymmetricCrypto
import xyz.fycz.myreader.util.utils.MD5Utils
/**
* js加解密扩展类, 在js中通过java变量调用
* 添加方法请更新文档/legado/app/src/main/assets/help/JsHelp.md
*/
interface JsEncodeUtils {
fun md5Encode(str: String): String {
return MD5Utils.md5Encode(str)
}
fun md5Encode16(str: String): String {
return MD5Utils.md5Encode16(str)
}
//******************对称加密解密************************//
/**
* 在js中这样使用
* java.createSymmetricCrypto(transformation, key, iv).decrypt(data)
* java.createSymmetricCrypto(transformation, key, iv).decryptStr(data)
* java.createSymmetricCrypto(transformation, key, iv).encrypt(data)
* java.createSymmetricCrypto(transformation, key, iv).encryptBase64(data)
* java.createSymmetricCrypto(transformation, key, iv).encryptHex(data)
*/
/* 调用SymmetricCrypto key为null时使用随机密钥*/
fun createSymmetricCrypto(
transformation: String,
key: ByteArray?,
iv: ByteArray?
): SymmetricCrypto {
val symmetricCrypto = SymmetricCrypto(transformation, key)
return if (iv != null && iv.isNotEmpty()) symmetricCrypto.setIv(iv) else symmetricCrypto
}
fun createSymmetricCrypto(
transformation: String,
key: ByteArray
): SymmetricCrypto {
return createSymmetricCrypto(transformation, key, null)
}
fun createSymmetricCrypto(
transformation: String,
key: String
): SymmetricCrypto {
return createSymmetricCrypto(transformation, key, null)
}
fun createSymmetricCrypto(
transformation: String,
key: String,
iv: String?
): SymmetricCrypto {
return createSymmetricCrypto(
transformation, key.encodeToByteArray(), iv?.encodeToByteArray()
)
}
//******************对称加密解密old************************//
/////AES
/**
* AES 解码为 ByteArray
* @param str 传入的AES加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decrypt(str)")
)
fun aesDecodeToByteArray(
str: String, key: String, transformation: String, iv: String
): ByteArray? {
return createSymmetricCrypto(transformation, key, iv).decrypt(str)
}
/**
* AES 解码为 String
* @param str 传入的AES加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(str)")
)
fun aesDecodeToString(
str: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).decryptStr(str)
}
/**
* AES解码为String算法参数经过Base64加密
*
* @param data 加密的字符串
* @param key Base64后的密钥
* @param mode 模式
* @param padding 补码方式
* @param iv Base64后的加盐
* @return 解密后的字符串
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun aesDecodeArgsBase64Str(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto(
"AES/${mode}/${padding}",
Base64.decode(key, Base64.NO_WRAP),
Base64.decode(iv, Base64.NO_WRAP)
).decryptStr(data)
}
/**
* 已经base64的AES 解码为 ByteArray
* @param str 传入的AES Base64加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decrypt(str)")
)
fun aesBase64DecodeToByteArray(
str: String, key: String, transformation: String, iv: String
): ByteArray? {
return createSymmetricCrypto(transformation, key, iv).decrypt(str)
}
/**
* 已经base64的AES 解码为 String
* @param str 传入的AES Base64加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(str)")
)
fun aesBase64DecodeToString(
str: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).decryptStr(str)
}
/**
* 加密aes为ByteArray
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decrypt(data)")
)
fun aesEncodeToByteArray(
data: String, key: String, transformation: String, iv: String
): ByteArray? {
return createSymmetricCrypto(transformation, key, iv).decrypt(data)
}
/**
* 加密aes为String
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun aesEncodeToString(
data: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).decryptStr(data)
}
/**
* 加密aes后Base64化的ByteArray
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data).toByteArray()")
)
fun aesEncodeToBase64ByteArray(
data: String, key: String, transformation: String, iv: String
): ByteArray? {
return createSymmetricCrypto(transformation, key, iv).encryptBase64(data).toByteArray()
}
/**
* 加密aes后Base64化的String
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data)")
)
fun aesEncodeToBase64String(
data: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).encryptBase64(data)
}
/**
* AES加密并转为Base64算法参数经过Base64加密
*
* @param data 被加密的字符串
* @param key Base64后的密钥
* @param mode 模式
* @param padding 补码方式
* @param iv Base64后的加盐
* @return 加密后的Base64
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data)")
)
fun aesEncodeArgsBase64Str(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto("AES/${mode}/${padding}", key, iv).encryptBase64(data)
}
/////DES
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun desDecodeToString(
data: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).decryptStr(data)
}
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun desBase64DecodeToString(
data: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).decryptStr(data)
}
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encrypt(data)")
)
fun desEncodeToString(
data: String, key: String, transformation: String, iv: String
): String? {
return String(createSymmetricCrypto(transformation, key, iv).encrypt(data))
}
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data)")
)
fun desEncodeToBase64String(
data: String, key: String, transformation: String, iv: String
): String? {
return createSymmetricCrypto(transformation, key, iv).encryptBase64(data)
}
//////3DES
/**
* 3DES解密
*
* @param data 加密的字符串
* @param key 密钥
* @param mode 模式
* @param padding 补码方式
* @param iv 加盐
* @return 解密后的字符串
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun tripleDESDecodeStr(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto("DESede/${mode}/${padding}", key, iv).decryptStr(data)
}
/**
* 3DES解密算法参数经过Base64加密
*
* @param data 加密的字符串
* @param key Base64后的密钥
* @param mode 模式
* @param padding 补码方式
* @param iv Base64后的加盐
* @return 解密后的字符串
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).decryptStr(data)")
)
fun tripleDESDecodeArgsBase64Str(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto(
"DESede/${mode}/${padding}",
Base64.decode(key, Base64.NO_WRAP),
iv.encodeToByteArray()
).decryptStr(data)
}
/**
* 3DES加密并转为Base64
*
* @param data 被加密的字符串
* @param key 密钥
* @param mode 模式
* @param padding 补码方式
* @param iv 加盐
* @return 加密后的Base64
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data)")
)
fun tripleDESEncodeBase64Str(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto("DESede/${mode}/${padding}", key, iv)
.encryptBase64(data)
}
/**
* 3DES加密并转为Base64算法参数经过Base64加密
*
* @param data 被加密的字符串
* @param key Base64后的密钥
* @param mode 模式
* @param padding 补码方式
* @param iv Base64后的加盐
* @return 加密后的Base64
*/
@Deprecated(
"过于繁琐弃用",
ReplaceWith("createSymmetricCrypto(transformation, key, iv).encryptBase64(data)")
)
fun tripleDESEncodeArgsBase64Str(
data: String,
key: String,
mode: String,
padding: String,
iv: String
): String? {
return createSymmetricCrypto(
"DESede/${mode}/${padding}",
Base64.decode(key, Base64.NO_WRAP),
iv.encodeToByteArray()
).encryptBase64(data)
}
//******************消息摘要/散列消息鉴别码************************//
/**
* 生成摘要并转为16进制字符串
*
* @param data 被摘要数据
* @param algorithm 签名算法
* @return 16进制字符串
*/
fun digestHex(
data: String,
algorithm: String,
): String {
return DigestUtil.digester(algorithm).digestHex(data)
}
/**
* 生成摘要并转为Base64字符串
*
* @param data 被摘要数据
* @param algorithm 签名算法
* @return Base64字符串
*/
fun digestBase64Str(
data: String,
algorithm: String,
): String {
return Base64.encodeToString(DigestUtil.digester(algorithm).digest(data), Base64.NO_WRAP)
}
/**
* 生成散列消息鉴别码并转为16进制字符串
*
* @param data 被摘要数据
* @param algorithm 签名算法
* @param key 密钥
* @return 16进制字符串
*/
@Suppress("FunctionName")
fun HMacHex(
data: String,
algorithm: String,
key: String
): String {
return HMac(algorithm, key.toByteArray()).digestHex(data)
}
/**
* 生成散列消息鉴别码并转为Base64字符串
*
* @param data 被摘要数据
* @param algorithm 签名算法
* @param key 密钥
* @return Base64字符串
*/
@Suppress("FunctionName")
fun HMacBase64(
data: String,
algorithm: String,
key: String
): String {
return Base64.encodeToString(
HMac(algorithm, key.toByteArray()).digest(data),
Base64.NO_WRAP
)
}
}

@ -22,19 +22,22 @@ import android.net.Uri
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import androidx.annotation.Keep import androidx.annotation.Keep
import cn.hutool.core.util.HexUtil
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import nl.siegmann.epublib.epub.PackageDocumentBase.dateFormat
import org.jsoup.Connection import org.jsoup.Connection
import org.jsoup.Jsoup import org.jsoup.Jsoup
import xyz.fycz.myreader.application.App import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.common.APPCONST import xyz.fycz.myreader.common.APPCONST
import xyz.fycz.myreader.common.APPCONST.dateFormat
import xyz.fycz.myreader.greendao.service.CacheManager import xyz.fycz.myreader.greendao.service.CacheManager
import xyz.fycz.myreader.greendao.service.CookieStore import xyz.fycz.myreader.greendao.service.CookieStore
import xyz.fycz.myreader.model.third3.BaseSource import xyz.fycz.myreader.model.third3.BaseSource
import xyz.fycz.myreader.model.third3.Debug import xyz.fycz.myreader.model.third3.Debug
import xyz.fycz.myreader.model.third3.NoStackTraceException
import xyz.fycz.myreader.model.third3.http.* import xyz.fycz.myreader.model.third3.http.*
import xyz.fycz.myreader.util.ToastUtils
import xyz.fycz.myreader.util.ZipUtils import xyz.fycz.myreader.util.ZipUtils
import xyz.fycz.myreader.util.utils.* import xyz.fycz.myreader.util.utils.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -54,7 +57,7 @@ import java.util.zip.ZipInputStream
*/ */
@Keep @Keep
@Suppress("unused") @Suppress("unused")
interface JsExtensions { interface JsExtensions : JsEncodeUtils {
val TAG: String? val TAG: String?
get() = JsExtensions::class.simpleName get() = JsExtensions::class.simpleName
@ -141,11 +144,52 @@ interface JsExtensions {
BackstageWebView( BackstageWebView(
url = url, url = url,
html = html, html = html,
javaScript = js javaScript = js,
headerMap = getSource()?.getHeaderMap(true),
tag = getSource()?.getKey()
).getStrResponse().body ).getStrResponse().body
} }
} }
/**
* 可从网络本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入JavaScript脚本
*/
fun importScript(path: String): String {
val result = when {
path.startsWith("http") -> cacheFile(path) ?: ""
path.isUri() -> String(DocumentUtil.readBytes(App.getmContext(), Uri.parse(path)))
path.startsWith("/storage") -> FileUtils.readText(path)
else -> readTxtFile(path)
}
if (result.isBlank()) throw NoStackTraceException("$path 内容获取失败或者为空")
return result
}
/**
* 缓存以文本方式保存的文件 .js .txt等
* @param urlStr 网络文件的链接
* @return 返回缓存后的文件内容
*/
fun cacheFile(urlStr: String): String? {
return cacheFile(urlStr, 0)
}
/**
* 缓存以文本方式保存的文件 .js .txt等
* @param saveTime 缓存时间单位
*/
fun cacheFile(urlStr: String, saveTime: Int): String? {
val key = md5Encode16(urlStr)
val cache = CacheManager.getFile(key)
if (cache.isNullOrBlank()) {
log("首次下载 $urlStr")
val value = ajax(urlStr) ?: return null
CacheManager.putFile(key, value, saveTime)
return value
}
return cache
}
/** /**
* 实现16进制字符串转文件 * 实现16进制字符串转文件
* @param content 需要转成文件的16进制字符串 * @param content 需要转成文件的16进制字符串
@ -172,20 +216,45 @@ interface JsExtensions {
* js实现重定向拦截,网络访问get * js实现重定向拦截,网络访问get
*/ */
fun get(urlStr: String, headers: Map<String, String>): Connection.Response { fun get(urlStr: String, headers: Map<String, String>): Connection.Response {
return Jsoup.connect(urlStr) val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory) .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true) .ignoreContentType(true)
.followRedirects(false) .followRedirects(false)
.headers(headers) .headers(headers)
.method(Connection.Method.GET) .method(Connection.Method.GET)
.execute() .execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
}
return response
}
/**
* js实现重定向拦截,网络访问head,不返回Response Body更省流量
*/
fun head(urlStr: String, headers: Map<String, String>): Connection.Response {
val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.headers(headers)
.method(Connection.Method.HEAD)
.execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
}
return response
} }
/** /**
* 网络访问post * 网络访问post
*/ */
fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response { fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response {
return Jsoup.connect(urlStr) val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory) .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true) .ignoreContentType(true)
.followRedirects(false) .followRedirects(false)
@ -193,6 +262,12 @@ interface JsExtensions {
.headers(headers) .headers(headers)
.method(Connection.Method.POST) .method(Connection.Method.POST)
.execute() .execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
}
return response
} }
/** /**
@ -241,12 +316,19 @@ interface JsExtensions {
return EncoderUtils.base64Encode(str, flags) return EncoderUtils.base64Encode(str, flags)
} }
fun md5Encode(str: String): String { /* HexString 解码为字节数组 */
return MD5Utils.md5Encode(str) fun hexDecodeToByteArray(hex: String): ByteArray? {
return HexUtil.decodeHex(hex)
}
/* hexString 解码为utf8String*/
fun hexDecodeToString(hex: String): String? {
return HexUtil.decodeHexStr(hex)
} }
fun md5Encode16(str: String): String { /* utf8 编码为hexString */
return MD5Utils.md5Encode16(str) fun hexEncodeToString(utf8: String): String? {
return HexUtil.encodeHexStr(utf8)
} }
/** /**
@ -499,174 +581,48 @@ interface JsExtensions {
} }
/** /**
* 输出调试日志 * 弹窗提示
*/ */
fun log(msg: String): String { fun toast(msg: Any?) {
getSource()?.let { ToastUtils.showInfo("${getSource()?.getTag()}: ${msg.toString()}")
Debug.log(it.getKey(), msg)
} ?: Debug.log(msg)
if (App.isDebug()) {
Log.d(TAG + "-" + getSource()?.getKey(), msg)
}
return msg
} }
/** /**
* 生成UUID * 弹窗提示 停留时间较长
*/ */
fun randomUUID(): String { fun longToast(msg: Any?) {
return UUID.randomUUID().toString() toast(msg)
} }
/** /**
* AES 解码为 ByteArray * 输出调试日志
* @param str 传入的AES加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
fun aesDecodeToByteArray(
str: String, key: String, transformation: String, iv: String
): ByteArray? {
return try {
EncoderUtils.decryptAES(
data = str.encodeToByteArray(),
key = key.encodeToByteArray(),
transformation,
iv.encodeToByteArray()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
log(e.localizedMessage ?: "aesDecodeToByteArrayERROR")
null
}
}
/**
* AES 解码为 String
* @param str 传入的AES加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
fun aesDecodeToString(
str: String, key: String, transformation: String, iv: String
): String? {
return aesDecodeToByteArray(str, key, transformation, iv)?.let { String(it) }
}
/**
* 已经base64的AES 解码为 ByteArray
* @param str 传入的AES Base64加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/ */
fun log(msg: String): String {
fun aesBase64DecodeToByteArray( getSource()?.let {
str: String, key: String, transformation: String, iv: String Debug.log(it.getKey(), msg)
): ByteArray? { } ?: Debug.log(msg)
return try { if (App.isDebug()) {
EncoderUtils.decryptBase64AES( Log.d(TAG + "-" + getSource()?.getKey(), msg)
str.encodeToByteArray(),
key.encodeToByteArray(),
transformation,
iv.encodeToByteArray()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
log(e.localizedMessage ?: "aesDecodeToByteArrayERROR")
null
} }
return msg
} }
/** /**
* 已经base64的AES 解码为 String * 输出对象类型
* @param str 传入的AES Base64加密的数据
* @param key AES 解密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/ */
fun logType(any: Any?) {
fun aesBase64DecodeToString( if (any == null) {
str: String, key: String, transformation: String, iv: String log("null")
): String? { } else {
return aesBase64DecodeToByteArray(str, key, transformation, iv)?.let { String(it) } log(any.javaClass.name)
}
/**
* 加密aes为ByteArray
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
fun aesEncodeToByteArray(
data: String, key: String, transformation: String, iv: String
): ByteArray? {
return try {
EncoderUtils.encryptAES(
data.encodeToByteArray(),
key = key.encodeToByteArray(),
transformation,
iv.encodeToByteArray()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
log(e.localizedMessage ?: "aesEncodeToByteArrayERROR")
null
} }
} }
/** /**
* 加密aes为String * 生成UUID
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/ */
fun aesEncodeToString( fun randomUUID(): String {
data: String, key: String, transformation: String, iv: String return UUID.randomUUID().toString()
): String? {
return aesEncodeToByteArray(data, key, transformation, iv)?.let { String(it) }
}
/**
* 加密aes后Base64化的ByteArray
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
fun aesEncodeToBase64ByteArray(
data: String, key: String, transformation: String, iv: String
): ByteArray? {
return try {
EncoderUtils.encryptAES2Base64(
data.encodeToByteArray(),
key.encodeToByteArray(),
transformation,
iv.encodeToByteArray()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
log(e.localizedMessage ?: "aesEncodeToBase64ByteArrayERROR")
null
}
}
/**
* 加密aes后Base64化的String
* @param data 传入的原始数据
* @param key AES加密的key
* @param transformation AES加密的方式
* @param iv ECB模式的偏移向量
*/
fun aesEncodeToBase64String(
data: String, key: String, transformation: String, iv: String
): String? {
return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) }
} }
fun android(): String { fun android(): String {

@ -361,7 +361,7 @@ public class BookDetailedActivity extends BaseActivity<ActivityBookDetailBinding
*/ */
protected void showMoreDesc() { protected void showMoreDesc() {
if (binding.ic.bookDetailTvDesc.getMaxLines() == 5) if (binding.ic.bookDetailTvDesc.getMaxLines() == 5)
binding.ic.bookDetailTvDesc.setMaxLines(15); binding.ic.bookDetailTvDesc.setMaxLines(Integer.MAX_VALUE);
else else
binding.ic.bookDetailTvDesc.setMaxLines(5); binding.ic.bookDetailTvDesc.setMaxLines(5);
} }
@ -426,7 +426,7 @@ public class BookDetailedActivity extends BaseActivity<ActivityBookDetailBinding
BookSource source = BookSourceManager.getBookSourceByStr(mBook.getSource()); BookSource source = BookSourceManager.getBookSourceByStr(mBook.getSource());
binding.ih.bookDetailSource.setText(String.format("书源:%s", source.getSourceName())); binding.ih.bookDetailSource.setText(String.format("书源:%s", source.getSourceName()));
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source); ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source);
if ((rc instanceof BookInfoCrawler && StringHelper.isEmpty(mBook.getImgUrl()))) { if (rc instanceof BookInfoCrawler && !isCollected) {
binding.pbLoading.setVisibility(View.VISIBLE); binding.pbLoading.setVisibility(View.VISIBLE);
BookInfoCrawler bic = (BookInfoCrawler) rc; BookInfoCrawler bic = (BookInfoCrawler) rc;
BookApi.getBookInfo(mBook, bic).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<Book>() { BookApi.getBookInfo(mBook, bic).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<Book>() {

@ -67,7 +67,6 @@ public class BookMarkAdapter extends ArrayAdapter<BookMark> {
viewHolder = new ViewHolder(); viewHolder = new ViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(mResourceId,null); convertView = LayoutInflater.from(getContext()).inflate(mResourceId,null);
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_chapter_title); viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_chapter_title);
viewHolder.vLine = (View) convertView.findViewById(R.id.v_line);
convertView.setTag(viewHolder); convertView.setTag(viewHolder);
}else { }else {
viewHolder = (ViewHolder) convertView.getTag(); viewHolder = (ViewHolder) convertView.getTag();
@ -78,14 +77,7 @@ public class BookMarkAdapter extends ArrayAdapter<BookMark> {
private void initView(int postion,final ViewHolder viewHolder){ private void initView(int postion,final ViewHolder viewHolder){
final BookMark bookMark = getItem(postion); final BookMark bookMark = getItem(postion);
assert bookMark != null;
viewHolder.tvTitle.setText(String.format("%s[%s]", bookMark.getTitle(), bookMark.getBookMarkReadPosition() + 1)); viewHolder.tvTitle.setText(String.format("%s[%s]", bookMark.getTitle(), bookMark.getBookMarkReadPosition() + 1));
if (ChapterService.isChapterCached(bookMark)){
viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(), R.drawable.selector_category_load),null,null,null);
} else {
viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(),R.drawable.selector_category_unload),null,null,null);
}
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.textSecondary));
/*if (!setting.isDayStyle()) { /*if (!setting.isDayStyle()) {
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word)); viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word));
viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line)); viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line));
@ -134,9 +126,7 @@ public class BookMarkAdapter extends ArrayAdapter<BookMark> {
} }
class ViewHolder{ class ViewHolder{
TextView tvTitle; TextView tvTitle;
View vLine;
} }
} }

@ -19,12 +19,15 @@
package xyz.fycz.myreader.ui.adapter; package xyz.fycz.myreader.ui.adapter;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Filter; import android.widget.Filter;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@ -37,7 +40,6 @@ import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.service.ChapterService; import xyz.fycz.myreader.greendao.service.ChapterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
@ -72,8 +74,9 @@ public class ChapterTitleAdapter extends ArrayAdapter<Chapter> {
if (convertView == null) { if (convertView == null) {
viewHolder = new ViewHolder(); viewHolder = new ViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null); convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null);
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_chapter_title); viewHolder.tvTitle = convertView.findViewById(R.id.tv_chapter_title);
viewHolder.vLine = (View) convertView.findViewById(R.id.v_line); viewHolder.tvTag = convertView.findViewById(R.id.tv_tag);
viewHolder.ivIcon = convertView.findViewById(R.id.iv_icon);
convertView.setTag(viewHolder); convertView.setTag(viewHolder);
} else { } else {
viewHolder = (ViewHolder) convertView.getTag(); viewHolder = (ViewHolder) convertView.getTag();
@ -86,21 +89,30 @@ public class ChapterTitleAdapter extends ArrayAdapter<Chapter> {
final Chapter chapter = getItem(postion); final Chapter chapter = getItem(postion);
// viewHolder.tvTitle.setText("【" + chapter.getTitle() + "】"); // viewHolder.tvTitle.setText("【" + chapter.getTitle() + "】");
viewHolder.tvTitle.setText(chapter.getTitle()); viewHolder.tvTitle.setText(chapter.getTitle());
//viewHolder.ivIcon.setImageResource(R.drawable.ic_cloud_download);
if (ChapterService.isChapterCached(chapter) || chapter.getEnd() > 0) { if (ChapterService.isChapterCached(chapter) || chapter.getEnd() > 0) {
viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(),R.drawable.selector_category_load), null, null, null); viewHolder.ivIcon.setVisibility(View.INVISIBLE);
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.textPrimary));
} else { } else {
viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(),R.drawable.selector_category_unload), null, null, null); viewHolder.ivIcon.setVisibility(View.VISIBLE);
}
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.textSecondary)); viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.textSecondary));
}
/*if (!setting.isDayStyle()) { /*if (!setting.isDayStyle()) {
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word)); viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word));
viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line)); viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line));
}else { }else {
viewHolder.tvTitle.setTextColor(getContext().getColor(R.color.title_black)); viewHolder.tvTitle.setTextColor(getContext().getColor(R.color.title_black));
}*/ }*/
if (TextUtils.isEmpty(chapter.getUpdateTime())) {
viewHolder.tvTag.setVisibility(View.GONE);
} else {
viewHolder.tvTag.setText(chapter.getUpdateTime());
viewHolder.tvTag.setVisibility(View.VISIBLE);
}
if (chapter.getNumber() == mBook.getHisttoryChapterNum()) { if (chapter.getNumber() == mBook.getHisttoryChapterNum()) {
viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.colorAccent)); viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.colorAccent));
/*viewHolder.ivIcon.setImageResource(R.drawable.ic_check);
viewHolder.ivIcon.setVisibility(View.VISIBLE);*/
} }
} }
@ -152,7 +164,8 @@ public class ChapterTitleAdapter extends ArrayAdapter<Chapter> {
class ViewHolder { class ViewHolder {
TextView tvTitle; TextView tvTitle;
View vLine; TextView tvTag;
ImageView ivIcon;
} }

@ -18,6 +18,8 @@
package xyz.fycz.myreader.ui.adapter.holder; package xyz.fycz.myreader.ui.adapter.holder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@ -34,23 +36,21 @@ import xyz.fycz.myreader.greendao.service.ChapterService;
*/ */
public class CatalogHolder extends ViewHolderImpl<Chapter> { public class CatalogHolder extends ViewHolderImpl<Chapter> {
private TextView tvTitle; private TextView tvTitle;
private ImageView ivIcon;
@Override @Override
protected int getItemLayoutId() { protected int getItemLayoutId() {
return R.layout.listview_chapter_title_item; return R.layout.item_chapter;
} }
@Override @Override
public void initView() { public void initView() {
tvTitle = findById(R.id.tv_chapter_title); tvTitle = findById(R.id.tv_chapter_title);
ivIcon = findById(R.id.iv_icon);
} }
@Override @Override
public void onBind(RecyclerView.ViewHolder holder, Chapter data, int pos) { public void onBind(RecyclerView.ViewHolder holder, Chapter data, int pos) {
if (ChapterService.isChapterCached(data) || data.getEnd() > 0) {
tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(),R.drawable.selector_category_load), null, null, null);
} else {
tvTitle.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getContext(),R.drawable.selector_category_unload), null, null, null);
}
tvTitle.setText(data.getTitle()); tvTitle.setText(data.getTitle());
ivIcon.setVisibility(View.GONE);
} }
} }

@ -49,6 +49,7 @@ import xyz.fycz.myreader.webapi.BookApi;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler;
import xyz.fycz.myreader.widget.CoverImageView; import xyz.fycz.myreader.widget.CoverImageView;
/** /**
@ -102,19 +103,23 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
initTagList(data); initTagList(data);
if (!StringHelper.isEmpty(data.getNewestChapterTitle())) { if (!StringHelper.isEmpty(data.getNewestChapterTitle())) {
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, data.getNewestChapterTitle())); tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, data.getNewestChapterTitle()));
tvNewestChapter.setVisibility(View.VISIBLE);
} else { } else {
data.setNewestChapterTitle(""); data.setNewestChapterTitle("");
tvNewestChapter.setText(""); tvNewestChapter.setText("");
tvNewestChapter.setVisibility(View.GONE);
} }
if (!StringHelper.isEmpty(data.getDesc())) { if (!StringHelper.isEmpty(data.getDesc())) {
tvDesc.setText(String.format("简介:%s", data.getDesc())); tvDesc.setText(String.format("简介:%s", data.getDesc()));
tvDesc.setVisibility(View.VISIBLE);
} else { } else {
data.setDesc(""); data.setDesc("");
tvDesc.setText(""); tvDesc.setText("");
tvDesc.setVisibility(View.GONE);
} }
if (!StringHelper.isEmpty(source.getSourceName()) && !"未知书源".equals(source.getSourceName())) if (!StringHelper.isEmpty(source.getSourceName()) && !"未知书源".equals(source.getSourceName()))
tvSource.setText(String.format("书源:%s", source.getSourceName())); tvSource.setText(String.format("书源:%s", source.getSourceName()));
if (needGetInfo(data) && rc instanceof BookInfoCrawler) { if (rc instanceof BookInfoCrawler && needGetInfo(data)) {
Log.i(data.getName(), "initOtherInfo"); Log.i(data.getName(), "initOtherInfo");
BookInfoCrawler bic = (BookInfoCrawler) rc; BookInfoCrawler bic = (BookInfoCrawler) rc;
BookApi.getBookInfo(data, bic).compose(RxUtils::toSimpleSingle) BookApi.getBookInfo(data, bic).compose(RxUtils::toSimpleSingle)
@ -132,9 +137,15 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
//简介 //简介
if (StringHelper.isEmpty(tvDesc.getText().toString())) { if (StringHelper.isEmpty(tvDesc.getText().toString())) {
tvDesc.setText(String.format("简介:%s", book.getDesc())); tvDesc.setText(String.format("简介:%s", book.getDesc()));
tvNewestChapter.setVisibility(View.VISIBLE);
} else {
tvNewestChapter.setVisibility(View.GONE);
} }
if (StringHelper.isEmpty(tvNewestChapter.getText().toString())) { if (StringHelper.isEmpty(tvNewestChapter.getText().toString())) {
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle())); tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
tvNewestChapter.setVisibility(View.VISIBLE);
} else {
tvNewestChapter.setVisibility(View.GONE);
} }
if (!StringHelper.isEmpty(book.getAuthor())) { if (!StringHelper.isEmpty(book.getAuthor())) {
tvAuthor.setText(book.getAuthor()); tvAuthor.setText(book.getAuthor());
@ -163,6 +174,7 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
tflBookTag.setAdapter(new BookTagAdapter(getContext(), tagList, 11)); tflBookTag.setAdapter(new BookTagAdapter(getContext(), tagList, 11));
} }
} }
private boolean needGetInfo(Book bookBean) { private boolean needGetInfo(Book bookBean) {
if (StringHelper.isEmpty(bookBean.getAuthor())) return true; if (StringHelper.isEmpty(bookBean.getAuthor())) return true;
if (StringHelper.isEmpty(bookBean.getType())) return true; if (StringHelper.isEmpty(bookBean.getType())) return true;
@ -170,6 +182,7 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
if (StringHelper.isEmpty(bookBean.getNewestChapterTitle())) return true; if (StringHelper.isEmpty(bookBean.getNewestChapterTitle())) return true;
return StringHelper.isEmpty(bookBean.getImgUrl()); return StringHelper.isEmpty(bookBean.getImgUrl());
} }
/** /**
* Here is the key method to apply the animation * Here is the key method to apply the animation
*/ */

@ -37,6 +37,7 @@ import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.hjq.permissions.OnPermissionCallback;
import com.kongzue.dialogx.dialogs.BottomMenu; import com.kongzue.dialogx.dialogs.BottomMenu;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -45,6 +46,7 @@ import java.io.File;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe; import io.reactivex.SingleOnSubscribe;
@ -84,6 +86,7 @@ import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.AdUtils; import xyz.fycz.myreader.util.utils.AdUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils; import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.RxUtils; import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.util.utils.StoragePermissionUtils;
/** /**
* @author fengyue * @author fengyue
@ -205,7 +208,7 @@ public class MineFragment extends BaseFragment {
@Override @Override
protected void initClick() { protected void initClick() {
super.initClick(); super.initClick();
binding.mineRlUser.setOnClickListener(v -> { binding.mineRlUser.setOnClickListener(v -> StoragePermissionUtils.request(this, (permissions, all) -> {
if (isLogin) { if (isLogin) {
Intent intent = new Intent(getActivity(), UserInfoActivity.class); Intent intent = new Intent(getActivity(), UserInfoActivity.class);
startActivityForResult(intent, APPCONST.REQUEST_LOGOUT); startActivityForResult(intent, APPCONST.REQUEST_LOGOUT);
@ -213,7 +216,7 @@ public class MineFragment extends BaseFragment {
Intent intent = new Intent(getActivity(), LoginActivity.class); Intent intent = new Intent(getActivity(), LoginActivity.class);
getActivity().startActivityForResult(intent, APPCONST.REQUEST_LOGIN); getActivity().startActivityForResult(intent, APPCONST.REQUEST_LOGIN);
} }
}); }));
binding.mineRlSyn.setOnClickListener(v -> { binding.mineRlSyn.setOnClickListener(v -> {
if (!isLogin) { if (!isLogin) {
ToastUtils.showWarring("请先登录!"); ToastUtils.showWarring("请先登录!");

@ -21,7 +21,6 @@ package xyz.fycz.myreader.ui.presenter;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import xyz.fycz.myreader.R; import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BasePresenter; import xyz.fycz.myreader.base.BasePresenter;
import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.Book;
@ -74,7 +73,7 @@ public class BookMarkPresenter implements BasePresenter {
private void initBookMarkList() { private void initBookMarkList() {
mBookMarks = (ArrayList<BookMark>) mBookMarkService.findBookAllBookMarkByBookId(mBook.getId()); mBookMarks = (ArrayList<BookMark>) mBookMarkService.findBookAllBookMarkByBookId(mBook.getId());
mBookMarkAdapter = new BookMarkAdapter(mBookMarkFragment.getActivity(), R.layout.listview_chapter_title_item, mBookMarks); mBookMarkAdapter = new BookMarkAdapter(mBookMarkFragment.getActivity(), R.layout.item_chapter, mBookMarks);
mBookMarkFragment.getLvBookmarkList().setAdapter(mBookMarkAdapter); mBookMarkFragment.getLvBookmarkList().setAdapter(mBookMarkAdapter);
} }

@ -24,7 +24,6 @@ import android.view.View;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import io.reactivex.disposables.Disposable;
import xyz.fycz.myreader.R; import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BasePresenter; import xyz.fycz.myreader.base.BasePresenter;
@ -137,7 +136,7 @@ public class CatalogPresenter implements BasePresenter {
//设置布局管理器 //设置布局管理器
int curChapterPosition; int curChapterPosition;
curChapterPosition = mBook.getHisttoryChapterNum(); curChapterPosition = mBook.getHisttoryChapterNum();
mChapterTitleAdapter = new ChapterTitleAdapter(mCatalogFragment.getContext(), R.layout.listview_chapter_title_item, mChapters, mBook); mChapterTitleAdapter = new ChapterTitleAdapter(mCatalogFragment.getContext(), R.layout.item_chapter, mChapters, mBook);
mCatalogFragment.getLvChapterList().setAdapter(mChapterTitleAdapter); mCatalogFragment.getLvChapterList().setAdapter(mChapterTitleAdapter);
mCatalogFragment.getLvChapterList().setSelection(curChapterPosition); mCatalogFragment.getLvChapterList().setSelection(curChapterPosition);
} }

@ -52,7 +52,7 @@ class ACache private constructor(cacheDir: File, max_size: Long, max_count: Int)
@JvmOverloads @JvmOverloads
fun get( fun get(
ctx: Context, ctx: Context = App.getmContext(),
cacheName: String = "ACache", cacheName: String = "ACache",
maxSize: Long = MAX_SIZE.toLong(), maxSize: Long = MAX_SIZE.toLong(),
maxCount: Int = MAX_COUNT, maxCount: Int = MAX_COUNT,

@ -214,7 +214,9 @@ public class OkHttpUtils {
} }
public static String getBakUpdateInfo() throws IOException { public static String getBakUpdateInfo() throws IOException {
return OkHttpUtils.getHtml("https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/FYReader-Update/" + return OkHttpUtils.getHtml(
//"https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/FYReader-Update/" +
"http://101.43.83.105:3000/fengyue/FYReader-Res/raw/branch/main/Plugin/" +
(App.isDebug() ? "debug" : "release") + (App.isDebug() ? "debug" : "release") +
"/content.txt"); "/content.txt");
} }

@ -58,24 +58,27 @@ object PluginUtils {
val oldConfig = GSON.fromJsonObject<PluginConfig>( val oldConfig = GSON.fromJsonObject<PluginConfig>(
SharedPreUtils.getInstance().getString("pluginConfig") SharedPreUtils.getInstance().getString("pluginConfig")
) ?: PluginConfig("dynamic.dex", 100) ) ?: PluginConfig("dynamic.dex", 100)
launch { loadAppLoader(App.getmContext(), config) } try {
val configJson = getProxyClient().newCallResponseBody { val configJson = getProxyClient().newCallResponseBody(5) {
url(pluginConfigUrl) url(pluginConfigUrl)
}.text() }.text()
config = GSON.fromJsonObject<PluginConfig>(configJson) config = GSON.fromJsonObject<PluginConfig>(configJson)
if (config != null) {
if (config!!.versionCode > oldConfig.versionCode) { if (config!!.versionCode > oldConfig.versionCode) {
downloadPlugin(config!!) downloadPlugin(config!!)
SharedPreUtils.getInstance().putString("pluginConfig", configJson)
} }
} else { } catch (e: Exception) {
config = oldConfig config = oldConfig
e.printStackTrace()
errorMsg = e.stackTraceToString()
} }
kotlin.runCatching {
if (config!!.md5.lowercase(Locale.getDefault()) if (config!!.md5.lowercase(Locale.getDefault())
!= getPluginMD5(config!!)?.lowercase(Locale.getDefault()) != getPluginMD5(config!!)?.lowercase(Locale.getDefault())
) { ) {
downloadPlugin(config!!) downloadPlugin(config!!)
} }
}
Log.d(TAG, config!!.toString())
}.onSuccess { }.onSuccess {
loadAppLoader(App.getmContext(), config) loadAppLoader(App.getmContext(), config)
} }
@ -117,6 +120,7 @@ object PluginUtils {
hasLoad = true hasLoad = true
loadSuccess = true loadSuccess = true
} }
SharedPreUtils.getInstance().putString("pluginConfig", GSON.toJson(config))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
errorMsg = e.stackTraceToString() errorMsg = e.stackTraceToString()

@ -23,6 +23,7 @@ package xyz.fycz.myreader.util.utils
import android.icu.text.Collator import android.icu.text.Collator
import android.icu.util.ULocale import android.icu.util.ULocale
import android.net.Uri import android.net.Uri
import android.text.Editable
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -30,14 +31,19 @@ fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true
fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)
fun String.parseToUri(): Uri { fun String.parseToUri(): Uri {
return if (isContentScheme()) { return if (isUri()) Uri.parse(this) else {
Uri.parse(this)
} else {
Uri.fromFile(File(this)) Uri.fromFile(File(this))
} }
} }
fun String?.isUri(): Boolean {
this ?: return false
return this.startsWith("file://", true) || isContentScheme()
}
fun String?.isAbsUrl() = fun String?.isAbsUrl() =
this?.let { this?.let {
it.startsWith("http://", true) || it.startsWith("https://", true) it.startsWith("http://", true) || it.startsWith("https://", true)
@ -65,8 +71,22 @@ fun String?.isJsonArray(): Boolean =
str.startsWith("[") && str.endsWith("]") str.startsWith("[") && str.endsWith("]")
} ?: false } ?: false
fun String.splitNotBlank(vararg delimiter: String): Array<String> = run { fun String?.isXml(): Boolean =
this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() this?.run {
val str = this.trim()
str.startsWith("<") && str.endsWith(">")
} ?: false
fun String?.isTrue(nullIsTrue: Boolean = false): Boolean {
if (this.isNullOrBlank() || this == "null") {
return nullIsTrue
}
return !this.trim().matches("(?i)^(false|no|not|0)$".toRegex())
}
fun String.splitNotBlank(vararg delimiter: String, limit: Int = 0): Array<String> = run {
this.split(*delimiter, limit = limit).map { it.trim() }.filterNot { it.isBlank() }
.toTypedArray()
} }
fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array<String> = run { fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array<String> = run {
@ -97,3 +117,4 @@ fun String.toStringArray(): Array<String> {
} }
} }

@ -18,11 +18,95 @@
package xyz.fycz.myreader.webapi.crawler.source.find package xyz.fycz.myreader.webapi.crawler.source.find
import io.reactivex.Observable
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.entity.FindKind
import xyz.fycz.myreader.entity.thirdsource.source3.ExploreKind3
import xyz.fycz.myreader.greendao.entity.rule.BookSource import xyz.fycz.myreader.greendao.entity.rule.BookSource
import xyz.fycz.myreader.util.utils.ACache
import xyz.fycz.myreader.util.utils.GSON
import xyz.fycz.myreader.util.utils.fromJsonArray
import xyz.fycz.myreader.util.utils.isJsonArray
/** /**
* @author fengyue * @author fengyue
* @date 2022/1/20 15:33 * @date 2022/1/20 15:33
*/ */
class Third3FindCrawler(source: BookSource) : ThirdFindCrawler(source) { class Third3FindCrawler(source: BookSource) : ThirdFindCrawler(source) {
override fun initData(): Observable<Boolean> {
return Observable.create { emitter ->
val exploreUrl = source.findRule.url
if (exploreUrl.isNullOrBlank()) {
emitter.onNext(false)
emitter.onComplete()
return@create
}
val kinds = arrayListOf<ExploreKind3>()
var ruleStr = exploreUrl
kotlin.runCatching {
if (exploreUrl.startsWith("<js>", false)
|| exploreUrl.startsWith("@js:", false)
) {
val aCache = ACache.get(cacheName = "explore")
ruleStr = aCache.getAsString(source.sourceUrl) ?: ""
if (ruleStr.isBlank()) {
val jsStr = if (exploreUrl.startsWith("@")) {
exploreUrl.substring(4)
} else {
exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
}
ruleStr = source.evalJS(jsStr).toString().trim()
aCache.put(source.sourceUrl, ruleStr)
}
}
if (ruleStr.isJsonArray()) {
GSON.fromJsonArray<ExploreKind3>(ruleStr)?.let {
kinds.addAll(it)
}
} else {
ruleStr.split("(&&|\n)+".toRegex()).forEach { kindStr ->
val kindCfg = kindStr.split("::")
kinds.add(ExploreKind3(kindCfg.getOrNull(0) ?: "", kindCfg.getOrNull(1)))
}
}
var children = arrayListOf<FindKind>()
var groupName = name
var nameCount = 0
kinds.forEach {
if (it.title.isBlank()) {
if (children.size > 0) {
nameCount++
kindsMap[groupName] = children
children = arrayListOf()
}
groupName = "$name[$nameCount]"
} else if (it.url.isNullOrBlank()) {
if (children.size > 0) {
kindsMap[groupName] = children
children = arrayListOf()
}
groupName = it.title.replace("\\s".toRegex(), "")
} else {
val findKindBean = FindKind().apply {
tag = source.sourceUrl
name = it.title.replace("\\s".toRegex(), "")
url = it.url
}
children.add(findKindBean)
}
}
if (children.size > 0) {
kindsMap[groupName] = children
}
emitter.onNext(true)
}.onFailure {
emitter.onNext(false)
kinds.add(ExploreKind3("ERROR:${it.localizedMessage}", it.stackTraceToString()))
if (App.isDebug()) {
it.printStackTrace()
}
}
emitter.onComplete()
}
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of FYReader.
~ FYReader is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FYReader is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with FYReader. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 - 2022 fengyuecanzhu
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:width="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M22.5,40.3180195 L22.5,26.5 C22.5,19.5964406 28.0964406,14 35,14 C41.0751322,14 46,18.9248678 46,25 C46,31.0751322 41.0751322,36 35,36 L28.5,36 L28.5,33 L35,33 C39.418278,33 43,29.418278 43,25 C43,20.581722 39.418278,17 35,17 C29.7532949,17 25.5,21.2532949 25.5,26.5 L25.5,40.3180195 L27.9393398,37.8786797 L30.0606602,40 L24,46.0606602 L17.9393398,40 L20.0606602,37.8786797 L22.5,40.3180195 Z"
android:strokeWidth="1.0" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M33.8258342,11.0485401 C32.0120952,7.45657788 28.2870652,5 24,5 C18.0506258,5 13.1836471,9.7309997 13.0050811,15.6619804 L12.9676728,16.9044801 L11.7398391,17.098463 C7.8813734,17.7080539 5,21.0505922 5,25 C5,29.418278 8.581722,33 13,33 L19.5,33 L19.5,36 L13,36 C6.92486775,36 2,31.0751322 2,25 C2,19.9793459 5.38459981,15.6735816 10.0917391,14.3885641 C10.8920106,7.40161656 16.8277534,2 24,2 C30.0211876,2 35.1709114,5.80695411 37.1422625,11.1628739 C36.443846,11.0556302 35.7284196,11 35,11 C34.6045681,11 34.2129653,11.0163942 33.8258342,11.0485401 L33.8258342,11.0485401 Z"
android:strokeWidth="1.0" />
</vector>

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of FYReader.
~ FYReader is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FYReader is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with FYReader. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 - 2022 fengyuecanzhu
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 没有焦点时字体颜色 -->
<item
android:state_selected="false"
android:drawable="@drawable/ic_item_category_download"/>
</selector>

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of FYReader.
~ FYReader is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FYReader is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with FYReader. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 - 2022 fengyuecanzhu
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 没有焦点时字体颜色 -->
<item
android:state_selected="false"
android:drawable="@drawable/ic_item_category_normal"/>
</selector>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of FYReader.
~ FYReader is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FYReader is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with FYReader. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 - 2022 fengyuecanzhu
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tv_chapter_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="13dp">
<TextView
android:id="@+id/tv_chapter_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/textSecondary"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@id/tv_tag"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_icon"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_tag"
android:layout_marginTop="2dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/textAssist"
android:textSize="@dimen/text_default_size"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_icon"
app:layout_constraintTop_toBottomOf="@+id/tv_chapter_title" />
<ImageView
android:id="@+id/iv_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@string/success"
android:padding="3.5dp"
android:src="@drawable/ic_cloud_download"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/textAssist" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/v_line"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_gravity="bottom"
android:background="@color/colorDivider"
android:layerType="software" />
</LinearLayout>

@ -64,7 +64,7 @@
android:id="@+id/book_detail_tv_catalog" android:id="@+id/book_detail_tv_catalog"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="15dp" android:padding="12dp"
android:text="最新章节" android:text="最新章节"
android:textColor="@color/textPrimary" android:textColor="@color/textPrimary"
android:textSize="15sp" /> android:textSize="15sp" />
@ -73,8 +73,8 @@
android:id="@+id/book_detail_tv_catalog_more" android:id="@+id/book_detail_tv_catalog_more"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="right" android:gravity="end"
android:padding="15dp" android:padding="12dp"
android:text="更多" android:text="更多"
android:textColor="@color/textSecondary" android:textColor="@color/textSecondary"
android:textSize="15sp" /> android:textSize="15sp" />
@ -84,7 +84,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/book_detail_tv_catalog" android:layout_below="@+id/book_detail_tv_catalog"
android:foregroundGravity="center" /> android:foregroundGravity="center"
android:paddingHorizontal="4dp" />
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of FYReader.
~ FYReader is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FYReader is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with FYReader. If not, see <https://www.gnu.org/licenses/>.
~
~ Copyright (C) 2020 - 2022 fengyuecanzhu
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_chapter_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:drawableLeft="@drawable/selector_category_unload"
android:textColor="@color/textSecondary"
android:text="chapter"
android:drawablePadding="10dp"
android:padding="13dp"
android:textSize="15sp" />
<View
android:id="@+id/v_line"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_gravity="bottom"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="@color/colorDivider"
android:layerType="software" />
</LinearLayout>

@ -17,5 +17,5 @@
# #
#Fri Jun 18 21:45:31 CST 2021 #Fri Jun 18 21:45:31 CST 2021
VERSION_CODE=250 VERSION_CODE=251
CREATE_RELEASE=true CREATE_RELEASE=true

@ -152,7 +152,7 @@ dependencies {
compileOnly('org.apache.commons:commons-text:1.9') compileOnly('org.apache.commons:commons-text:1.9')
//https://github.com/fengyuecanzhu/Maple //https://github.com/fengyuecanzhu/Maple
compileOnly("me.fycz.maple:maple:1.9") compileOnly("me.fycz.maple:maple:2.0")
compileOnly project(":app") compileOnly project(":app")
compileOnly project(":DialogX") compileOnly project(":DialogX")
} }

@ -57,6 +57,7 @@ class AppLoadImpl : IAppLoader {
App246Fix3::class.java, App246Fix3::class.java,
App246Fix4::class.java, App246Fix4::class.java,
App246Fix5::class.java, App246Fix5::class.java,
App250Fix::class.java,
) )
override fun onLoad(appParam: AppParam) { override fun onLoad(appParam: AppParam) {

@ -39,7 +39,7 @@ import xyz.fycz.myreader.util.utils.ScreenUtils
* @author fengyue * @author fengyue
* @date 2022/6/3 15:34 * @date 2022/6/3 15:34
*/ */
@AppFix([243, 244, 245, 246], ["[设置-缓存设置]新增清除广告文件"], "2022-06-03") @AppFix([243, 244, 245, 246, 250], ["[设置-缓存设置]新增清除广告文件"], "2022-06-03")
class App246Fix : AppFixHandle { class App246Fix : AppFixHandle {
override fun onFix(key: String): BooleanArray { override fun onFix(key: String): BooleanArray {

@ -200,7 +200,8 @@ class App246Fix5 : AppFixHandle {
@Throws(IOException::class) @Throws(IOException::class)
fun getBakUpdateInfo(): String { fun getBakUpdateInfo(): String {
return OkHttpUtils.getHtml( return OkHttpUtils.getHtml(
"https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/FYReader-Update/" + //"https://gitlab.com/fengyuecanzhu/fyreader-resource/-/raw/main/FYReader-Update/" +
"http://101.43.83.105:3000/fengyue/FYReader-Res/raw/branch/main/Plugin/" +
(if (App.isDebug()) "debug" else "release") + (if (App.isDebug()) "debug" else "release") +
"/content.txt" "/content.txt"
) )

@ -0,0 +1,92 @@
/*
* This file is part of FYReader.
* FYReader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FYReader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FYReader. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 - 2022 fengyuecanzhu
*/
package xyz.fycz.dynamic.fix
import android.widget.TextView
import androidx.viewbinding.ViewBinding
import me.fycz.maple.MapleBridge
import me.fycz.maple.MapleUtils
import me.fycz.maple.MethodReplacement
import xyz.fycz.myreader.greendao.DbManager
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.greendao.gen.ChapterDao
import xyz.fycz.myreader.greendao.service.ChapterService
import xyz.fycz.myreader.ui.activity.BookDetailedActivity
/**
* @author fengyue
* @date 2022/8/11 16:44
*/
@AppFix(
[243, 244, 245, 246, 250],
["[书籍详情界面]取消书籍简介展开时最大行数限制(无法显示完全)",
"修复从数据库中读取章节时部分数据项缺失的bug"],
"2022-08-11"
)
class App250Fix : AppFixHandle {
override fun onFix(key: String): BooleanArray {
return handleFix(
key,
"showMoreDesc" to { fxShowMoreDesc() },
"findBookAllChapterByBookId" to { fxFindBookAllChapterByBookId() },
)
}
private fun fxFindBookAllChapterByBookId() {
MapleUtils.findAndHookMethod(
ChapterService::class.java,
"findBookAllChapterByBookId",
String::class.java,
object : MethodReplacement() {
override fun replaceHookedMethod(param: MapleBridge.MethodHookParam): Any {
val bookId = param.args[0] as String?
if (bookId.isNullOrBlank()) {
return emptyList<Chapter>()
}
return DbManager.getDaoSession().chapterDao
.queryBuilder()
.where(ChapterDao.Properties.BookId.eq(bookId))
.orderAsc(ChapterDao.Properties.Number)
.list()
}
}
)
}
fun fxShowMoreDesc() {
MapleUtils.findAndHookMethod(
BookDetailedActivity::class.java,
"showMoreDesc",
object : MethodReplacement() {
override fun replaceHookedMethod(param: MapleBridge.MethodHookParam) {
val binding =
MapleUtils.getObjectField(param.thisObject, "binding") as ViewBinding
val icBinding = MapleUtils.getObjectField(binding, "ic") as ViewBinding
val bookDetailTvDesc =
MapleUtils.getObjectField(icBinding, "bookDetailTvDesc") as TextView
if (bookDetailTvDesc.maxLines == 5) {
bookDetailTvDesc.maxLines = Int.MAX_VALUE
} else {
bookDetailTvDesc.maxLines = 5
}
}
}
)
}
}
Loading…
Cancel
Save