diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 256c54e..42bd19f 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -19,8 +19,8 @@ diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index e4f1bec..ba3975b 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/modules.xml b/.idea/modules.xml index c9f6d5e..669fcc4 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,8 @@ - - + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 56782ca..8bc84c2 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,6 +1,7 @@ + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9..394b624 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2020 fengyuecanzhu Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index a9a1b54..0d0ce49 100644 --- a/README.md +++ b/README.md @@ -200,3 +200,20 @@ ![Image](./img/10.png)![Image](./img/5.png) ![Image](./img/6.png)![Image](./img/7.png) ![Image](./img/8.png)![Image](./img/9.png) + +## LICENSE +``` + Copyright 2020 fengyuecanzhu + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` diff --git a/app/build.gradle b/app/build.gradle index 9ca848e..0134962 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,6 @@ dependencies { implementation 'com.google.android.material:material:1.1.0' - implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0' //Scroller implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5' @@ -154,10 +153,25 @@ dependencies { //ImmersionBar implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + //简繁转换 + implementation 'com.luhuiguo:chinese-utils:1.0' + + //颜色选择 + implementation 'com.jaredrummler:colorpicker:1.1.0' + + //二维码 + implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7' + //Material Dialogs + /*def dialog_version = '3.3.0' + implementation "com.afollestad.material-dialogs:core:$dialog_version" + implementation "com.afollestad.material-dialogs:input:$dialog_version" + implementation "com.afollestad.material-dialogs:files:$dialog_version" + implementation "com.afollestad.material-dialogs:bottomsheets:$dialog_version"*/ + } greendao { - schemaVersion 16 + schemaVersion 18 daoPackage 'xyz.fycz.myreader.greendao.gen' // targetGenDir 'src/main/java' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b635103..3249073 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -89,6 +89,10 @@ + + + + @@ -98,6 +102,7 @@ + diff --git a/app/src/main/assets/bg/p01.jpg b/app/src/main/assets/bg/p01.jpg new file mode 100644 index 0000000..d353f98 Binary files /dev/null and b/app/src/main/assets/bg/p01.jpg differ diff --git a/app/src/main/assets/bg/p02.jpg b/app/src/main/assets/bg/p02.jpg new file mode 100644 index 0000000..5c1f1ec Binary files /dev/null and b/app/src/main/assets/bg/p02.jpg differ diff --git a/app/src/main/assets/bg/p03.jpg b/app/src/main/assets/bg/p03.jpg new file mode 100644 index 0000000..39138f6 Binary files /dev/null and b/app/src/main/assets/bg/p03.jpg differ diff --git a/app/src/main/assets/bg/p04.jpg b/app/src/main/assets/bg/p04.jpg new file mode 100644 index 0000000..04f0a6b Binary files /dev/null and b/app/src/main/assets/bg/p04.jpg differ diff --git a/app/src/main/assets/bg/p05.jpg b/app/src/main/assets/bg/p05.jpg new file mode 100644 index 0000000..bc7ebc4 Binary files /dev/null and b/app/src/main/assets/bg/p05.jpg differ diff --git a/app/src/main/assets/bg/p06.jpeg b/app/src/main/assets/bg/p06.jpeg new file mode 100644 index 0000000..b3c9c25 Binary files /dev/null and b/app/src/main/assets/bg/p06.jpeg differ diff --git a/app/src/main/assets/bg/p07.jpeg b/app/src/main/assets/bg/p07.jpeg new file mode 100644 index 0000000..740f20a Binary files /dev/null and b/app/src/main/assets/bg/p07.jpeg differ diff --git a/app/src/main/assets/bg/p08.jpeg b/app/src/main/assets/bg/p08.jpeg new file mode 100644 index 0000000..9e65f8c Binary files /dev/null and b/app/src/main/assets/bg/p08.jpeg differ diff --git a/app/src/main/assets/bg/p09.jpeg b/app/src/main/assets/bg/p09.jpeg new file mode 100644 index 0000000..9abd953 Binary files /dev/null and b/app/src/main/assets/bg/p09.jpeg differ diff --git a/app/src/main/assets/bg/p10.jpeg b/app/src/main/assets/bg/p10.jpeg new file mode 100644 index 0000000..4b3e0b9 Binary files /dev/null and b/app/src/main/assets/bg/p10.jpeg differ diff --git a/app/src/main/assets/bg/p11.png b/app/src/main/assets/bg/p11.png new file mode 100644 index 0000000..725fdb4 Binary files /dev/null and b/app/src/main/assets/bg/p11.png differ diff --git a/app/src/main/assets/bg/p12.png b/app/src/main/assets/bg/p12.png new file mode 100644 index 0000000..91068c7 Binary files /dev/null and b/app/src/main/assets/bg/p12.png differ diff --git a/app/src/main/assets/bg/p13.jpg b/app/src/main/assets/bg/p13.jpg new file mode 100644 index 0000000..7ba1636 Binary files /dev/null and b/app/src/main/assets/bg/p13.jpg differ diff --git a/app/src/main/assets/bg/p21.png b/app/src/main/assets/bg/p21.png new file mode 100644 index 0000000..8e7093a Binary files /dev/null and b/app/src/main/assets/bg/p21.png differ diff --git a/app/src/main/assets/bg/p22.png b/app/src/main/assets/bg/p22.png new file mode 100644 index 0000000..7b5abc0 Binary files /dev/null and b/app/src/main/assets/bg/p22.png differ diff --git a/app/src/main/assets/bg/p23.png b/app/src/main/assets/bg/p23.png new file mode 100644 index 0000000..fceb5ac Binary files /dev/null and b/app/src/main/assets/bg/p23.png differ diff --git a/app/src/main/assets/bg/p24.png b/app/src/main/assets/bg/p24.png new file mode 100644 index 0000000..dd4d7f8 Binary files /dev/null and b/app/src/main/assets/bg/p24.png differ diff --git a/app/src/main/assets/bg/p25.png b/app/src/main/assets/bg/p25.png new file mode 100644 index 0000000..80f9258 Binary files /dev/null and b/app/src/main/assets/bg/p25.png differ diff --git a/app/src/main/assets/bg/p26.png b/app/src/main/assets/bg/p26.png new file mode 100644 index 0000000..47a2f6c Binary files /dev/null and b/app/src/main/assets/bg/p26.png differ diff --git a/app/src/main/assets/bg/p27.png b/app/src/main/assets/bg/p27.png new file mode 100644 index 0000000..b5ac4b1 Binary files /dev/null and b/app/src/main/assets/bg/p27.png differ diff --git a/app/src/main/assets/font_img/仓耳与墨W02.png b/app/src/main/assets/font_img/仓耳与墨W02.png new file mode 100644 index 0000000..6c8922b Binary files /dev/null and b/app/src/main/assets/font_img/仓耳与墨W02.png differ diff --git a/app/src/main/assets/font_img/仓耳舒圆体W02.png b/app/src/main/assets/font_img/仓耳舒圆体W02.png new file mode 100644 index 0000000..db27d4d Binary files /dev/null and b/app/src/main/assets/font_img/仓耳舒圆体W02.png differ diff --git a/app/src/main/assets/font_img/仓耳非白W02.png b/app/src/main/assets/font_img/仓耳非白W02.png new file mode 100644 index 0000000..ea5d5f6 Binary files /dev/null and b/app/src/main/assets/font_img/仓耳非白W02.png differ diff --git a/app/src/main/assets/font_img/包图小白体.png b/app/src/main/assets/font_img/包图小白体.png new file mode 100644 index 0000000..a560b59 Binary files /dev/null and b/app/src/main/assets/font_img/包图小白体.png differ diff --git a/app/src/main/assets/font_img/千图小兔体.png b/app/src/main/assets/font_img/千图小兔体.png new file mode 100644 index 0000000..b1669bc Binary files /dev/null and b/app/src/main/assets/font_img/千图小兔体.png differ diff --git a/app/src/main/assets/font_img/品如手写体.png b/app/src/main/assets/font_img/品如手写体.png new file mode 100644 index 0000000..0dea631 Binary files /dev/null and b/app/src/main/assets/font_img/品如手写体.png differ diff --git a/app/src/main/assets/font_img/手书体.png b/app/src/main/assets/font_img/手书体.png new file mode 100644 index 0000000..6126f4d Binary files /dev/null and b/app/src/main/assets/font_img/手书体.png differ diff --git a/app/src/main/assets/font_img/方正书宋简体.png b/app/src/main/assets/font_img/方正书宋简体.png new file mode 100644 index 0000000..4ac0400 Binary files /dev/null and b/app/src/main/assets/font_img/方正书宋简体.png differ diff --git a/app/src/main/assets/font_img/方正仿宋简体.png b/app/src/main/assets/font_img/方正仿宋简体.png new file mode 100644 index 0000000..6c12b3d Binary files /dev/null and b/app/src/main/assets/font_img/方正仿宋简体.png differ diff --git a/app/src/main/assets/font_img/方正楷体.png b/app/src/main/assets/font_img/方正楷体.png new file mode 100644 index 0000000..12ec889 Binary files /dev/null and b/app/src/main/assets/font_img/方正楷体.png differ diff --git a/app/src/main/assets/font_img/方正硬笔行书.png b/app/src/main/assets/font_img/方正硬笔行书.png new file mode 100644 index 0000000..55c5e61 Binary files /dev/null and b/app/src/main/assets/font_img/方正硬笔行书.png differ diff --git a/app/src/main/assets/font_img/方正行楷.png b/app/src/main/assets/font_img/方正行楷.png new file mode 100644 index 0000000..2da99db Binary files /dev/null and b/app/src/main/assets/font_img/方正行楷.png differ diff --git a/app/src/main/assets/font_img/方正黄草.png b/app/src/main/assets/font_img/方正黄草.png new file mode 100644 index 0000000..fd8b8c1 Binary files /dev/null and b/app/src/main/assets/font_img/方正黄草.png differ diff --git a/app/src/main/assets/font_img/方正黑体简体.png b/app/src/main/assets/font_img/方正黑体简体.png new file mode 100644 index 0000000..2f82f55 Binary files /dev/null and b/app/src/main/assets/font_img/方正黑体简体.png differ diff --git a/app/src/main/assets/font_img/杨任东竹石体.png b/app/src/main/assets/font_img/杨任东竹石体.png new file mode 100644 index 0000000..b381b66 Binary files /dev/null and b/app/src/main/assets/font_img/杨任东竹石体.png differ diff --git a/app/src/main/assets/font_img/演示夏行楷.png b/app/src/main/assets/font_img/演示夏行楷.png new file mode 100644 index 0000000..153b36f Binary files /dev/null and b/app/src/main/assets/font_img/演示夏行楷.png differ diff --git a/app/src/main/assets/font_img/演示悠然小楷.png b/app/src/main/assets/font_img/演示悠然小楷.png new file mode 100644 index 0000000..0ec2a8d Binary files /dev/null and b/app/src/main/assets/font_img/演示悠然小楷.png differ diff --git a/app/src/main/assets/font_img/演示春风楷.png b/app/src/main/assets/font_img/演示春风楷.png new file mode 100644 index 0000000..00c6470 Binary files /dev/null and b/app/src/main/assets/font_img/演示春风楷.png differ diff --git a/app/src/main/assets/font_img/演示秋鸿楷.png b/app/src/main/assets/font_img/演示秋鸿楷.png new file mode 100644 index 0000000..305b5a5 Binary files /dev/null and b/app/src/main/assets/font_img/演示秋鸿楷.png differ diff --git a/app/src/main/assets/font_img/站酷仓耳渔阳体.png b/app/src/main/assets/font_img/站酷仓耳渔阳体.png new file mode 100644 index 0000000..f1a9282 Binary files /dev/null and b/app/src/main/assets/font_img/站酷仓耳渔阳体.png differ diff --git a/app/src/main/assets/font_img/经典宋体.png b/app/src/main/assets/font_img/经典宋体.png new file mode 100644 index 0000000..e6555e4 Binary files /dev/null and b/app/src/main/assets/font_img/经典宋体.png differ diff --git a/app/src/main/assets/font_img/迷你隶书.png b/app/src/main/assets/font_img/迷你隶书.png new file mode 100644 index 0000000..a86c029 Binary files /dev/null and b/app/src/main/assets/font_img/迷你隶书.png differ diff --git a/app/src/main/assets/share.png b/app/src/main/assets/share.png new file mode 100644 index 0000000..789abb0 Binary files /dev/null and b/app/src/main/assets/share.png differ diff --git a/app/src/main/assets/updatelog.fy b/app/src/main/assets/updatelog.fy index 4e70f17..c242fd9 100644 --- a/app/src/main/assets/updatelog.fy +++ b/app/src/main/assets/updatelog.fy @@ -1,16 +1,63 @@ +1、新增书架排序设置 +2、新增书籍分享(以图片+二维码的形式分享)及扫一扫 + +2020.11.28 +风月读书v1.20.112822 +1、新增书源:妙趣阁、丹书铁券、小说旗、读一读网、飘天文学 +2、新增书城:100本·实体(名著)、妙趣阁、小说旗 +3、新增排行榜:小说旗 +4、新增模糊搜索、精确搜索(建议选择模糊搜索) +5、修复书源:搜小说网 +6、修复应用切换到后台后从桌面启动时重新进入欢迎页的bug +7、修复横屏模式下进入阅读界面闪退的bug +8、修复恢复备份后设置未立即生效的bug +9、阅读布局支持备份/恢复 + +2020.11.23 +风月读书v1.20.112309 +1、修复切换布局是预加载章节页面混乱的bug +2、修复自定义布局后自动覆盖已有布局的bug + +2020.11.22 +风月读书v1.20.112216 +1、修复音量键翻页失效的bug +2、修复滚动模式下自动跳行的bug +3、修复上下翻页模式下使用音量键翻上页动画丢失的bug +4、修复不同布局下亮度和护眼混乱的bug + +2020.11.22 +风月读书v1.20.112212 +1、新增布局设置:支持自定义阅读背景/背景颜色、字体颜色,并内置20张背景图片;支持保存布局(5套日间布局+1套夜间布局);支持导入导出布局 +2、新增排版设置:支持自定义排版(行间距、段间距、字间距及四边距) +3、新增文字繁简转换 +4、新增横屏阅读 +5、新增上下翻页模式 +6、新增护眼模式 +7、新增朗读时音量键翻页、菜单显示章节标题/链接设置开关 +8、优化自动翻页,修改自动翻页方式为滚屏 +9、优化阅读设置菜单 +10、优化阅读文字显示 + 2020.11.14 风月读书v1.20.1111420 +1、修复语音朗读时部分章节朗读段落高亮错位的bug +2、新增阅读界面显示状态栏设置 +3、新增阅读界面全屏点击翻下页设置 +4、新增多种字体 +5、优化翻页速度 + +2020.11.12 +风月读书v1.20.1111217 1、修复语音朗读定时停止无效的bug 2、修复语音朗读时切换日夜间闪退的bug 3、修复语音朗读时切换章节闪退的bug 4、修复语音朗读时部分安卓版本卡死的bug -5、修复语音朗读时部分章节朗读段落高亮错位的bug +5、修复语音朗读时章节首页朗读段落高亮错位的bug 6、修复语音朗读时章节标题不高亮的bug 7、修复安卓5进入字体界面闪退的bug 2020.11.11 -风月读书v1.20.1111121 -1、新增语音朗读(听书) +1、新增语音朗读 2020.11.08 风月读书v1.20.1110811 diff --git a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java index b16056f..37b1def 100644 --- a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java +++ b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java @@ -44,6 +44,8 @@ import javax.net.ssl.X509TrustManager; import xyz.fycz.myreader.R; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.model.backup.UserService; +import xyz.fycz.myreader.model.storage.Backup; import xyz.fycz.myreader.ui.dialog.APPDownloadTip; import xyz.fycz.myreader.ui.dialog.DialogCreator; import xyz.fycz.myreader.entity.Setting; @@ -51,7 +53,9 @@ import xyz.fycz.myreader.entity.UpdateInfo; import xyz.fycz.myreader.ui.activity.MainActivity; import xyz.fycz.myreader.ui.fragment.BookcaseFragment; import xyz.fycz.myreader.util.*; +import xyz.fycz.myreader.util.llog.LLog; import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.callback.ResultCallback; public class MyApplication extends Application { @@ -73,6 +77,7 @@ public class MyApplication extends Application { } mFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());//初始化线程池 initNightTheme(); +// LLog.init(APPCONST.LOG_DIR); } public void initNightTheme() { @@ -87,7 +92,7 @@ public class MyApplication extends Application { } } - protected boolean isNightTheme() { + public boolean isNightTheme() { return !SysManager.getSetting().isDayStyle(); } @@ -254,7 +259,7 @@ public class MyApplication extends Application { MyApplication.getApplication().newThread(() -> { Document doc = null; try { - String url = "https://shimo.im/docs/JzVpMgbZUJARSaFs/read"; + String url = "https://shimo.im/docs/cqkgjPRRydYYhQKt/read"; if (isApkInDebug(getmContext())) { url = "https://shimo.im/docs/zfzpda7MUGskOC9v/read"; } diff --git a/app/src/main/java/xyz/fycz/myreader/application/SysManager.java b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java index 8db9dfa..fd80134 100644 --- a/app/src/main/java/xyz/fycz/myreader/application/SysManager.java +++ b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java @@ -1,41 +1,59 @@ package xyz.fycz.myreader.application; import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.enums.BookSource; import xyz.fycz.myreader.enums.BookcaseStyle; import xyz.fycz.myreader.enums.Font; import xyz.fycz.myreader.enums.Language; import xyz.fycz.myreader.enums.ReadStyle; +import xyz.fycz.myreader.model.backup.UserService; import xyz.fycz.myreader.model.storage.Backup; import xyz.fycz.myreader.util.CacheHelper; +import xyz.fycz.myreader.R; import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; import xyz.fycz.myreader.widget.page.PageMode; import static xyz.fycz.myreader.application.MyApplication.getVersionCode; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_LEATHER; +import static xyz.fycz.myreader.common.APPCONST.s; +import static xyz.fycz.myreader.widget.page.PageLoader.DEFAULT_MARGIN_WIDTH; public class SysManager { - public static void logout() { - - } + private static Setting mSetting; /** * 获取设置 * @return */ public static Setting getSetting() { + if (mSetting != null){ + return mSetting; + } + mSetting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING); + if (mSetting == null){ + mSetting = getDefaultSetting(); + saveSetting(mSetting); + } + return mSetting; + } + public static Setting getNewSetting() { Setting setting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING); - if (setting == null){ + if (setting == null) { setting = getDefaultSetting(); saveSetting(setting); } return setting; } - /** - * 保存设置 - * @param setting - */ + + /** + * 保存设置 + * @param setting + */ public static void saveSetting(Setting setting) { CacheHelper.saveObject(setting, APPCONST.FILE_NAME_SETTING); } @@ -48,19 +66,8 @@ public class SysManager { private static Setting getDefaultSetting(){ Setting setting = new Setting(); setting.setDayStyle(true); - setting.setReadStyle(ReadStyle.leather); - setting.setReadWordSize(25); - setting.setBrightProgress(50); - setting.setBrightFollowSystem(true); - setting.setLanguage(Language.simplified); - setting.setFont(Font.默认字体); - setting.setAutoScrollSpeed(300); - setting.setPageMode(PageMode.COVER); - setting.setVolumeTurnPage(true); - setting.setResetScreen(3); setting.setBookcaseStyle(BookcaseStyle.listMode); setting.setNewestVersionCode(getVersionCode()); - setting.setLocalFontName(""); setting.setAutoSyn(false); setting.setMatchChapter(true); setting.setMatchChapterSuitability(0.7f); @@ -68,9 +75,17 @@ public class SysManager { setting.setRefreshWhenStart(true); setting.setOpenBookStore(true); setting.setSettingVersion(APPCONST.SETTING_VERSION); + setting.setSourceVersion(APPCONST.SOURCE_VERSION); + setting.setHorizontalScreen(false); + setting.initReadStyle(); + setting.setCurReadStyleIndex(1); return setting; } + public static void regetmSetting(){ + mSetting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING); + } + /** * 重置设置 @@ -78,7 +93,18 @@ public class SysManager { public static void resetSetting(){ Setting setting = getSetting(); - Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.BACKUP_FILE_DIR,null, false); + setting.initReadStyle(); + setting.setCurReadStyleIndex(1); + setting.setSharedLayout(true); setting.setSettingVersion(APPCONST.SETTING_VERSION); + saveSetting(setting); + } + + public static void resetSource(){ + Setting setting = getSetting(); + ReadCrawlerUtil.addReadCrawler(BookSource.miaobi, BookSource.dstq, BookSource.xs7, BookSource.du1du,BookSource.paiotian); + ReadCrawlerUtil.removeReadCrawler("cangshu99"); + setting.setSourceVersion(APPCONST.SOURCE_VERSION); + saveSetting(setting); } } diff --git a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java index fab16c9..7b95886 100644 --- a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java +++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java @@ -18,6 +18,9 @@ public class APPCONST { public static final String ALARM_SCHEDULE_MSG = "alarm_schedule_msg"; public static final String FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/"; + public static final String SHARE_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/share/"; + public static final String LOG_DIR = Environment.getExternalStorageDirectory() + "/FYReader/log/"; + public static final String BG_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/bg/"; public static final String TEM_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/tem/"; public static final String BACKUP_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/backup/"; public static final String TXT_BOOK_DIR = Environment.getExternalStorageDirectory() + "/FYReader/noveltxt/"; @@ -29,6 +32,7 @@ public class APPCONST { + "book_cache"+ File.separator ; public static String HTML_CACHE_PATH = FileUtils.getCachePath() + File.separator + "html_cache"+ File.separator ; + public static final String LOADING_ERROR = "\t\t \t\t\t\t\n\n  章节内容转码失败!\n\n\t\t \t\t\t\n\n"; public static long exitTime; public static final int exitConfirmTime = 2000; @@ -41,28 +45,35 @@ public class APPCONST { public static final String SEARCH_BOOK_BEAN = "SearchBookBean"; public static final String SOURCE_INDEX = "sourceIndex"; public static final String FONT = "font"; - public static final String RESULT_RESET_SCREEN = "result_reset_screen"; + public static final String RESULT_NEED_REFRESH = "result_need_refresh"; public static final String RESULT_IS_COLLECTED = "result_is_collected"; public static final String RESULT_LAST_READ_POSITION = "result_last_read_position"; public static final String RESULT_HISTORY_CHAPTER = "result_history_chapter"; + public static final String RESULT_UP_MENU = "result_up_meu"; - public static final int[] READ_STYLE_NIGHT = {R.color.sys_night_word, R.color.sys_night_bg};//黑夜 - public static final int[] READ_STYLE_PROTECTED_EYE = {R.color.sys_protect_eye_word, R.color.sys_protect_eye_bg};//护眼 - public static final int[] READ_STYLE_COMMON = {R.color.sys_common_word, R.color.sys_common_bg};//普通 - public static final int[] READ_STYLE_BLUE_DEEP = {R.color.sys_blue_deep_word, R.color.sys_blue_deep_bg};//深蓝 - public static final int[] READ_STYLE_LEATHER = {R.color.sys_leather_word, R.color.sys_leather_bg};//羊皮纸 - public static final int[] READ_STYLE_BREEN_EYE = {R.color.sys_breen_word, R.color.sys_breen_bg};//棕绿色 + public static final String[] READ_STYLE_NIGHT = {"#94928c", "#393431"};//黑夜 + public static final String[] READ_STYLE_PROTECTED_EYE = {"#313031", "#E1F1DA"};//护眼 + public static final String[] READ_STYLE_COMMON = {"#313031", "#f5f4f0"};//普通 + public static final String[] READ_STYLE_BLUE_DEEP = {"#637079", "#001c29"};//深蓝 + public static final String[] READ_STYLE_LEATHER = {"#313031", "#CEC29C"};//羊皮纸 + public static final String[] READ_STYLE_BREEN = {"#313031", "#b5bd9a"};//棕绿色 public static final String FILE_NAME_SETTING = "setting"; public static final String FILE_NAME_UPDATE_INFO = "updateInfo"; public static final String FILE_NAME_SPLASH_IMAGE = "splashImage"; + public static final int SELECT_TEXT_COLOR = 201; + public static final int SELECT_BG_COLOR = 301; + public static final int REQUEST_FONT = 1001; public static final int REQUEST_CHAPTER_PAGE = 1002; - public static final int REQUEST_RESET_SCREEN_TIME = 1003; - public static final int REQUEST_LOGIN = 1004; + public static final int REQUEST_LOGIN = 1003; + public static final int REQUEST_REFRESH_READ_UI = 1004; + public static final int REQUEST_SELECT_BG = 1005; + public static final int REQUEST_IMPORT_LAYOUT = 1006; + public static final int REQUEST_QR_SCAN = 1007; public static final int REQUEST_READ = 1; @@ -79,7 +90,9 @@ public class APPCONST { public static final int PERMISSIONS_REQUEST_STORAGE = 10001; //设置版本号 - public static final int SETTING_VERSION = 6; + public static final int SETTING_VERSION = 11; + + public static final int SOURCE_VERSION = 1; public static final String FORMAT_FILE_DATE = "yyyy-MM-dd"; @@ -87,7 +100,6 @@ public class APPCONST { public final static String channelIdRead = "channel_read_aloud"; - public static final String DEFAULT_WEB_DAV_URL = "https://dav.jianguoyun.com/dav/"; } diff --git a/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java b/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java new file mode 100644 index 0000000..32a1d00 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/ReadBookControl.java @@ -0,0 +1,813 @@ +//Copyright (c) 2017. 章钦豪. All rights reserved. +package xyz.fycz.myreader.entity; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.provider.Settings; +import android.util.DisplayMetrics; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.util.SharedPreUtils; +import xyz.fycz.myreader.util.utils.BitmapUtil; +import xyz.fycz.myreader.util.utils.MeUtils; + +import static xyz.fycz.myreader.widget.page2.PageLoader.DEFAULT_MARGIN_WIDTH; + + +public class ReadBookControl { + private static final int DEFAULT_BG = 1; + private int textDrawableIndex = DEFAULT_BG; + private List> textDrawable; + private Bitmap bgBitmap; + private int screenDirection; + private int speechRate; + private boolean speechRateFollowSys; + private int textSize; + private int textColor; + private boolean bgIsColor; + private int bgColor; + private float lineMultiplier; + private float paragraphSize; + private int pageMode; + private Boolean hideStatusBar; + private Boolean hideNavigationBar; + private String fontPath; + private int textConvert; + private int navBarColor; + private Boolean textBold; + private Boolean canClickTurn; + private Boolean canKeyTurn; + private Boolean readAloudCanKeyTurn; + private int CPM; + private Boolean clickAllNext; + private Boolean showTitle; + private Boolean showTimeBattery; + private Boolean showLine; + private Boolean darkStatusIcon; + private int indent; + private int screenTimeOut; + private int paddingLeft; + private int paddingTop; + private int paddingRight; + private int paddingBottom; + private int tipPaddingLeft; + private int tipPaddingTop; + private int tipPaddingRight; + private int tipPaddingBottom; + private float textLetterSpacing; + private boolean canSelectText; + public int minCPM = 200; + public int maxCPM = 2000; + private int defaultCPM = 500; + + private SharedPreferences preferences; + + private static ReadBookControl readBookControl; + + public static ReadBookControl getInstance() { + if (readBookControl == null) { + synchronized (ReadBookControl.class) { + if (readBookControl == null) { + readBookControl = new ReadBookControl(); + } + } + } + return readBookControl; + } + + private ReadBookControl() { + preferences = SharedPreUtils.getInstance().getSharedReadable(); + initTextDrawable(); + updateReaderSettings(); + } + + public void updateReaderSettings() { + this.hideStatusBar = preferences.getBoolean("hide_status_bar", false); + this.hideNavigationBar = preferences.getBoolean("hide_navigation_bar", false); + this.indent = preferences.getInt("indent", 2); + this.textSize = preferences.getInt("textSize", 20); + this.canClickTurn = preferences.getBoolean("canClickTurn", true); + this.canKeyTurn = preferences.getBoolean("canKeyTurn", true); + this.readAloudCanKeyTurn = preferences.getBoolean("readAloudCanKeyTurn", false); + this.lineMultiplier = preferences.getFloat("lineMultiplier", 1); + this.paragraphSize = preferences.getFloat("paragraphSize", 1); + this.CPM = preferences.getInt("CPM", defaultCPM) > maxCPM + ? minCPM : preferences.getInt("CPM", defaultCPM); + this.clickAllNext = preferences.getBoolean("clickAllNext", false); + this.fontPath = preferences.getString("fontPath", null); + this.textConvert = preferences.getInt("textConvertInt", 0); + this.textBold = preferences.getBoolean("textBold", false); + this.speechRate = preferences.getInt("speechRate", 10); + this.speechRateFollowSys = preferences.getBoolean("speechRateFollowSys", true); + this.showTitle = preferences.getBoolean("showTitle", true); + this.showTimeBattery = preferences.getBoolean("showTimeBattery", true); + this.showLine = preferences.getBoolean("showLine", true); + this.screenTimeOut = preferences.getInt("screenTimeOut", 0); + this.paddingLeft = preferences.getInt("paddingLeft", DEFAULT_MARGIN_WIDTH); + this.paddingTop = preferences.getInt("paddingTop", 0); + this.paddingRight = preferences.getInt("paddingRight", DEFAULT_MARGIN_WIDTH); + this.paddingBottom = preferences.getInt("paddingBottom", 0); + this.tipPaddingLeft = preferences.getInt("tipPaddingLeft", DEFAULT_MARGIN_WIDTH); + this.tipPaddingTop = preferences.getInt("tipPaddingTop", 0); + this.tipPaddingRight = preferences.getInt("tipPaddingRight", DEFAULT_MARGIN_WIDTH); + this.tipPaddingBottom = preferences.getInt("tipPaddingBottom", 0); + this.pageMode = preferences.getInt("pageMode", 0); + this.screenDirection = preferences.getInt("screenDirection", 0); + this.navBarColor = preferences.getInt("navBarColorInt", 0); + this.textLetterSpacing = preferences.getFloat("textLetterSpacing", 0); + this.canSelectText = preferences.getBoolean("canSelectText", false); + initTextDrawableIndex(); + } + + //阅读背景 + private void initTextDrawable() { + if (null == textDrawable) { + textDrawable = new ArrayList<>(); + Map temp1 = new HashMap<>(); + temp1.put("textColor", Color.parseColor("#3E3D3B")); + temp1.put("bgIsColor", 1); + temp1.put("textBackground", Color.parseColor("#F3F3F3")); + temp1.put("darkStatusIcon", 1); + textDrawable.add(temp1); + + Map temp2 = new HashMap<>(); + temp2.put("textColor", Color.parseColor("#5E432E")); + temp2.put("bgIsColor", 1); + temp2.put("textBackground", Color.parseColor("#C6BAA1")); + temp2.put("darkStatusIcon", 1); + textDrawable.add(temp2); + + Map temp3 = new HashMap<>(); + temp3.put("textColor", Color.parseColor("#22482C")); + temp3.put("bgIsColor", 1); + temp3.put("textBackground", Color.parseColor("#E1F1DA")); + temp3.put("darkStatusIcon", 1); + textDrawable.add(temp3); + + Map temp4 = new HashMap<>(); + temp4.put("textColor", Color.parseColor("#FFFFFF")); + temp4.put("bgIsColor", 1); + temp4.put("textBackground", Color.parseColor("#015A86")); + temp4.put("darkStatusIcon", 0); + textDrawable.add(temp4); + + Map temp5 = new HashMap<>(); + temp5.put("textColor", Color.parseColor("#808080")); + temp5.put("bgIsColor", 1); + temp5.put("textBackground", Color.parseColor("#000000")); + temp5.put("darkStatusIcon", 0); + textDrawable.add(temp5); + } + } + + public void initTextDrawableIndex() { + if (getIsNightTheme()) { + textDrawableIndex = preferences.getInt("textDrawableIndexNight", 4); + } else { + textDrawableIndex = preferences.getInt("textDrawableIndex", DEFAULT_BG); + } + if (textDrawableIndex == -1) { + textDrawableIndex = DEFAULT_BG; + } + initPageStyle(); + setTextDrawable(); + } + + @SuppressWarnings("ConstantConditions") + private void initPageStyle() { + int bgCustom = getBgCustom(textDrawableIndex); + if ((bgCustom == 2 || bgCustom == 3) && getBgPath(textDrawableIndex) != null) { + bgIsColor = false; + String bgPath = getBgPath(textDrawableIndex); + Resources resources = MyApplication.getApplication().getResources(); + DisplayMetrics dm = resources.getDisplayMetrics(); + int width = dm.widthPixels; + int height = dm.heightPixels; + if (bgCustom == 2) { + bgBitmap = BitmapUtil.getFitSampleBitmap(bgPath, width, height); + } else { + bgBitmap = MeUtils.getFitAssetsSampleBitmap(MyApplication.getApplication().getAssets(), bgPath, width, height); + } + if (bgBitmap != null) { + return; + } + } else if (getBgCustom(textDrawableIndex) == 1) { + bgIsColor = true; + bgColor = getBgColor(textDrawableIndex); + return; + } + bgIsColor = true; + bgColor = textDrawable.get(textDrawableIndex).get("textBackground"); + } + + private void setTextDrawable() { + darkStatusIcon = getDarkStatusIcon(textDrawableIndex); + textColor = getTextColor(textDrawableIndex); + } + + public int getTextColor(int textDrawableIndex) { + if (preferences.getInt("textColor" + textDrawableIndex, 0) != 0) { + return preferences.getInt("textColor" + textDrawableIndex, 0); + } else { + return getDefaultTextColor(textDrawableIndex); + } + } + + public void setTextColor(int textDrawableIndex, int textColor) { + preferences.edit() + .putInt("textColor" + textDrawableIndex, textColor) + .apply(); + } + + @SuppressWarnings("ConstantConditions") + public Drawable getBgDrawable(int textDrawableIndex, Context context, int width, int height) { + int color; + try { + Bitmap bitmap = null; + switch (getBgCustom(textDrawableIndex)) { + case 3: + bitmap = MeUtils.getFitAssetsSampleBitmap(context.getAssets(), getBgPath(textDrawableIndex), width, height); + if (bitmap != null) { + return new BitmapDrawable(context.getResources(), bitmap); + } + case 2: + bitmap = BitmapUtil.getFitSampleBitmap(getBgPath(textDrawableIndex), width, height); + if (bitmap != null) { + return new BitmapDrawable(context.getResources(), bitmap); + } + break; + case 1: + color = getBgColor(textDrawableIndex); + return new ColorDrawable(color); + } + if (textDrawable.get(textDrawableIndex).get("bgIsColor") != 0) { + color = textDrawable.get(textDrawableIndex).get("textBackground"); + return new ColorDrawable(color); + } else { + return getDefaultBgDrawable(textDrawableIndex, context); + } + } catch (Exception e) { + if (textDrawable.get(textDrawableIndex).get("bgIsColor") != 0) { + color = textDrawable.get(textDrawableIndex).get("textBackground"); + return new ColorDrawable(color); + } else { + return getDefaultBgDrawable(textDrawableIndex, context); + } + } + } + + @SuppressWarnings("ConstantConditions") + public Drawable getDefaultBgDrawable(int textDrawableIndex, Context context) { + if (textDrawable.get(textDrawableIndex).get("bgIsColor") != 0) { + return new ColorDrawable(textDrawable.get(textDrawableIndex).get("textBackground")); + } else { + return context.getResources().getDrawable(getDefaultBg(textDrawableIndex)); + } + } + + public int getBgCustom(int textDrawableIndex) { + return preferences.getInt("bgCustom" + textDrawableIndex, 0); + } + + public void setBgCustom(int textDrawableIndex, int bgCustom) { + preferences.edit() + .putInt("bgCustom" + textDrawableIndex, bgCustom) + .apply(); + } + + public String getBgPath(int textDrawableIndex) { + return preferences.getString("bgPath" + textDrawableIndex, null); + } + + public void setBgPath(int textDrawableIndex, String bgUri) { + preferences.edit() + .putString("bgPath" + textDrawableIndex, bgUri) + .apply(); + } + + @SuppressWarnings("ConstantConditions") + public int getDefaultTextColor(int textDrawableIndex) { + return textDrawable.get(textDrawableIndex).get("textColor"); + } + + @SuppressWarnings("ConstantConditions") + private int getDefaultBg(int textDrawableIndex) { + return textDrawable.get(textDrawableIndex).get("textBackground"); + } + + public int getBgColor(int index) { + return preferences.getInt("bgColor" + index, Color.parseColor("#1e1e1e")); + } + + public void setBgColor(int index, int bgColor) { + preferences.edit() + .putInt("bgColor" + index, bgColor) + .apply(); + } + + private boolean getIsNightTheme() { + return MyApplication.getApplication().isNightTheme(); + } + + public boolean getImmersionStatusBar() { + return preferences.getBoolean("immersionStatusBar", false); + } + + public void setImmersionStatusBar(boolean immersionStatusBar) { + preferences.edit() + .putBoolean("immersionStatusBar", immersionStatusBar) + .apply(); + } + + public int getTextSize() { + return textSize; + } + + public void setTextSize(int textSize) { + this.textSize = textSize; + preferences.edit() + .putInt("textSize", textSize) + .apply(); + } + + public int getTextColor() { + return textColor; + } + + public boolean bgIsColor() { + return bgIsColor; + } + + public Drawable getTextBackground(Context context) { + if (bgIsColor) { + return new ColorDrawable(bgColor); + } + return new BitmapDrawable(context.getResources(), bgBitmap); + } + + public int getBgColor() { + return bgColor; + } + + public boolean bgBitmapIsNull() { + return bgBitmap == null || bgBitmap.isRecycled(); + } + + public Bitmap getBgBitmap() { + return bgBitmap.copy(Bitmap.Config.ARGB_8888, true); + } + + public int getTextDrawableIndex() { + return textDrawableIndex; + } + + public void setTextDrawableIndex(int textDrawableIndex) { + this.textDrawableIndex = textDrawableIndex; + if (getIsNightTheme()) { + preferences.edit() + .putInt("textDrawableIndexNight", textDrawableIndex) + .apply(); + } else { + preferences.edit() + .putInt("textDrawableIndex", textDrawableIndex) + .apply(); + } + setTextDrawable(); + } + + public void setTextConvert(int textConvert) { + this.textConvert = textConvert; + preferences.edit() + .putInt("textConvertInt", textConvert) + .apply(); + } + + public void setNavBarColor(int navBarColor) { + this.navBarColor = navBarColor; + preferences.edit() + .putInt("navBarColorInt", navBarColor) + .apply(); + } + + public int getNavBarColor() { + return navBarColor; + } + + + public void setTextBold(boolean textBold) { + this.textBold = textBold; + preferences.edit() + .putBoolean("textBold", textBold) + .apply(); + } + + public void setReadBookFont(String fontPath) { + this.fontPath = fontPath; + preferences.edit() + .putString("fontPath", fontPath) + .apply(); + } + + public String getFontPath() { + return fontPath; + } + + public int getTextConvert() { + return textConvert == -1 ? 2 : textConvert; + } + + public Boolean getTextBold() { + return textBold; + } + + public Boolean getCanKeyTurn(Boolean isPlay) { + if (!canKeyTurn) { + return false; + } else if (readAloudCanKeyTurn) { + return true; + } else { + return !isPlay; + } + } + + public Boolean getCanKeyTurn() { + return canKeyTurn; + } + + public void setCanKeyTurn(Boolean canKeyTurn) { + this.canKeyTurn = canKeyTurn; + preferences.edit() + .putBoolean("canKeyTurn", canKeyTurn) + .apply(); + } + + public Boolean getAloudCanKeyTurn() { + return readAloudCanKeyTurn; + } + + public void setAloudCanKeyTurn(Boolean canAloudKeyTurn) { + this.readAloudCanKeyTurn = canAloudKeyTurn; + preferences.edit() + .putBoolean("readAloudCanKeyTurn", canAloudKeyTurn) + .apply(); + } + + public Boolean getCanClickTurn() { + return canClickTurn; + } + + public void setCanClickTurn(Boolean canClickTurn) { + this.canClickTurn = canClickTurn; + preferences.edit() + .putBoolean("canClickTurn", canClickTurn) + .apply(); + } + + public float getTextLetterSpacing() { + return textLetterSpacing; + } + + public void setTextLetterSpacing(float textLetterSpacing) { + this.textLetterSpacing = textLetterSpacing; + preferences.edit() + .putFloat("textLetterSpacing", textLetterSpacing) + .apply(); + } + + public float getLineMultiplier() { + return lineMultiplier; + } + + public void setLineMultiplier(float lineMultiplier) { + this.lineMultiplier = lineMultiplier; + preferences.edit() + .putFloat("lineMultiplier", lineMultiplier) + .apply(); + } + + public float getParagraphSize() { + return paragraphSize; + } + + public void setParagraphSize(float paragraphSize) { + this.paragraphSize = paragraphSize; + preferences.edit() + .putFloat("paragraphSize", paragraphSize) + .apply(); + } + + public int getCPM() { + return CPM; + } + + public void setCPM(int cpm) { + if (cpm < minCPM || cpm > maxCPM) cpm = defaultCPM; + this.CPM = cpm; + preferences.edit() + .putInt("CPM", cpm) + .apply(); + } + + public Boolean getClickAllNext() { + return clickAllNext; + } + + public void setClickAllNext(Boolean clickAllNext) { + this.clickAllNext = clickAllNext; + preferences.edit() + .putBoolean("clickAllNext", clickAllNext) + .apply(); + } + + public int getSpeechRate() { + return speechRate; + } + + public void setSpeechRate(int speechRate) { + this.speechRate = speechRate; + preferences.edit() + .putInt("speechRate", speechRate) + .apply(); + } + + public boolean isSpeechRateFollowSys() { + return speechRateFollowSys; + } + + public void setSpeechRateFollowSys(boolean speechRateFollowSys) { + this.speechRateFollowSys = speechRateFollowSys; + preferences.edit() + .putBoolean("speechRateFollowSys", speechRateFollowSys) + .apply(); + } + + public Boolean getShowTitle() { + return showTitle; + } + + public void setShowTitle(Boolean showTitle) { + this.showTitle = showTitle; + preferences.edit() + .putBoolean("showTitle", showTitle) + .apply(); + } + + public Boolean getShowTimeBattery() { + return showTimeBattery; + } + + public void setShowTimeBattery(Boolean showTimeBattery) { + this.showTimeBattery = showTimeBattery; + preferences.edit() + .putBoolean("showTimeBattery", showTimeBattery) + .apply(); + } + + public Boolean getHideStatusBar() { + return hideStatusBar; + } + + public void setHideStatusBar(Boolean hideStatusBar) { + this.hideStatusBar = hideStatusBar; + preferences.edit() + .putBoolean("hide_status_bar", hideStatusBar) + .apply(); + } + + public Boolean getToLh() { + return preferences.getBoolean("toLh", false); + } + + public void setToLh(Boolean toLh) { + preferences.edit() + .putBoolean("toLh", toLh) + .apply(); + } + + public Boolean getHideNavigationBar() { + return hideNavigationBar; + } + + public void setHideNavigationBar(Boolean hideNavigationBar) { + this.hideNavigationBar = hideNavigationBar; + preferences.edit() + .putBoolean("hide_navigation_bar", hideNavigationBar) + .apply(); + } + + public Boolean getShowLine() { + return showLine; + } + + public void setShowLine(Boolean showLine) { + this.showLine = showLine; + preferences.edit() + .putBoolean("showLine", showLine) + .apply(); + } + + public boolean getDarkStatusIcon() { + return darkStatusIcon; + } + + @SuppressWarnings("ConstantConditions") + public boolean getDarkStatusIcon(int textDrawableIndex) { + return preferences.getBoolean("darkStatusIcon" + textDrawableIndex, textDrawable.get(textDrawableIndex).get("darkStatusIcon") != 0); + } + + public void setDarkStatusIcon(int textDrawableIndex, Boolean darkStatusIcon) { + preferences.edit() + .putBoolean("darkStatusIcon" + textDrawableIndex, darkStatusIcon) + .apply(); + } + + public int getScreenTimeOut() { + return screenTimeOut; + } + + public void setScreenTimeOut(int screenTimeOut) { + this.screenTimeOut = screenTimeOut; + preferences.edit() + .putInt("screenTimeOut", screenTimeOut) + .apply(); + } + + public int getPaddingLeft() { + return paddingLeft; + } + + public void setPaddingLeft(int paddingLeft) { + this.paddingLeft = paddingLeft; + preferences.edit() + .putInt("paddingLeft", paddingLeft) + .apply(); + } + + public int getPaddingTop() { + return paddingTop; + } + + public void setPaddingTop(int paddingTop) { + this.paddingTop = paddingTop; + preferences.edit() + .putInt("paddingTop", paddingTop) + .apply(); + } + + public int getPaddingRight() { + return paddingRight; + } + + public void setPaddingRight(int paddingRight) { + this.paddingRight = paddingRight; + preferences.edit() + .putInt("paddingRight", paddingRight) + .apply(); + } + + public int getPaddingBottom() { + return paddingBottom; + } + + public void setPaddingBottom(int paddingBottom) { + this.paddingBottom = paddingBottom; + preferences.edit() + .putInt("paddingBottom", paddingBottom) + .apply(); + } + + public int getTipPaddingLeft() { + return tipPaddingLeft; + } + + public void setTipPaddingLeft(int tipPaddingLeft) { + this.tipPaddingLeft = tipPaddingLeft; + preferences.edit() + .putInt("tipPaddingLeft", tipPaddingLeft) + .apply(); + } + + public boolean isCanSelectText() { + return canSelectText; + } + + public void setCanSelectText(boolean canSelectText) { + this.canSelectText = canSelectText; + preferences.edit() + .putBoolean("canSelectText", canSelectText) + .apply(); + } + + public int getTipPaddingTop() { + return tipPaddingTop; + } + + public void setTipPaddingTop(int tipPaddingTop) { + this.tipPaddingTop = tipPaddingTop; + preferences.edit() + .putInt("tipPaddingTop", tipPaddingTop) + .apply(); + } + + public int getTipPaddingRight() { + return tipPaddingRight; + } + + public void setTipPaddingRight(int tipPaddingRight) { + this.tipPaddingRight = tipPaddingRight; + preferences.edit() + .putInt("tipPaddingRight", tipPaddingRight) + .apply(); + } + + public int getTipPaddingBottom() { + return tipPaddingBottom; + } + + public void setTipPaddingBottom(int tipPaddingBottom) { + this.tipPaddingBottom = tipPaddingBottom; + preferences.edit() + .putInt("tipPaddingBottom", tipPaddingBottom) + .apply(); + } + + public int getPageMode() { + return pageMode; + } + + public void setPageMode(int pageMode) { + this.pageMode = pageMode; + preferences.edit() + .putInt("pageMode", pageMode) + .apply(); + } + + public int getScreenDirection() { + return screenDirection; + } + + public void setScreenDirection(int screenDirection) { + this.screenDirection = screenDirection; + preferences.edit() + .putInt("screenDirection", screenDirection) + .apply(); + } + + public void setIndent(int indent) { + this.indent = indent; + preferences.edit() + .putInt("indent", indent) + .apply(); + } + + public int getIndent() { + return indent; + } + + public int getLight() { + return preferences.getInt("light", getScreenBrightness()); + } + + public void setLight(int light) { + preferences.edit() + .putInt("light", light) + .apply(); + } + + public Boolean getLightFollowSys() { + return preferences.getBoolean("lightFollowSys", true); + } + + public void setLightFollowSys(boolean isFollowSys) { + preferences.edit() + .putBoolean("lightFollowSys", isFollowSys) + .apply(); + } + + private int getScreenBrightness() { + int value = 0; + ContentResolver cr = MyApplication.getApplication().getContentResolver(); + try { + value = Settings.System.getInt(cr, Settings.System.SCREEN_BRIGHTNESS); + } catch (Settings.SettingNotFoundException ignored) { + } + return value; + } + + public boolean disableScrollClickTurn() { + return preferences.getBoolean("disableScrollClickTurn", false); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/ReadStyle.java b/app/src/main/java/xyz/fycz/myreader/entity/ReadStyle.java new file mode 100644 index 0000000..4e496ac --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/ReadStyle.java @@ -0,0 +1,318 @@ +package xyz.fycz.myreader.entity; + +import com.google.gson.Gson; + +import java.io.Serializable; + +import xyz.fycz.myreader.enums.Font; +import xyz.fycz.myreader.enums.Language; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.widget.page.PageMode; + +/** + * 阅读布局 + */ + +public class ReadStyle implements Serializable { + + private static final long serialVersionUID = 2295691803219441757L; + + private int readWordSize;//阅读字体大小 + + private int brightProgress;//亮度 1- 100 + + private boolean brightFollowSystem;//亮度跟随系统 + + private Language language;//简繁体 + + private Font font;//字体 + + private int autoScrollSpeed;//自动滑屏速度 + + private PageMode pageMode;//翻页模式 + + private boolean isVolumeTurnPage = true;//是否开启音量键翻页 + + private int resetScreen = 3;//息屏时间(单位:min,0是不息屏) + + private String localFontName;//本地字体名字 + + private boolean isShowStatusBar;//是否显示状态栏 + + private boolean alwaysNext;//是否总是翻到下一页 + + private int intent;//缩进字符 + + private float lineMultiplier;//行间距 + + private float paragraphSize;//段间距 + + private float textLetterSpacing;//字间距 + + private int paddingLeft;//左边距 + private int paddingTop;//上边距 + private int paddingRight;//右边距 + private int paddingBottom;//下边距 + + private int composition;//排版选择 + + private boolean tightCom;//是否紧凑排版 + + private boolean bgIsColor;//背景是否为颜色 不能共用 + + private boolean bgIsAssert;//背景是否为assert文件 不能共用 + + private int textColor;//文字颜色 不能共用 + + private int bgColor;//背景颜色 不能共用 + + private String bgPath;//背景图片地址 不能共用 + + private boolean protectEye;//开启护眼模式 + + private int blueFilterPercent;//蓝光过滤比率 + + @Override + public Object clone() { + try { + Gson gson = new Gson(); + String json = gson.toJson(this); + return gson.fromJson(json, ReadStyle.class); + } catch (Exception e) { + e.printStackTrace(); + } + return this; + } + + public int getReadWordSize() { + return readWordSize; + } + + public void setReadWordSize(int readWordSize) { + this.readWordSize = readWordSize; + } + + public int getBrightProgress() { + return brightProgress; + } + + public void setBrightProgress(int brightProgress) { + this.brightProgress = brightProgress; + } + + public boolean isBrightFollowSystem() { + return brightFollowSystem; + } + + public void setBrightFollowSystem(boolean brightFollowSystem) { + this.brightFollowSystem = brightFollowSystem; + } + + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + this.font = font; + } + + public int getAutoScrollSpeed() { + return autoScrollSpeed; + } + + public void setAutoScrollSpeed(int autoScrollSpeed) { + this.autoScrollSpeed = autoScrollSpeed; + } + + public PageMode getPageMode() { + return pageMode; + } + + public void setPageMode(PageMode pageMode) { + this.pageMode = pageMode; + } + + public boolean isVolumeTurnPage() { + return isVolumeTurnPage; + } + + public void setVolumeTurnPage(boolean volumeTurnPage) { + isVolumeTurnPage = volumeTurnPage; + } + + public int getResetScreen() { + return resetScreen; + } + + public void setResetScreen(int resetScreen) { + this.resetScreen = resetScreen; + } + + public String getLocalFontName() { + return localFontName; + } + + public void setLocalFontName(String localFontName) { + this.localFontName = localFontName; + } + + public boolean isShowStatusBar() { + return isShowStatusBar; + } + + public void setShowStatusBar(boolean showStatusBar) { + isShowStatusBar = showStatusBar; + } + + public boolean isAlwaysNext() { + return alwaysNext; + } + + public void setAlwaysNext(boolean alwaysNext) { + this.alwaysNext = alwaysNext; + } + + public int getIntent() { + return intent; + } + + public void setIntent(int intent) { + this.intent = intent; + } + + public float getLineMultiplier() { + return lineMultiplier; + } + + public void setLineMultiplier(float lineMultiplier) { + this.lineMultiplier = lineMultiplier; + } + + public float getParagraphSize() { + return paragraphSize; + } + + public void setParagraphSize(float paragraphSize) { + this.paragraphSize = paragraphSize; + } + + public float getTextLetterSpacing() { + return textLetterSpacing; + } + + public void setTextLetterSpacing(float textLetterSpacing) { + this.textLetterSpacing = textLetterSpacing; + } + + public int getPaddingLeft() { + return paddingLeft; + } + + public void setPaddingLeft(int paddingLeft) { + this.paddingLeft = paddingLeft; + } + + public int getPaddingTop() { + return paddingTop; + } + + public void setPaddingTop(int paddingTop) { + this.paddingTop = paddingTop; + } + + public int getPaddingRight() { + return paddingRight; + } + + public void setPaddingRight(int paddingRight) { + this.paddingRight = paddingRight; + } + + public int getPaddingBottom() { + return paddingBottom; + } + + public void setPaddingBottom(int paddingBottom) { + this.paddingBottom = paddingBottom; + } + + public int getComposition() { + return composition; + } + + public void setComposition(int composition) { + this.composition = composition; + } + + public boolean isTightCom() { + return tightCom; + } + + public void setTightCom(boolean tightCom) { + this.tightCom = tightCom; + } + + public boolean bgIsColor() { + return bgIsColor; + } + + public void setBgIsColor(boolean bgIsColor) { + this.bgIsColor = bgIsColor; + } + + public boolean bgIsAssert() { + return bgIsAssert; + } + + public void setBgIsAssert(boolean bgIsAssert) { + this.bgIsAssert = bgIsAssert; + } + + public int getTextColor() { + return textColor; + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + } + + public int getBgColor() { + return bgColor; + } + + public void setBgColor(int bgColor) { + this.bgColor = bgColor; + } + + public String getBgPath() { + return bgPath; + } + + public void setBgPath(String bgPath) { + this.bgPath = bgPath; + } + + public boolean isProtectEye() { + return protectEye; + } + + public void setProtectEye(boolean protectEye) { + this.protectEye = protectEye; + } + + public int getBlueFilterPercent() { + return blueFilterPercent; + } + + public void setBlueFilterPercent(int blueFilterPercent) { + this.blueFilterPercent = blueFilterPercent; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Setting.java b/app/src/main/java/xyz/fycz/myreader/entity/Setting.java index c4dd0ea..8ce9265 100644 --- a/app/src/main/java/xyz/fycz/myreader/entity/Setting.java +++ b/app/src/main/java/xyz/fycz/myreader/entity/Setting.java @@ -1,12 +1,41 @@ package xyz.fycz.myreader.entity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.enums.BookcaseStyle; import xyz.fycz.myreader.enums.Font; import xyz.fycz.myreader.enums.Language; -import xyz.fycz.myreader.enums.ReadStyle; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.ZipUtils; +import xyz.fycz.myreader.util.utils.BitmapUtil; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.GsonExtensionsKt; +import xyz.fycz.myreader.util.utils.MeUtils; import xyz.fycz.myreader.widget.page.PageMode; +import java.io.File; +import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_BLUE_DEEP; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_BREEN; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_COMMON; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_LEATHER; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_NIGHT; +import static xyz.fycz.myreader.common.APPCONST.READ_STYLE_PROTECTED_EYE; +import static xyz.fycz.myreader.common.APPCONST.s; +import static xyz.fycz.myreader.widget.page.PageLoader.DEFAULT_MARGIN_WIDTH; /** * 用户设置 @@ -17,30 +46,19 @@ public class Setting implements Serializable { private static final long serialVersionUID = 2295691810299441757L; - private float readWordSize;//阅读字体大小 + /** + * 共七套布局,5套自带布局(0~4),一套自定义(5),一套夜间(6) + */ + private List readStyles;//阅读布局 - private ReadStyle readStyle = ReadStyle.leather;//阅读模式 + private int curReadStyleIndex;//当前阅读布局 private boolean dayStyle;//是否日间模式 - private int brightProgress;//亮度 1- 100 - private boolean brightFollowSystem;//亮度跟随系统 - private Language language;//简繁体 - private Font font;//字体 - - private int autoScrollSpeed = 3;//自动滑屏速度 - - private PageMode pageMode;//翻页模式 - - private boolean isVolumeTurnPage = true;//是否开启音量键翻页 - - private int resetScreen = 3;//息屏时间(单位:min,0是不息屏) private BookcaseStyle bookcaseStyle;//书架布局 private int newestVersionCode;//最新版本号 - private String localFontName;//本地字体名字 - private boolean isAutoSyn;//是否自动同步书架 private boolean isMatchChapter = true;//是否开启智能匹配历史章节 @@ -53,46 +71,257 @@ public class Setting implements Serializable { private boolean openBookStore;//是否开启书城 + private boolean sharedLayout;//是否共用布局 + + private boolean horizontalScreen;//是否横屏 + + private boolean noMenuChTitle;//关闭阅读上边菜单章节标题和链接显示 + + private boolean readAloudVolumeTurnPage;//朗读时音量键翻页 + + private int searchFilter;//搜索过滤:0-不过滤,1-模糊搜索,2-精确搜索 + + private int sortStyle;//排序方式:0-手动排序,1-按时间排序,2-按照书名排序 + + private int sourceVersion;//书源版本号 + private int settingVersion;//设置版本号 + public ReadStyle getCurReadStyle(){ + //Log.d("curReadStyleIndex", String.valueOf(curReadStyleIndex)); + ReadStyle readStyle = readStyles.get(curReadStyleIndex); + if (readStyle == null) { + initReadStyle(); + } + if (!dayStyle){ + return readStyles.get(6); + } + return readStyles.get(curReadStyleIndex); + } + + public Drawable getBgDrawable(int readStyleIndex, Context context, int width, int height) { + ReadStyle readStyle = readStyles.get(readStyleIndex); + if (readStyle.bgIsColor()){ + return new ColorDrawable(readStyle.getBgColor()); + }else { + Bitmap bitmap = null; + try { + if (readStyle.bgIsAssert()) { + bitmap = MeUtils.getFitAssetsSampleBitmap(context.getAssets(), readStyle.getBgPath(), width, height); + } else { + bitmap = BitmapUtil.getFitSampleBitmap(readStyle.getBgPath(), width, height); + } + if (bitmap == null) { + bitmap = MeUtils.getFitAssetsSampleBitmap(context.getAssets(), "bg/p01.jpg", width, height); + } + }catch (Exception e){ + bitmap = MeUtils.getFitAssetsSampleBitmap(context.getAssets(), "bg/p01.jpg", width, height); + } + return new BitmapDrawable(context.getResources(), bitmap); + } + } + + public void initReadStyle(){ + readStyles = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + ReadStyle readStyle = new ReadStyle(); + String textColor; + String bgColor; + switch(i){ + case 0: + textColor = READ_STYLE_COMMON[0]; + bgColor = READ_STYLE_COMMON[1]; + break; + case 1: + default: + textColor = READ_STYLE_LEATHER[0]; + bgColor = READ_STYLE_LEATHER[1]; + break; + case 2: + textColor = READ_STYLE_PROTECTED_EYE[0]; + bgColor = READ_STYLE_PROTECTED_EYE[1]; + break; + case 3: + textColor = READ_STYLE_BREEN[0]; + bgColor = READ_STYLE_BREEN[1]; + break; + case 4: + textColor = READ_STYLE_BLUE_DEEP[0]; + bgColor = READ_STYLE_BLUE_DEEP[1]; + break; + case 6: + textColor = READ_STYLE_NIGHT[0]; + bgColor = READ_STYLE_NIGHT[1]; + break; + } + readStyle.setTextColor(Color.parseColor(textColor)); + readStyle.setBgColor(Color.parseColor(bgColor)); + readStyle.setReadWordSize(20); + readStyle.setBrightProgress(50); + readStyle.setBrightFollowSystem(true); + readStyle.setLanguage(Language.normal); + readStyle.setFont(Font.默认字体); + readStyle.setLocalFontName(""); + readStyle.setAutoScrollSpeed(60); + readStyle.setPageMode(PageMode.COVER); + readStyle.setVolumeTurnPage(true); + readStyle.setResetScreen(3); + readStyle.setShowStatusBar(false); + readStyle.setAlwaysNext(false); + readStyle.setIntent(2); + readStyle.setLineMultiplier(1); + readStyle.setParagraphSize(0.9f); + readStyle.setTextLetterSpacing(0); + readStyle.setPaddingLeft(DEFAULT_MARGIN_WIDTH); + readStyle.setPaddingRight(DEFAULT_MARGIN_WIDTH); + readStyle.setPaddingTop(0); + readStyle.setPaddingBottom(0); + readStyle.setTightCom(false); + readStyle.setBgIsColor(true); + readStyle.setBgIsAssert(true); + readStyle.setBgPath(""); + readStyle.setBlueFilterPercent(30); + readStyle.setProtectEye(false); + readStyle.setComposition(1); + + readStyles.add(readStyle); + } + } + + public void resetLayout(){ + for (int i = 0; i < 7; i++) { + ReadStyle readStyle = readStyles.get(i); + String textColor; + String bgColor; + switch(i){ + case 0: + textColor = READ_STYLE_COMMON[0]; + bgColor = READ_STYLE_COMMON[1]; + break; + case 1: + default: + textColor = READ_STYLE_LEATHER[0]; + bgColor = READ_STYLE_LEATHER[1]; + break; + case 2: + textColor = READ_STYLE_PROTECTED_EYE[0]; + bgColor = READ_STYLE_PROTECTED_EYE[1]; + break; + case 3: + textColor = READ_STYLE_BREEN[0]; + bgColor = READ_STYLE_BREEN[1]; + break; + case 4: + textColor = READ_STYLE_BLUE_DEEP[0]; + bgColor = READ_STYLE_BLUE_DEEP[1]; + break; + case 6: + textColor = READ_STYLE_NIGHT[0]; + bgColor = READ_STYLE_NIGHT[1]; + break; + } + readStyle.setTextColor(Color.parseColor(textColor)); + readStyle.setBgColor(Color.parseColor(bgColor)); + readStyle.setBgIsColor(true); + readStyle.setBgIsAssert(true); + readStyle.setBgPath(""); + } + } + + public void sharedLayout(){ + if (sharedLayout) { + for (int i = 0; i < 7; i++) { + if (curReadStyleIndex == i) continue; + ReadStyle newReadStyle = (ReadStyle) getCurReadStyle().clone(); + ReadStyle oldReadStyle = readStyles.get(i); + newReadStyle.setTextColor(oldReadStyle.getTextColor()); + newReadStyle.setBgColor(oldReadStyle.getBgColor()); + newReadStyle.setBgIsAssert(oldReadStyle.bgIsAssert()); + newReadStyle.setBgIsColor(oldReadStyle.bgIsColor()); + newReadStyle.setBgPath(oldReadStyle.getBgPath()); + readStyles.set(i, newReadStyle); + } + } + } + + public void saveLayout(int index){ + ReadStyle newReadStyle = (ReadStyle) getCurReadStyle().clone(); + readStyles.set(index, newReadStyle); + } + + public boolean exportLayout(int index){ + ReadStyle readStyle = readStyles.get(index); + String json = GsonExtensionsKt.getGSON().toJson(readStyle); + List filesPath = new ArrayList<>(); + filesPath.add(APPCONST.TEM_FILE_DIR + "readConfig.fyl"); + filesPath.add(APPCONST.TEM_FILE_DIR + "bg.fyl"); + if (!FileUtils.writeFile(json.getBytes(), + FileUtils.getFile(filesPath.get(0)))){ + return false; + } + if (!readStyle.bgIsColor() && !readStyle.bgIsAssert()){ + if (!FileUtils.copy(readStyle.getBgPath(), filesPath.get(1))){ + return false; + } + } + try { + ZipUtils.zipFiles(filesPath, APPCONST.FILE_DIR + "readConfig.zip", "风月读书布局导出配置"); + FileUtils.deleteFile(filesPath.get(0)); + FileUtils.deleteFile(filesPath.get(1)); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public void importLayout(int index, ReadStyle readStyle){ + readStyles.set(index, readStyle); + } + public int getAutoScrollSpeed() { - return autoScrollSpeed; + return getCurReadStyle().getAutoScrollSpeed(); } public void setAutoScrollSpeed(int autoScrollSpeed) { - this.autoScrollSpeed = autoScrollSpeed; + getCurReadStyle().setAutoScrollSpeed(autoScrollSpeed); + sharedLayout(); } public Font getFont() { - return font; + return getCurReadStyle().getFont(); } public void setFont(Font font) { - this.font = font; + getCurReadStyle().setFont(font); + sharedLayout(); } public Language getLanguage() { - return language; + return getCurReadStyle().getLanguage(); } public void setLanguage(Language language) { - this.language = language; + getCurReadStyle().setLanguage(language); + sharedLayout(); } public boolean isBrightFollowSystem() { - return brightFollowSystem; + return getCurReadStyle().isBrightFollowSystem(); } public void setBrightFollowSystem(boolean brightFollowSystem) { - this.brightFollowSystem = brightFollowSystem; + getCurReadStyle().setBrightFollowSystem(brightFollowSystem); + sharedLayout(); } public void setBrightProgress(int brightProgress) { - this.brightProgress = brightProgress; + getCurReadStyle().setBrightProgress(brightProgress); + sharedLayout(); } public int getBrightProgress() { - return brightProgress; + return getCurReadStyle().getBrightProgress(); } public boolean isDayStyle() { @@ -103,36 +332,31 @@ public class Setting implements Serializable { this.dayStyle = dayStyle; } - public float getReadWordSize() { - return readWordSize; - } - - public void setReadWordSize(float readWordSize) { - this.readWordSize = readWordSize; + public int getReadWordSize() { + return getCurReadStyle().getReadWordSize(); } - public ReadStyle getReadStyle() { - return readStyle; - } - - public void setReadStyle(ReadStyle readStyle) { - this.readStyle = readStyle; + public void setReadWordSize(int readWordSize) { + getCurReadStyle().setReadWordSize(readWordSize); + sharedLayout(); } public PageMode getPageMode() { - return pageMode; + return getCurReadStyle().getPageMode(); } public void setPageMode(PageMode pageMode) { - this.pageMode = pageMode; + getCurReadStyle().setPageMode(pageMode); + sharedLayout(); } public boolean isVolumeTurnPage() { - return isVolumeTurnPage; + return getCurReadStyle().isVolumeTurnPage(); } public void setVolumeTurnPage(boolean volumeTurnPage) { - isVolumeTurnPage = volumeTurnPage; + getCurReadStyle().setVolumeTurnPage(volumeTurnPage); + sharedLayout(); } public BookcaseStyle getBookcaseStyle() { @@ -152,11 +376,12 @@ public class Setting implements Serializable { } public String getLocalFontName() { - return localFontName; + return getCurReadStyle().getLocalFontName(); } public void setLocalFontName(String localFontName) { - this.localFontName = localFontName; + getCurReadStyle().setLocalFontName(localFontName); + sharedLayout(); } public boolean isAutoSyn() { @@ -176,11 +401,12 @@ public class Setting implements Serializable { } public int getResetScreen() { - return resetScreen; + return getCurReadStyle().getResetScreen(); } public void setResetScreen(int resetScreen) { - this.resetScreen = resetScreen; + getCurReadStyle().setResetScreen(resetScreen); + sharedLayout(); } public boolean isMatchChapter() { @@ -215,6 +441,24 @@ public class Setting implements Serializable { this.refreshWhenStart = refreshWhenStart; } + public boolean isShowStatusBar() { + return getCurReadStyle().isShowStatusBar(); + } + + public void setShowStatusBar(boolean showStatusBar) { + getCurReadStyle().setShowStatusBar(showStatusBar); + sharedLayout(); + } + + public boolean isAlwaysNext() { + return getCurReadStyle().isAlwaysNext(); + } + + public void setAlwaysNext(boolean alwaysNext) { + getCurReadStyle().setAlwaysNext(alwaysNext); + sharedLayout(); + } + public boolean isOpenBookStore() { return openBookStore; } @@ -222,4 +466,232 @@ public class Setting implements Serializable { public void setOpenBookStore(boolean openBookStore) { this.openBookStore = openBookStore; } + + public boolean isHorizontalScreen() { + return horizontalScreen; + } + + public void setHorizontalScreen(boolean horizontalScreen) { + this.horizontalScreen = horizontalScreen; + } + + public void setIntent(int intent) { + getCurReadStyle().setIntent(intent); + sharedLayout(); + } + + public void setLineMultiplier(float lineMultiplier) { + getCurReadStyle().setLineMultiplier(lineMultiplier); + sharedLayout(); + } + + public void setParagraphSize(float paragraphSize) { + getCurReadStyle().setParagraphSize(paragraphSize); + sharedLayout(); + } + + public int getIntent() { + return getCurReadStyle().getIntent(); + } + + public float getLineMultiplier() { + return getCurReadStyle().getLineMultiplier(); + } + + public float getParagraphSize() { + return getCurReadStyle().getParagraphSize(); + } + + public float getTextLetterSpacing() { + return getCurReadStyle().getTextLetterSpacing(); + } + + public void setTextLetterSpacing(float textLetterSpacing) { + getCurReadStyle().setTextLetterSpacing(textLetterSpacing); + sharedLayout(); + } + + public int getComposition() { + return getCurReadStyle().getComposition(); + } + + public void setComposition(int composition) { + getCurReadStyle().setComposition(composition); + sharedLayout(); + } + + public int getPaddingLeft() { + return getCurReadStyle().getPaddingLeft(); + } + + public void setPaddingLeft(int paddingLeft) { + getCurReadStyle().setPaddingLeft(paddingLeft); + sharedLayout(); + } + + public int getPaddingTop() { + return getCurReadStyle().getPaddingTop(); + } + + public void setPaddingTop(int paddingTop) { + getCurReadStyle().setPaddingTop(paddingTop); + sharedLayout(); + } + + public int getPaddingRight() { + return getCurReadStyle().getPaddingRight(); + } + + public void setPaddingRight(int paddingRight) { + getCurReadStyle().setPaddingRight(paddingRight); + sharedLayout(); + } + + public int getPaddingBottom() { + return getCurReadStyle().getPaddingBottom(); + } + + public void setPaddingBottom(int paddingBottom) { + getCurReadStyle().setPaddingBottom(paddingBottom); + sharedLayout(); + } + + public boolean isTightCom() { + return getCurReadStyle().isTightCom(); + } + + public void setTightCom(boolean tightCom) { + getCurReadStyle().setTightCom(tightCom); + sharedLayout(); + } + + public boolean bgIsColor() { + if (StringHelper.isEmpty(getCurReadStyle().getBgPath())) { + getCurReadStyle().setBgIsColor(true); + } + return getCurReadStyle().bgIsColor(); + } + + public void setBgIsColor(boolean bgIsColor) { + getCurReadStyle().setBgIsColor(bgIsColor); + } + + public boolean bgIsAssert() { + return getCurReadStyle().bgIsAssert(); + } + + public void setBgIsAssert(boolean bgIsAssert) { + getCurReadStyle().setBgIsAssert(bgIsAssert); + } + + public int getTextColor() { + if (getCurReadStyle().getTextColor() == 0) { + getCurReadStyle().setTextColor(Color.parseColor(READ_STYLE_LEATHER[0])); + } + return getCurReadStyle().getTextColor(); + } + + public void setTextColor(int textColor) { + getCurReadStyle().setTextColor(textColor); + } + + public int getBgColor() { + if (getCurReadStyle().getBgColor() == 0) { + getCurReadStyle().setBgColor(Color.parseColor(READ_STYLE_LEATHER[1])); + } + return getCurReadStyle().getBgColor(); + } + + public void setBgColor(int bgColor) { + getCurReadStyle().setBgColor(bgColor); + } + + public String getBgPath() { + return getCurReadStyle().getBgPath(); + } + + public void setBgPath(String bgPath) { + getCurReadStyle().setBgPath(bgPath); + } + + public int getCurReadStyleIndex() { + return curReadStyleIndex; + } + + public void setCurReadStyleIndex(int curReadStyleIndex) { + this.curReadStyleIndex = curReadStyleIndex; + } + + public boolean isSharedLayout() { + return sharedLayout; + } + + public void setSharedLayout(boolean sharedLayout) { + this.sharedLayout = sharedLayout; + } + + public boolean isNoMenuChTitle() { + return noMenuChTitle; + } + + public boolean isReadAloudVolumeTurnPage() { + return readAloudVolumeTurnPage; + } + + public void setReadAloudVolumeTurnPage(boolean readAloudVolumeTurnPage) { + this.readAloudVolumeTurnPage = readAloudVolumeTurnPage; + } + + public void setNoMenuChTitle(boolean noMenuChTitle) { + this.noMenuChTitle = noMenuChTitle; + } + + public boolean isProtectEye() { + return getCurReadStyle().isProtectEye(); + } + + public void setProtectEye(boolean protectEye) { + getCurReadStyle().setProtectEye(protectEye); + } + + public int getBlueFilterPercent() { + if (getCurReadStyle().getBlueFilterPercent() == 0) getCurReadStyle().setBlueFilterPercent(30); + return getCurReadStyle().getBlueFilterPercent(); + } + + public void setBlueFilterPercent(int blueFilterPercent) { + getCurReadStyle().setBlueFilterPercent(blueFilterPercent); + } + + public int getSourceVersion() { + return sourceVersion; + } + + public void setSourceVersion(int sourceVersion) { + this.sourceVersion = sourceVersion; + } + + public List getReadStyles() { + return readStyles; + } + + public void setReadStyles(List readStyles) { + this.readStyles = readStyles; + } + + public int getSearchFilter() { + return searchFilter; + } + + public void setSearchFilter(int searchFilter) { + this.searchFilter = searchFilter; + } + + public int getSortStyle() { + return sortStyle; + } + + public void setSortStyle(int sortStyle) { + this.sortStyle = sortStyle; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/entity/SharedBook.java b/app/src/main/java/xyz/fycz/myreader/entity/SharedBook.java new file mode 100644 index 0000000..2b68c77 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/SharedBook.java @@ -0,0 +1,102 @@ +package xyz.fycz.myreader.entity; + +import xyz.fycz.myreader.greendao.entity.Book; + +/** + * @author fengyue + * @date 2020/11/29 19:01 + */ +public class SharedBook { + private String name; + private String author; + private String type; + private String desc; + private String imgUrl; + private String chapterUrl; + private String source; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public String getChapterUrl() { + return chapterUrl; + } + + public void setChapterUrl(String chapterUrl) { + this.chapterUrl = chapterUrl; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public static SharedBook bookToSharedBook(Book book){ + int maxDesc = 208; + String desc = book.getDesc(); + if (desc.length() > maxDesc){ + desc = desc.substring(0, maxDesc - 1) + "…"; + } + SharedBook sharedBook = new SharedBook(); + sharedBook.setName(book.getName()); + sharedBook.setAuthor(book.getAuthor()); + sharedBook.setType(book.getType()); + sharedBook.setDesc(desc); + sharedBook.setImgUrl(book.getImgUrl()); + sharedBook.setChapterUrl(book.getChapterUrl()); + sharedBook.setSource(book.getSource()); + return sharedBook; + } + + public static Book sharedBookToBook(SharedBook sharedBook){ + Book book = new Book(); + book.setName(sharedBook.name); + book.setAuthor(sharedBook.author); + book.setType(sharedBook.type); + book.setDesc(sharedBook.desc); + book.setImgUrl(sharedBook.imgUrl); + book.setChapterUrl(sharedBook.chapterUrl); + book.setSource(sharedBook.source); + return book; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/bookstore/BookType.java b/app/src/main/java/xyz/fycz/myreader/entity/bookstore/BookType.java index 2fc9aea..34075e5 100644 --- a/app/src/main/java/xyz/fycz/myreader/entity/bookstore/BookType.java +++ b/app/src/main/java/xyz/fycz/myreader/entity/bookstore/BookType.java @@ -9,6 +9,7 @@ public class BookType { private String typeName;//分类名称 private String url;//分类链接 + private int pageSize;//页数 public String getTypeName() { return typeName; @@ -25,4 +26,12 @@ public class BookType { public void setUrl(String url) { this.url = url; } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/enums/BookSource.java b/app/src/main/java/xyz/fycz/myreader/enums/BookSource.java index 72bc0c8..989a960 100644 --- a/app/src/main/java/xyz/fycz/myreader/enums/BookSource.java +++ b/app/src/main/java/xyz/fycz/myreader/enums/BookSource.java @@ -1,5 +1,9 @@ package xyz.fycz.myreader.enums; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.util.ToastUtils; + /** * 小说源 */ @@ -7,21 +11,27 @@ package xyz.fycz.myreader.enums; public enum BookSource { fynovel("风月小说"), - tianlai("天籁小说"), - biquge44("笔趣阁44"), - pinshu("品书网"), - biquge("笔趣阁"), - qb5("全本小说"), - miqu("米趣小说"), - jiutao("九桃小说"), - yunzhong("云中书库"), - sonovel("搜小说网"), - quannovel("全小说网"), - qiqi("奇奇小说"), - chaoxing("超星图书·实体"), - zuopin("作品集·实体"), - cangshu99("99藏书·实体"), - ben100("100本·实体"), + tianlai(MyApplication.getApplication().getString(R.string.read_tianlai)), + biquge44(MyApplication.getApplication().getString(R.string.read_biquge44)), + pinshu(MyApplication.getApplication().getString(R.string.read_pinshu)), + biquge(MyApplication.getApplication().getString(R.string.read_biquge)), + qb5(MyApplication.getApplication().getString(R.string.read_qb5)), + miqu(MyApplication.getApplication().getString(R.string.read_miqu)), + jiutao(MyApplication.getApplication().getString(R.string.read_jiutao)), + miaobi(MyApplication.getApplication().getString(R.string.read_miaobi)), + dstq(MyApplication.getApplication().getString(R.string.read_dstq)), + yunzhong(MyApplication.getApplication().getString(R.string.read_yunzhong)), + sonovel(MyApplication.getApplication().getString(R.string.read_sonovel)), + quannovel(MyApplication.getApplication().getString(R.string.read_quannovel)), + qiqi(MyApplication.getApplication().getString(R.string.read_qiqi)), + xs7(MyApplication.getApplication().getString(R.string.read_xs7)), + du1du(MyApplication.getApplication().getString(R.string.read_du1du)), + paiotian(MyApplication.getApplication().getString(R.string.read_paiotian)), + chaoxing(MyApplication.getApplication().getString(R.string.read_chaoxing)), + zuopin(MyApplication.getApplication().getString(R.string.read_zuopin)), + cangshu99(MyApplication.getApplication().getString(R.string.read_cangshu99)), + ben100(MyApplication.getApplication().getString(R.string.read_ben100)), + //liulangcat("流浪猫·实体"), local("本地书籍"); public String text; @@ -35,7 +45,7 @@ public enum BookSource { return bookSource.toString(); } } - return null; + return ""; } public static BookSource get(int var0) { @@ -43,7 +53,12 @@ public enum BookSource { } public static BookSource fromString(String string) { - return valueOf(string); + try { + return valueOf(string); + } catch (Exception e) { + ToastUtils.showError(e.getLocalizedMessage()); + return fynovel; + } } } diff --git a/app/src/main/java/xyz/fycz/myreader/enums/FindType.java b/app/src/main/java/xyz/fycz/myreader/enums/FindType.java index 02c5704..4b1d5ec 100644 --- a/app/src/main/java/xyz/fycz/myreader/enums/FindType.java +++ b/app/src/main/java/xyz/fycz/myreader/enums/FindType.java @@ -2,11 +2,6 @@ package xyz.fycz.myreader.enums; import java.io.Serializable; -/** - * 小说源 - * Created by zhao on 2020/04/13. - */ - public enum FindType implements Serializable { qidian("排行榜[起点中文网]", "https://www.qidian.com/rank"), diff --git a/app/src/main/java/xyz/fycz/myreader/enums/Font.java b/app/src/main/java/xyz/fycz/myreader/enums/Font.java index 3b016c5..a705791 100644 --- a/app/src/main/java/xyz/fycz/myreader/enums/Font.java +++ b/app/src/main/java/xyz/fycz/myreader/enums/Font.java @@ -1,29 +1,35 @@ package xyz.fycz.myreader.enums; -import xyz.fycz.myreader.application.SysManager; -import xyz.fycz.myreader.common.APPCONST; - -/** - * Created by zhao on 2016/11/3. - */ - public enum Font { - 默认字体("默认字体", "默认字体"), - 方正楷体("fangzhengkaiti.ttf", "https://fycz.lanzous.com/ilLFMe6kefe"), - 方正行楷("fangzhengxingkai.ttf", "https://fycz.lanzous.com/imFvne6keji"), - 经典宋体("songti.ttf", "https://fycz.lanzous.com/idhI5e6keqf"), - 迷你隶书("mini_lishu.ttf", "https://fycz.lanzous.com/ihaXVe6kekj"), - 方正黄草("fangzhenghuangcao.ttf", "https://fycz.lanzous.com/iQg67e6keed"), - 方正硬笔行书("fangzheng_yingbi_xingshu.ttf", "https://fycz.lanzous.com/ilVh6ep9xja"), - 本地字体("本地字体", "默认字体"); - - public String fileName; + 默认字体( "默认字体"), + 本地字体( "默认字体"), + 方正楷体( "https://fycz.lanzous.com/ilLFMe6kefe"), + 方正行楷( "https://fycz.lanzous.com/imFvne6keji"), + 经典宋体("https://fycz.lanzous.com/idhI5e6keqf"), + 方正硬笔行书("https://fycz.lanzous.com/ilVh6ep9xja"), + 包图小白体("https://fycz.lanzoui.com/i5qgAicrirc"), + 仓耳非白W02("https://fycz.lanzoui.com/iHwRnicriuf"), + 仓耳舒圆体W02("https://fycz.lanzoui.com/i3GVPicrj3e"), + 仓耳与墨W02("https://fycz.lanzoui.com/ivhv9icrj7i"), + 方正仿宋简体("https://fycz.lanzoui.com/iEcCHicrjef"), + 方正黑体简体("https://fycz.lanzoui.com/iw8kKicrjij"), + 方正书宋简体("https://fycz.lanzoui.com/i5976icrjmd"), + 品如手写体("https://fycz.lanzoui.com/iZccuicrjyf"), + 千图小兔体("https://fycz.lanzoui.com/iOONMicrkda"), + 手书体("https://fycz.lanzoui.com/iqbmdicrkvi"), + 演示春风楷("https://fycz.lanzoui.com/ioRJSicrldg"), + 演示秋鸿楷("https://fycz.lanzoui.com/i8qnzicrlsb"), + 演示夏行楷("https://fycz.lanzoui.com/iyYUTicrm6f"), + 演示悠然小楷("https://fycz.lanzoui.com/ikKq7icrmrg"), + 杨任东竹石体("https://fycz.lanzoui.com/iiWdVicrnbg"), + 站酷仓耳渔阳体("https://fycz.lanzoui.com/if5weicrnje"), + 迷你隶书( "https://fycz.lanzous.com/ihaXVe6kekj"), + 方正黄草("https://fycz.lanzous.com/iQg67e6keed"); public String downloadPath; - Font(String fileName, String downloadPath) { - this.fileName = fileName; + Font(String downloadPath) { this.downloadPath = downloadPath; } diff --git a/app/src/main/java/xyz/fycz/myreader/enums/Language.java b/app/src/main/java/xyz/fycz/myreader/enums/Language.java index 5948245..4943bc9 100644 --- a/app/src/main/java/xyz/fycz/myreader/enums/Language.java +++ b/app/src/main/java/xyz/fycz/myreader/enums/Language.java @@ -1,13 +1,10 @@ package xyz.fycz.myreader.enums; -/** - * Created by zhao on 2016/11/3. - */ - public enum Language { - simplified,//简体中文 - traditional;//繁体中文 + normal, + simplified,//繁转简 + traditional;//简转繁 Language() { diff --git a/app/src/main/java/xyz/fycz/myreader/enums/ReadStyle.java b/app/src/main/java/xyz/fycz/myreader/enums/ReadStyle.java index 7103448..3457355 100644 --- a/app/src/main/java/xyz/fycz/myreader/enums/ReadStyle.java +++ b/app/src/main/java/xyz/fycz/myreader/enums/ReadStyle.java @@ -1,16 +1,13 @@ package xyz.fycz.myreader.enums; -/** - * Created by zhao on 2016/11/3. - */ - public enum ReadStyle { protectedEye,//护眼 common,//普通 blueDeep,//深蓝 leather,//羊皮纸 - breen;// + breen,// + custom; ReadStyle() { } diff --git a/app/src/main/java/xyz/fycz/myreader/greendao/GreenDaoManager.java b/app/src/main/java/xyz/fycz/myreader/greendao/GreenDaoManager.java index dcac87c..d7203e2 100644 --- a/app/src/main/java/xyz/fycz/myreader/greendao/GreenDaoManager.java +++ b/app/src/main/java/xyz/fycz/myreader/greendao/GreenDaoManager.java @@ -11,6 +11,8 @@ import xyz.fycz.myreader.greendao.util.MySQLiteOpenHelper; public class GreenDaoManager { private static GreenDaoManager instance; private static DaoMaster daoMaster; + private DaoSession mDaoSession; + private static MySQLiteOpenHelper mySQLiteOpenHelper; public static GreenDaoManager getInstance() { @@ -23,12 +25,15 @@ public class GreenDaoManager { public GreenDaoManager(){ mySQLiteOpenHelper = new MySQLiteOpenHelper(MyApplication.getmContext(), "read" , null); daoMaster = new DaoMaster(mySQLiteOpenHelper.getWritableDatabase()); + mDaoSession = daoMaster.newSession(); } - + public static DaoSession getDaoSession() { + return getInstance().mDaoSession; + } public DaoSession getSession(){ - return daoMaster.newSession(); + return mDaoSession; } } diff --git a/app/src/main/java/xyz/fycz/myreader/greendao/entity/Book.java b/app/src/main/java/xyz/fycz/myreader/greendao/entity/Book.java index ec02228..9f6f3f5 100644 --- a/app/src/main/java/xyz/fycz/myreader/greendao/entity/Book.java +++ b/app/src/main/java/xyz/fycz/myreader/greendao/entity/Book.java @@ -2,11 +2,15 @@ package xyz.fycz.myreader.greendao.entity; import androidx.annotation.Nullable; + +import com.google.gson.Gson; + import org.greenrobot.greendao.annotation.Entity; import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Transient; import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.util.SharedPreUtils; import java.io.Serializable; import java.util.Objects; @@ -32,7 +36,6 @@ public class Book implements Serializable { private String author;//作者 @Nullable private String type;//类型(本地书籍为:本地书籍) - private String updateDate;//更新时间 @Nullable private String newestChapterId;//最新章节id @@ -65,15 +68,19 @@ public class Book implements Serializable { private int groupSort;//分组排序 - @Generated(hash = 170911086) - public Book(String id, String name, String chapterUrl, String infoUrl, - String imgUrl, String desc, String author, String type, - String updateDate, String newestChapterId, String newestChapterTitle, - String newestChapterUrl, String historyChapterId, - int histtoryChapterNum, int sortCode, int noReadNum, - int chapterTotalNum, int lastReadPosition, String source, - boolean isCloseUpdate, boolean isDownLoadAll, String groupId, - int groupSort) { + private String tag; + + private Boolean replaceEnable = SharedPreUtils.getInstance().getBoolean("replaceEnableDefault", true); + + private long lastReadTime; + + @Generated(hash = 1269102537) + public Book(String id, String name, String chapterUrl, String infoUrl, String imgUrl, String desc, + String author, String type, String updateDate, String newestChapterId, String newestChapterTitle, + String newestChapterUrl, String historyChapterId, int histtoryChapterNum, int sortCode, int noReadNum, + int chapterTotalNum, int lastReadPosition, String source, boolean isCloseUpdate, + boolean isDownLoadAll, String groupId, int groupSort, String tag, Boolean replaceEnable, + long lastReadTime) { this.id = id; this.name = name; this.chapterUrl = chapterUrl; @@ -97,37 +104,27 @@ public class Book implements Serializable { this.isDownLoadAll = isDownLoadAll; this.groupId = groupId; this.groupSort = groupSort; + this.tag = tag; + this.replaceEnable = replaceEnable; + this.lastReadTime = lastReadTime; } - public Book(Book book) { - this.id = book.id; - this.name = book.name; - this.chapterUrl = book.chapterUrl; - this.infoUrl = book.infoUrl; - this.imgUrl = book.imgUrl; - this.desc = book.desc; - this.author = book.author; - this.type = book.type; - this.updateDate = book.updateDate; - this.newestChapterId = book.newestChapterId; - this.newestChapterTitle = book.newestChapterTitle; - this.newestChapterUrl = book.newestChapterUrl; - this.historyChapterId = book.historyChapterId; - this.histtoryChapterNum = book.histtoryChapterNum; - this.sortCode = book.sortCode; - this.noReadNum = book.noReadNum; - this.chapterTotalNum = book.chapterTotalNum; - this.lastReadPosition = book.lastReadPosition; - this.source = book.source; - this.isCloseUpdate = book.isCloseUpdate; - this.isDownLoadAll = book.isDownLoadAll; - this.groupId = book.groupId; - this.groupSort = book.groupSort; - } @Generated(hash = 1839243756) public Book() { } + + @Override + public Object clone() { + try { + Gson gson = new Gson(); + String json = gson.toJson(this); + return gson.fromJson(json, Book.class); + } catch (Exception ignored) { + } + return this; + } + public String getId() { return this.id; } @@ -264,33 +261,6 @@ public class Book implements Serializable { } - @Override - public String toString() { - return "{\n" + - "id='" + id + '\'' + - ",\nname='" + name + '\'' + - ",\nchapterUrl='" + chapterUrl + '\'' + - ",\nimgUrl='" + imgUrl + '\'' + - ",\ndesc='" + desc + '\'' + - ",\nauthor='" + author + '\'' + - ",\ntype='" + type + '\'' + - ",\nupdateDate='" + updateDate + '\'' + - ",\nnewestChapterId='" + newestChapterId + '\'' + - ",\nnewestChapterTitle='" + newestChapterTitle + '\'' + - ",\nnewestChapterUrl='" + newestChapterUrl + '\'' + - ",\nhistoryChapterId='" + historyChapterId + '\'' + - ",\nhisttoryChapterNum='" + histtoryChapterNum + '\'' + - ",\nsortCode='" + sortCode + '\'' + - ",\nnoReadNum='" + noReadNum + '\'' + - ",\nchapterTotalNum='" + chapterTotalNum + '\'' + - ",\nlastReadPosition='" + lastReadPosition + '\'' + - ",\nsource='" + source + '\'' + - ",\nisCloseUpdate='" + isCloseUpdate + '\'' + - ",\nisDownLoadAll='" + isDownLoadAll + '\'' + - ",\ngroupId='" + groupId + '\'' + - "\n}"; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -324,11 +294,34 @@ public class Book implements Serializable { } + public String getTag() { + return this.tag; + } + + + public void setTag(String tag) { + this.tag = tag; + } + + + public Boolean getReplaceEnable() { + return this.replaceEnable; + } + + public void setReplaceEnable(Boolean replaceEnable) { + this.replaceEnable = replaceEnable; + } + public long getLastReadTime() { + return this.lastReadTime; + } + public void setLastReadTime(long lastReadTime) { + this.lastReadTime = lastReadTime; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/greendao/entity/ReplaceRuleBean.java b/app/src/main/java/xyz/fycz/myreader/greendao/entity/ReplaceRuleBean.java new file mode 100644 index 0000000..321a7d2 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/greendao/entity/ReplaceRuleBean.java @@ -0,0 +1,169 @@ +package xyz.fycz.myreader.greendao.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.OrderBy; +import org.greenrobot.greendao.annotation.Transient; + +import java.util.regex.Pattern; + +/** + * Created by GKF on 2018/2/7. + * 阅读内容替换规则 + */ +@Entity +public class ReplaceRuleBean implements Parcelable { + @Id(autoincrement = true) + private Long id; + //描述 + private String replaceSummary; + //替换规则 + private String regex; + //替换为 + private String replacement; + //作用于 + private String useTo; + + private Boolean enable; + + private Boolean isRegex; + + @OrderBy + private int serialNumber; + + private ReplaceRuleBean(Parcel in) { + id = in.readLong(); + regex = in.readString(); + replacement = in.readString(); + replaceSummary = in.readString(); + useTo = in.readString(); + enable = in.readByte() != 0; + serialNumber = in.readInt(); + isRegex = in.readByte() != 0; + } + + @Generated(hash = 1896663649) + public ReplaceRuleBean(Long id, String replaceSummary, String regex, String replacement, + String useTo, Boolean enable, Boolean isRegex, int serialNumber) { + this.id = id; + this.replaceSummary = replaceSummary; + this.regex = regex; + this.replacement = replacement; + this.useTo = useTo; + this.enable = enable; + this.isRegex = isRegex; + this.serialNumber = serialNumber; + } + + @Generated(hash = 582692869) + public ReplaceRuleBean() { + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeLong(id); + parcel.writeString(regex); + parcel.writeString(replacement); + parcel.writeString(replaceSummary); + parcel.writeString(useTo); + parcel.writeByte((byte) (enable ? 1 : 0)); + parcel.writeInt(serialNumber); + parcel.writeByte((byte) (isRegex ? 1 : 0)); + } + + @Transient + public static final Creator CREATOR = new Creator() { + @Override + public ReplaceRuleBean createFromParcel(Parcel in) { + return new ReplaceRuleBean(in); + } + + @Override + public ReplaceRuleBean[] newArray(int size) { + return new ReplaceRuleBean[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + public String getReplaceSummary() { + return this.replaceSummary; + } + + public void setReplaceSummary(String replaceSummary) { + this.replaceSummary = replaceSummary; + } + + public String getRegex() { + return this.regex; + } + + public String getFixedRegex() { + if (getIsRegex()) + return this.regex; + else + return Pattern.quote(regex); + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public String getReplacement() { + return this.replacement; + } + + public void setReplacement(String replacement) { + this.replacement = replacement; + } + + public Boolean getEnable() { + if (enable == null) { + return false; + } + return this.enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public int getSerialNumber() { + return this.serialNumber; + } + + public void setSerialNumber(int serialNumber) { + this.serialNumber = serialNumber; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUseTo() { + return this.useTo; + } + + public void setUseTo(String useTo) { + this.useTo = useTo; + } + + public Boolean getIsRegex() { + return isRegex == null ? true : isRegex; + } + + public void setIsRegex(Boolean isRegex) { + this.isRegex = isRegex; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/model/ReplaceRuleManager.java b/app/src/main/java/xyz/fycz/myreader/model/ReplaceRuleManager.java new file mode 100644 index 0000000..d9ef951 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/model/ReplaceRuleManager.java @@ -0,0 +1,114 @@ +package xyz.fycz.myreader.model; + +import android.text.TextUtils; + + +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import xyz.fycz.myreader.greendao.GreenDaoManager; +import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; +import xyz.fycz.myreader.greendao.gen.ReplaceRuleBeanDao; +import xyz.fycz.myreader.util.utils.GsonUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +/** + * Created by GKF on 2018/2/12. + * 替换规则管理 + */ + +public class ReplaceRuleManager { + private static List replaceRuleBeansEnabled; + + public static List getEnabled() { + if (replaceRuleBeansEnabled == null) { + replaceRuleBeansEnabled = GreenDaoManager.getDaoSession() + .getReplaceRuleBeanDao().queryBuilder() + .where(ReplaceRuleBeanDao.Properties.Enable.eq(true)) + .orderAsc(ReplaceRuleBeanDao.Properties.SerialNumber) + .list(); + } + return replaceRuleBeansEnabled; + } + + public static Single> getAll() { + return Single.create((SingleOnSubscribe>) emitter -> emitter.onSuccess(GreenDaoManager.getDaoSession() + .getReplaceRuleBeanDao().queryBuilder() + .orderAsc(ReplaceRuleBeanDao.Properties.SerialNumber) + .list())).compose(RxUtils::toSimpleSingle); + } + + public static Single saveData(ReplaceRuleBean replaceRuleBean) { + return Single.create((SingleOnSubscribe) emitter -> { + if (replaceRuleBean.getSerialNumber() == 0) { + replaceRuleBean.setSerialNumber((int) (GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().queryBuilder().count() + 1)); + } + GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().insertOrReplace(replaceRuleBean); + refreshDataS(); + emitter.onSuccess(true); + }).compose(RxUtils::toSimpleSingle); + } + + public static void delData(ReplaceRuleBean replaceRuleBean) { + GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().delete(replaceRuleBean); + refreshDataS(); + } + + public static void addDataS(List replaceRuleBeans) { + if (replaceRuleBeans != null && replaceRuleBeans.size() > 0) { + GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().insertOrReplaceInTx(replaceRuleBeans); + refreshDataS(); + } + } + + public static void delDataS(List replaceRuleBeans) { + for (ReplaceRuleBean replaceRuleBean : replaceRuleBeans) { + GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().delete(replaceRuleBean); + } + refreshDataS(); + } + + private static void refreshDataS() { + replaceRuleBeansEnabled = GreenDaoManager.getDaoSession() + .getReplaceRuleBeanDao().queryBuilder() + .where(ReplaceRuleBeanDao.Properties.Enable.eq(true)) + .orderAsc(ReplaceRuleBeanDao.Properties.SerialNumber) + .list(); + } + + public static Observable importReplaceRule(String text) { + if (TextUtils.isEmpty(text)) return null; + text = text.trim(); + if (text.length() == 0) return null; + if (StringUtils.isJsonType(text)) { + return importReplaceRuleO(text) + .compose(RxUtils::toSimpleSingle); + } + /*if (NetworkUtils.isUrl(text)) { + return BaseModelImpl.getInstance().getRetrofitString(StringUtils.getBaseUrl(text), "utf-8") + .create(IHttpGetApi.class) + .get(text, AnalyzeHeaders.getMap(null)) + .flatMap(rsp -> importReplaceRuleO(rsp.body())) + .compose(RxUtils::toSimpleSingle); + }*/ + return Observable.error(new Exception("不是Json或Url格式")); + } + + private static Observable importReplaceRuleO(String json) { + return Observable.create(e -> { + try { + List replaceRuleBeans = GsonUtils.parseJArray(json, ReplaceRuleBean.class); + addDataS(replaceRuleBeans); + e.onNext(true); + } catch (Exception e1) { + e1.printStackTrace(); + e.onNext(false); + } + e.onComplete(); + }); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java b/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java index 3e97682..a09b378 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java +++ b/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java @@ -95,7 +95,7 @@ public class SearchEngine { } /** - * 搜索关键字 + * 搜索关键字(模糊搜索) * * @param keyword */ @@ -108,11 +108,12 @@ public class SearchEngine { searchSuccessNum = 0; searchSiteIndex = -1; searchFinishNum = 0; - for (int i = 0; i < threadsNum; i++) { + for (int i = 0; i < Math.min(mSourceList.size(), threadsNum); i++) { searchOnEngine(keyword); } } + /** * 根据书名和作者搜索书籍 * @@ -128,7 +129,7 @@ public class SearchEngine { searchSuccessNum = 0; searchSiteIndex = -1; searchFinishNum = 0; - for (int i = 0; i < threadsNum; i++) { + for (int i = 0; i < Math.min(mSourceList.size(), threadsNum); i++) { searchOnEngine(title, author); } } diff --git a/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java b/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java new file mode 100644 index 0000000..be9c073 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java @@ -0,0 +1,333 @@ +package xyz.fycz.myreader.model.backup; + +import io.reactivex.annotations.NonNull; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.model.storage.Backup; +import xyz.fycz.myreader.model.storage.Restore; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.util.*; +import xyz.fycz.myreader.util.utils.FileUtils; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * @author fengyue + * @date 2020/4/26 11:03 + */ +public class UserService { + /** + * 登录 + * @param userLoginInfo 用户名输入的用户名和密码等登录信息 + * @return 是否成功登录 + */ + public static void login(final Map userLoginInfo, final ResultCallback resultCallback) { + MyApplication.getApplication().newThread(() -> { + HttpURLConnection conn = null; + try { + URL url = new URL(URLCONST.APP_WEB_URL + "login"); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(60 * 1000); + conn.setReadTimeout(60 * 1000); + conn.setDoInput(true); + conn.setDoOutput(true); + String params = "username=" + userLoginInfo.get("loginName") + + "&password=" + userLoginInfo.get("loginPwd") + makeSignalParam(); + // 获取URLConnection对象对应的输出流 + PrintWriter out = new PrintWriter(conn.getOutputStream()); + // 发送请求参数 + out.print(params); + // flush输出流的缓冲 + out.flush(); + InputStream in = conn.getInputStream(); + BufferedReader bw = new BufferedReader(new InputStreamReader(in, "utf-8")); + StringBuilder sb = new StringBuilder(); + String line = bw.readLine(); + while (line != null) { + sb.append(line); + line = bw.readLine(); + } + resultCallback.onFinish(sb.toString(), 1); + } catch (IOException e) { + e.printStackTrace(); + resultCallback.onError(e); + }finally { + if (conn != null) { + conn.disconnect(); + } + } + }); + } + + public static void register(final Map userRegisterInfo, final ResultCallback resultCallback) { + MyApplication.getApplication().newThread(() -> { + HttpURLConnection conn = null; + try { + URL url = new URL(URLCONST.APP_WEB_URL + "reg"); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoInput(true); + conn.setDoOutput(true); + String params = "username=" + userRegisterInfo.get("username") + "&password=" + + CyptoUtils.encode(APPCONST.KEY, userRegisterInfo.get("password")) + "&key=" + + CyptoUtils.encode(APPCONST.KEY, APPCONST.publicKey) + makeSignalParam(); + // 获取URLConnection对象对应的输出流 + PrintWriter out = new PrintWriter(conn.getOutputStream()); + // 发送请求参数 + out.print(params); + // flush输出流的缓冲 + out.flush(); + BufferedReader bw = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder sb = new StringBuilder(); + String line = bw.readLine(); + while (line != null) { + sb.append(line); + line = bw.readLine(); + } + resultCallback.onFinish(sb.toString(), 1); + } catch (IOException e) { + e.printStackTrace(); + resultCallback.onError(e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + + }); + } + + /** + * 写配置 + * @param userLoginInfo + * @return + */ + public static boolean writeConfig(Map userLoginInfo){ + FileOutputStream fos = null; + try { + fos = MyApplication.getApplication().openFileOutput("userConfig.fy", MyApplication.getApplication().MODE_PRIVATE); + String userInfo = "username='" + userLoginInfo.get("loginName") + "',\npassword='" + userLoginInfo.get("loginPwd") + "'"; + byte[] bs = userInfo.getBytes(); + fos.write(bs); + //写完后一定要刷新 + fos.flush(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + IOUtils.close(fos); + } + } + + /** + * 读配置 + * @return + */ + public static Map readConfig(){ + File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy"); + if (!file.exists()){ + return null; + } + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file)); + String tem; + StringBuilder config = new StringBuilder(); + while ((tem = br.readLine()) != null){ + config.append(tem); + } + String[] user = config.toString().split(","); + String userName = user[0].substring(user[0].indexOf("'") + 1, user[0].lastIndexOf("'")); + String password = user[1].substring(user[1].indexOf("'") + 1, user[1].lastIndexOf("'")); + Map userInfo = new HashMap<>(); + userInfo.put("userName", userName); + userInfo.put("password", password); + return userInfo; + } catch (IOException e) { + e.printStackTrace(); + }finally { + IOUtils.close(br); + } + return null; + } + + public static void writeUsername(String username){ + File file = FileUtils.getFile(APPCONST.QQ_DATA_DIR + "user"); + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter(file)); + bw.write(username); + bw.flush(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + IOUtils.close(bw); + } + } + + public static String readUsername(){ + File file = new File(APPCONST.QQ_DATA_DIR + "user"); + if (!file.exists()){ + return ""; + } + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file)); + return br.readLine(); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } finally { + IOUtils.close(br); + } + } + + /** + * 网络备份 + * @return + */ + public static void webBackup(ResultCallback rc){ + Map userInfo = readConfig(); + if (userInfo == null){ + rc.onFinish(false, 0); + } + Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.FILE_DIR + "webBackup/", new Backup.CallBack() { + @Override + public void backupSuccess() { + MyApplication.getApplication().newThread(() ->{ + File inputFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup"); + if (!inputFile.exists()) { + rc.onFinish(false, 0); + } + File zipFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup.zip"); + FileInputStream fis = null; + HttpURLConnection conn = null; + try { + //压缩文件 + ZipUtils.zipFile(inputFile, zipFile); + fis = new FileInputStream(zipFile); + URL url = new URL(URLCONST.APP_WEB_URL + "bak?username=" + userInfo.get("userName") + + makeSignalParam()); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-type", "multipart/form-data"); + conn.setDoInput(true); + conn.setDoOutput(true); + OutputStream out = conn.getOutputStream(); + byte[] bytes = new byte[1024]; + int len = -1; + while ((len = fis.read(bytes)) != -1){ + out.write(bytes, 0, len); + } + out.flush(); + zipFile.delete(); + BufferedReader bw = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder sb = new StringBuilder(); + String line = bw.readLine(); + while (line != null) { + sb.append(line); + line = bw.readLine(); + } + String[] info = sb.toString().split(":"); + int code = Integer.parseInt(info[0].trim()); + rc.onFinish(code == 104, 0); + } catch (Exception e) { + e.printStackTrace(); + rc.onError(e); + } finally { + IOUtils.close(fis); + if (conn != null) { + conn.disconnect(); + } + } + }); + } + + @Override + public void backupError(@NonNull String msg) { + ToastUtils.showError(msg); + rc.onFinish(false, 0); + } + }, false); + + } + + /** + * 网络恢复 + * @return + */ + public static void webRestore(ResultCallback rc){ + Map userInfo = readConfig(); + if (userInfo == null){ + rc.onFinish(false, 0); + } + FileOutputStream fos = null; + File zipFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup.zip"); + HttpURLConnection conn = null; + try { + URL url = new URL(URLCONST.APP_WEB_URL + "ret?username=" + userInfo.get("userName") + + makeSignalParam()); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoInput(true); + InputStream is = conn.getInputStream(); + fos = new FileOutputStream(zipFile); + //一边读,一边写 + byte[] bytes = new byte[512]; + int readCount = 0; + while ((readCount = is.read(bytes)) != -1) { + fos.write(bytes,0, readCount); + } + //刷新,输出流一定要刷新 + fos.flush(); + if (zipFile.length() == 0){ + zipFile.delete(); + rc.onFinish(false, 0); + } + ZipUtils.unzipFile(zipFile.getAbsolutePath(), APPCONST.FILE_DIR); + Restore.INSTANCE.restore(APPCONST.FILE_DIR + "webBackup/", new Restore.CallBack() { + @Override + public void restoreSuccess() { + zipFile.delete(); + rc.onFinish(true, 0); + } + + @Override + public void restoreError(@NonNull String msg) { + ToastUtils.showError(msg); + rc.onFinish(false, 0); + } + }); + } catch (Exception e) { + e.printStackTrace(); + rc.onError(e); + }finally { + IOUtils.close(fos); + if (conn != null) { + conn.disconnect(); + } + } + } + + + private static String makeSignalParam(){ + return "&signal=" + AppInfoUtils.getSingInfo(MyApplication.getmContext(), + MyApplication.getApplication().getPackageName(), AppInfoUtils.SHA1); + } + + /** + * 判断是否登录 + * @return + */ + public static boolean isLogin(){ + File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy"); + return file.exists(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt index 5193400..1fba104 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt +++ b/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt @@ -39,6 +39,7 @@ object Backup { "myBookMark.json", "myBookGroup.json", "setting.json", + "readStyles.json", "config.xml" ) } @@ -86,9 +87,22 @@ object Backup { .writeText(json) } } - val json = GSON.toJson(SysManager.getSetting()) - FileUtils.getFile(backupPath + File.separator + "setting.json") - .writeText(json) + + + try { + val setting = SysManager.getNewSetting() + val readStyles = setting.readStyles + val readStylesJson = GSON.toJson(readStyles) + setting.readStyles = null + val settingJson = GSON.toJson(setting) + FileUtils.getFile(backupPath + File.separator + "setting.json") + .writeText(settingJson) + FileUtils.getFile(backupPath + File.separator + "readStyles.json") + .writeText(readStylesJson) + } catch (e: Exception) { + e.printStackTrace() + } + Preferences.getSharedPreferences(context, backupPath, "config")?.let { sp -> val edit = sp.edit() SharedPreUtils.getInstance().all.map { diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt index f39c9a5..e776b37 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt +++ b/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt @@ -1,18 +1,14 @@ package xyz.fycz.myreader.model.storage import android.app.Activity -import android.app.Activity.RESULT_OK -import android.content.Intent import android.net.Uri import android.text.TextUtils -import androidx.core.content.ContextCompat import androidx.documentfile.provider.DocumentFile import io.reactivex.Single import io.reactivex.SingleEmitter import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import org.jetbrains.anko.alert -import org.jetbrains.anko.toast +import xyz.fycz.myreader.application.SysManager import xyz.fycz.myreader.base.observer.MySingleObserver import xyz.fycz.myreader.common.APPCONST import xyz.fycz.myreader.model.storage.WebDavHelp.getWebDavFileNames @@ -47,6 +43,7 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack { } override fun restoreSuccess() { + SysManager.regetmSetting() ToastUtils.showSuccess("恢复成功") } diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt index 5215a73..8d996b2 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt +++ b/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt @@ -10,6 +10,7 @@ import io.reactivex.schedulers.Schedulers import xyz.fycz.myreader.application.MyApplication import xyz.fycz.myreader.application.SysManager import xyz.fycz.myreader.base.observer.MySingleObserver +import xyz.fycz.myreader.entity.ReadStyle import xyz.fycz.myreader.entity.Setting import xyz.fycz.myreader.greendao.GreenDaoManager import xyz.fycz.myreader.greendao.entity.Book @@ -94,9 +95,16 @@ object Restore { e.printStackTrace() } try { - val file = FileUtils.getFile(path + File.separator + "setting.json") - val json = file.readText() - SysManager.saveSetting(GSON.fromJsonObject(json)) + val settingFile = FileUtils.getFile(path + File.separator + "setting.json") + val settingJson = settingFile.readText() + val readStyleFile = FileUtils.getFile(path + File.separator + "readStyles.json") + val readStylesJson = readStyleFile.readText() + val readStyles = GSON.fromJsonObject>(readStylesJson) + val setting = GSON.fromJsonObject(settingJson) + if (setting != null) { + setting.readStyles = readStyles + } + SysManager.saveSetting(setting) } catch (e: Exception) { e.printStackTrace() } diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt index f8ac305..0d1e423 100644 --- a/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt +++ b/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt @@ -69,7 +69,7 @@ object WebDavHelp { fun showRestoreDialog(context: Context, names: ArrayList, callBack: Restore.CallBack?): Boolean { return if (names.isNotEmpty()) { context.selector(title = "选择恢复文件", items = names) { _, index -> - if (index in 0 until names.size) { + if (index in 0 until 30.coerceAtLeast(names.size)) { restoreWebDav(names[index], callBack) } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java index 70b1ed9..1edcb15 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java @@ -33,6 +33,8 @@ public class AboutActivity extends BaseActivity { CardView vmUpdate; @BindView(R.id.vw_update_log) CardView vmUpdateLog; + @BindView(R.id.vw_qq) + CardView vmQQ; @BindView(R.id.vw_git) CardView vmGit; @BindView(R.id.vw_disclaimer) @@ -60,10 +62,29 @@ public class AboutActivity extends BaseActivity { @Override protected void initClick() { super.initClick(); + ClipboardManager mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + vmAuthor.setOnClickListener(v -> { + //数据 + ClipData mClipData = ClipData.newPlainText("Label", "fy@fycz.xyz"); + //把数据设置到剪切板上 + assert mClipboardManager != null; + mClipboardManager.setPrimaryClip(mClipData); + ToastUtils.showSuccess("邮箱复制成功!"); + }); vmShare.setOnClickListener(v -> ShareUtils.share(this, getString(R.string.share_text) + - SharedPreUtils.getInstance().getString(getString(R.string.downloadLink, URLCONST.LAN_ZOUS_URL)))); + SharedPreUtils.getInstance().getString(getString(R.string.downloadLink), URLCONST.LAN_ZOUS_URL))); vmUpdate.setOnClickListener(v -> MyApplication.checkVersionByServer(this, true, null)); vmUpdateLog.setOnClickListener(v -> DialogCreator.createAssetTipDialog(this, "更新日志", "updatelog.fy")); + vmQQ.setOnClickListener(v -> { + if (!MyApplication.joinQQGroup(this,"8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")){ + //数据 + ClipData mClipData = ClipData.newPlainText("Label", "1085028304"); + //把数据设置到剪切板上 + assert mClipboardManager != null; + mClipboardManager.setPrimaryClip(mClipData); + ToastUtils.showError("未安装手Q或安装的版本不支持!\n已复制QQ群号,您可自行前往QQ添加!"); + } + }); vmGit.setOnClickListener(v -> openIntent(Intent.ACTION_VIEW, getString(R.string.this_github_url))); vmDisclaimer.setOnClickListener(v -> DialogCreator.createAssetTipDialog(this, "免责声明", "disclaimer.fy")); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java index 888f3d7..973ee9f 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java @@ -2,27 +2,61 @@ package xyz.fycz.myreader.ui.activity; import android.annotation.SuppressLint; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.*; + import androidx.appcompat.widget.Toolbar; +import androidx.core.content.FileProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import butterknife.BindView; import butterknife.OnClick; + import com.bumptech.glide.Glide; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.RequestOptions; +import com.google.zxing.EncodeHintType; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import cn.bingoogolapple.qrcode.zxing.QRCodeEncoder; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.annotations.NonNull; +import xyz.fycz.myreader.BuildConfig; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.base.observer.MySingleObserver; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.entity.SharedBook; +import xyz.fycz.myreader.util.IOUtils; +import xyz.fycz.myreader.util.SharedPreUtils; +import xyz.fycz.myreader.util.utils.BitmapUtil; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.GsonExtensionsKt; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.util.utils.ScreenUtils; +import xyz.fycz.myreader.util.utils.StringUtils; import xyz.fycz.myreader.webapi.callback.ResultCallback; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.ui.dialog.SourceExchangeDialog; @@ -43,7 +77,11 @@ import xyz.fycz.myreader.util.utils.NetworkUtils; import xyz.fycz.myreader.webapi.CommonApi; import xyz.fycz.myreader.widget.CoverImageView; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; +import java.util.List; /** * @author fengyue @@ -52,8 +90,8 @@ import java.util.ArrayList; public class BookDetailedActivity extends BaseActivity { @BindView(R.id.book_detail_iv_cover) CoverImageView mIvCover; - /* @BindView(R.id.book_detail_iv_blur_cover) - ImageView mIvBlurCover;*/ + /* @BindView(R.id.book_detail_iv_blur_cover) + ImageView mIvBlurCover;*/ @BindView(R.id.book_detail_tv_author) TextView mTvAuthor; @BindView(R.id.book_detail_tv_type) @@ -188,7 +226,7 @@ public class BookDetailedActivity extends BaseActivity { break; } } - }else { + } else { aBooks.get(sourceIndex).setNewestChapterId("true"); } } @@ -227,7 +265,7 @@ public class BookDetailedActivity extends BaseActivity { //换源对话框 mSourceDialog.setOnSourceChangeListener((bean, pos) -> { - Book bookTem = new Book(mBook); + Book bookTem = (Book) mBook.clone(); bookTem.setChapterUrl(bean.getChapterUrl()); if (!StringHelper.isEmpty(bean.getImgUrl())) { bookTem.setImgUrl(bean.getImgUrl()); @@ -235,7 +273,7 @@ public class BookDetailedActivity extends BaseActivity { if (!StringHelper.isEmpty(bean.getType())) { bookTem.setType(bean.getType()); } - if (!StringHelper.isEmpty(bean.getDesc())){ + if (!StringHelper.isEmpty(bean.getDesc())) { bookTem.setDesc(bean.getDesc()); } bookTem.setSource(bean.getSource()); @@ -522,6 +560,9 @@ public class BookDetailedActivity extends BaseActivity { }*/ mSourceDialog.show(); break; + case R.id.action_share: + shareBook(); + break; case R.id.action_reload: //重新加载 mHandler.sendEmptyMessage(1); break; @@ -607,4 +648,172 @@ public class BookDetailedActivity extends BaseActivity { } } } + + /** + * 分享书籍 + */ + private void shareBook() { + ToastUtils.showInfo("正在生成分享图片"); + Single.create((SingleOnSubscribe) emitter -> { + // 使用url + String url = SharedPreUtils.getInstance().getString(getString(R.string.downloadLink), URLCONST.LAN_ZOUS_URL); + if (url == null) + url = ""; + + int maxLength = 1273 - 1 - url.length(); + + SharedBook sharedBook = SharedBook.bookToSharedBook(mBook); + + url = url + "#" + GsonExtensionsKt.getGSON().toJson(sharedBook); + + Log.d("QRcode", "Length=" + url.length() + "\n" + url); + + Bitmap bitmap; + QRCodeEncoder.HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); + bitmap = QRCodeEncoder.syncEncodeQRCode(url, 360); + QRCodeEncoder.HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + + File share = makeShareFile(bitmap); + if (share == null) { + ToastUtils.showError("分享图片生成失败"); + return; + } + emitter.onSuccess(share); + }).compose(RxUtils::toSimpleSingle) + .subscribe(new MySingleObserver() { + @Override + public void onSuccess(@NonNull File File) { + share(File); + } + }); + } + + /** + * 生成分享图片 + * @param QRCode + * @return + */ + + private File makeShareFile(Bitmap QRCode) { + FileOutputStream fos = null; + try { + Bitmap back = BitmapFactory.decodeStream(getResources().getAssets().open("share.png")).copy(Bitmap.Config.ARGB_8888, true); + int backWidth = back.getWidth(); + int backHeight = back.getHeight(); + + int margin = 60; + + int marginTop = 24; + + mIvCover.setDrawingCacheEnabled(true); + Bitmap img = Bitmap.createBitmap(mIvCover.getDrawingCache()).copy(Bitmap.Config.ARGB_8888, true); + mIvCover.setDrawingCacheEnabled(false); + img = BitmapUtil.getBitmap(img, 152, 209); + + Canvas cv = new Canvas(back); + cv.drawBitmap(img, margin, margin + marginTop * 2, null); + + TextPaint textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setFilterBitmap(true); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(40); + + String name = TextUtils.ellipsize(mBook.getName(), textPaint, backWidth - margin + marginTop * 3 - img.getWidth(), TextUtils.TruncateAt.END).toString(); + cv.drawText(name, margin + marginTop + img.getWidth(), margin + marginTop * 4, textPaint); + + + textPaint.setColor(getResources().getColor(R.color.origin)); + textPaint.setTextSize(32); + cv.drawText(mBook.getAuthor(), margin + marginTop + img.getWidth(), margin + marginTop * 6, textPaint); + + textPaint.setColor(Color.BLACK); + cv.drawText(mBook.getType() == null ? "" : mBook.getType(), margin + marginTop + img.getWidth(), margin + marginTop * 8, textPaint); + assert mBook.getSource() != null; + cv.drawText("书源:" + BookSource.fromString(mBook.getSource()).text, margin + marginTop + img.getWidth(), margin + marginTop * 10, textPaint); + + int textSize = 35; + int textInterval = textSize / 2; + textPaint.setTextSize(textSize); + + drawDesc(getDescLines(backWidth - margin * 2, textPaint), textPaint, cv, margin + marginTop * 4 + img.getHeight(), margin, textInterval); + + cv.drawBitmap(QRCode, backWidth - QRCode.getWidth(), backHeight - QRCode.getHeight(), null); + + cv.save();// 保存 + cv.restore();// 存储 + + File share = FileUtils.getFile(APPCONST.SHARE_FILE_DIR + mBook.getName() + "_share.png"); + fos = new FileOutputStream(share); + back.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + Log.i("tag", "saveBitmap success: " + share.getAbsolutePath()); + return share; + } catch (Exception e) { + e.printStackTrace(); + ToastUtils.showError(e.getLocalizedMessage()); + return null; + } finally { + IOUtils.close(fos); + } + } + + /** + * 分享生成的图片 + * @param share + */ + private void share(File share) { + //noinspection ResultOfMethodCallIgnored + share.setReadable(true, false); + Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", share); + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intent.EXTRA_STREAM, contentUri); + intent.setType("image/png"); + startActivity(Intent.createChooser(intent, "分享书籍")); + } + + /** + * 绘制简介 + * @param lines + * @param textPaint + * @param canvas + * @param top + * @param left + * @param textInterval + */ + private void drawDesc(List lines, TextPaint textPaint, Canvas canvas, int top, int left, int textInterval) { + float interval = textInterval + textPaint.getTextSize(); + for (String line : lines) { + canvas.drawText(line, left, top, textPaint); + top += interval; + } + } + + /** + * 生成简介lines + * @param width + * @param textPaint + * @return + */ + + private List getDescLines(int width, TextPaint textPaint) { + List lines = new ArrayList<>(); + String desc = StringUtils.halfToFull(" ") + mBook.getDesc(); + int i = 0; + int wordCount = 0; + String subStr = null; + while (desc.length() > 0) { + if (i == 9) { + lines.add(TextUtils.ellipsize(desc, textPaint, width / 1.8f, TextUtils.TruncateAt.END).toString()); + break; + } + wordCount = textPaint.breakText(desc, true, width, null); + subStr = desc.substring(0, wordCount); + lines.add(subStr); + desc = desc.substring(wordCount); + i++; + } + return lines; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java index 98adbf8..f593cf0 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java @@ -37,7 +37,9 @@ import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; import xyz.fycz.myreader.widget.RefreshLayout; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * @author fengyue @@ -160,7 +162,7 @@ public class BookstoreActivity extends BaseActivity { super.initClick(); mBookStoreBookAdapter.setOnItemClickListener((view, pos) -> { Book book = bookList.get(pos); - if (!findCrawler.hasImg()) { + if (!findCrawler.needSearch()) { goToBookDetail(book); } else { if (BookService.getInstance().isBookCollected(book)) { @@ -202,7 +204,7 @@ public class BookstoreActivity extends BaseActivity { protected void processLogic() { super.processLogic(); getData(); - if (findCrawler.hasImg()) { + if (findCrawler.needSearch()) { SharedPreUtils spu = SharedPreUtils.getInstance(); boolean isReadTopTip = spu.getBoolean(getString(R.string.isReadTopTip), false); if (!isReadTopTip) { @@ -224,11 +226,8 @@ public class BookstoreActivity extends BaseActivity { @Override public void onFinish(Object o, int code) { spu.putString(getString(R.string.qdCookie), (String) o); - mBookTypes = ((QiDianMobileRank) findCrawler).getRankTypes(); - curType = mBookTypes.get(0); - mHandler.sendMessage(mHandler.obtainMessage(1)); - page = 1; - getBooksData(); + mBookTypes = findCrawler.getBookTypes(); + initBooks(); } @Override @@ -238,21 +237,17 @@ public class BookstoreActivity extends BaseActivity { } }); } else { - mBookTypes = ((QiDianMobileRank) findCrawler).getRankTypes(); - curType = mBookTypes.get(0); - mHandler.sendMessage(mHandler.obtainMessage(1)); - page = 1; - getBooksData(); + mBookTypes = findCrawler.getBookTypes(); + initBooks(); } + } else if ((mBookTypes = findCrawler.getBookTypes()) != null){ + initBooks(); } else { BookStoreApi.getBookTypeList(findCrawler, new ResultCallback() { @Override public void onFinish(Object o, int code) { mBookTypes = (ArrayList) o; - curType = mBookTypes.get(0); - mHandler.sendMessage(mHandler.obtainMessage(1)); - page = 1; - getBooksData(); + initBooks(); } @Override @@ -262,7 +257,13 @@ public class BookstoreActivity extends BaseActivity { } }); } + } + private void initBooks(){ + curType = mBookTypes.get(0); + mHandler.sendMessage(mHandler.obtainMessage(1)); + page = 1; + getBooksData(); } /** @@ -311,7 +312,7 @@ public class BookstoreActivity extends BaseActivity { } }); } else { - BookStoreApi.getBookRankList(curType.getUrl(), findCrawler, new ResultCallback() { + BookStoreApi.getBookRankList(curType, findCrawler, new ResultCallback() { @Override public void onFinish(Object o, int code) { mHandler.sendMessage(mHandler.obtainMessage(2, o)); @@ -366,7 +367,8 @@ public class BookstoreActivity extends BaseActivity { rvBookList.scrollToPosition(0); } else { this.bookList.addAll(bookList); - mBookStoreBookAdapter.addItems(bookList); + this.bookList = new ArrayList<>(new LinkedHashSet<>(this.bookList));//去重 + mBookStoreBookAdapter.refreshItems(this.bookList); } //刷新动作完成 @@ -402,7 +404,7 @@ public class BookstoreActivity extends BaseActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { - if (findCrawler.hasImg()) { + if (findCrawler.needSearch()) { menu.findItem(R.id.action_tip).setVisible(true); } return true; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/FontsActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/FontsActivity.java index 4127c34..5977a77 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/FontsActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/FontsActivity.java @@ -26,6 +26,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import static xyz.fycz.myreader.util.UriFileUtil.getPath; @@ -81,14 +82,7 @@ public class FontsActivity extends BaseActivity { private void initFonts() { mFonts = new ArrayList<>(); - mFonts.add(Font.默认字体); - mFonts.add(Font.方正楷体); - mFonts.add(Font.经典宋体); - mFonts.add(Font.方正行楷); - mFonts.add(Font.迷你隶书); - mFonts.add(Font.方正黄草); - mFonts.add(Font.方正硬笔行书); - mFonts.add(Font.本地字体); + Collections.addAll(mFonts, Font.values()); } public void saveLocalFont(String path){ diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/LoginActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/LoginActivity.java new file mode 100644 index 0000000..1544bd8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/LoginActivity.java @@ -0,0 +1,231 @@ +package xyz.fycz.myreader.ui.activity; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.appcompat.widget.Toolbar; +import butterknife.BindView; +import com.google.android.material.textfield.TextInputLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.model.backup.UserService; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.ui.dialog.DialogCreator; +import xyz.fycz.myreader.util.CodeUtil; +import xyz.fycz.myreader.util.CyptoUtils; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author fengyue + * @date 2020/9/18 22:27 + */ +public class LoginActivity extends BaseActivity implements TextWatcher { + @BindView(R.id.et_user) + TextInputLayout user; + @BindView(R.id.et_password) + TextInputLayout password; + @BindView(R.id.bt_login) + Button loginBtn; + @BindView(R.id.tv_register) + TextView tvRegister; + @BindView(R.id.et_captcha) + TextInputLayout etCaptcha; + @BindView(R.id.iv_captcha) + ImageView ivCaptcha; + + private String code; + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @SuppressLint("HandlerLeak") + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + loginBtn.setEnabled(true); + break; + case 2: + createCaptcha(); + break; + } + } + }; + + @Override + protected int getContentId() { + return R.layout.activity_login; + } + + @Override + protected void setUpToolbar(Toolbar toolbar) { + super.setUpToolbar(toolbar); + setStatusBarColor(R.color.colorPrimary, true); + getSupportActionBar().setTitle("登录"); + } + + @Override + protected void initData(Bundle savedInstanceState) { + super.initData(savedInstanceState); + } + + @Override + protected void initWidget() { + super.initWidget(); + mHandler.sendMessage(mHandler.obtainMessage(2)); + String username = UserService.readUsername(); + user.getEditText().setText(username); + user.getEditText().requestFocus(username.length()); + //监听内容改变 -> 控制按钮的点击状态 + user.getEditText().addTextChangedListener(this); + password.getEditText().addTextChangedListener(this); + etCaptcha.getEditText().addTextChangedListener(this); + } + + @Override + protected void initClick() { + super.initClick(); + ivCaptcha.setOnClickListener(v -> mHandler.sendMessage(mHandler.obtainMessage(2))); + + loginBtn.setOnClickListener(v -> { + mHandler.sendMessage(mHandler.obtainMessage(2)); + if (!code.toLowerCase().equals(etCaptcha.getEditText().getText().toString().toLowerCase())){ + DialogCreator.createTipDialog(this, "验证码错误!"); + return; + } + if (!NetworkUtils.isNetWorkAvailable()) { + ToastUtils.showError("无网络连接!"); + return; + } + ProgressDialog dialog = DialogCreator.createProgressDialog(this, null, "正在登陆..."); + loginBtn.setEnabled(false); + final String loginName = user.getEditText().getText().toString().trim(); + String loginPwd = password.getEditText().getText().toString(); + final Map userLoginInfo = new HashMap<>(); + userLoginInfo.put("loginName", loginName); + userLoginInfo.put("loginPwd", CyptoUtils.encode(APPCONST.KEY, loginPwd)); + //验证用户名和密码 + UserService.login(userLoginInfo, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + String result = (String) o; + String[] info = result.split(":"); + int resultCode = Integer.parseInt(info[0].trim()); + String resultName = info[1].trim(); + //最后输出结果 + if (resultCode == 102) { + UserService.writeConfig(userLoginInfo); + UserService.writeUsername(loginName); + Intent intent = new Intent(); + intent.putExtra("isLogin", true); + setResult(Activity.RESULT_OK, intent); + finish(); + ToastUtils.showSuccess(resultName); + } else { + mHandler.sendMessage(mHandler.obtainMessage(1)); + dialog.dismiss(); + ToastUtils.showWarring(resultName); + } + + } + + @Override + public void onError(Exception e) { + ToastUtils.showError("登录失败\n" + e.getLocalizedMessage()); + mHandler.sendMessage(mHandler.obtainMessage(1)); + dialog.dismiss(); + } + }); + + }); + + tvRegister.setOnClickListener(v -> { + Intent intent = new Intent(LoginActivity.this, RegisterActivity.class); + startActivity(intent); + }); + } + + public void createCaptcha() { + code = CodeUtil.getInstance().createCode(); + Bitmap codeBitmap = CodeUtil.getInstance().createBitmap(code); + ivCaptcha.setImageBitmap(codeBitmap); + } + + + /** + * 当有控件获得焦点focus 自动弹出键盘 + * 1. 点击软键盘的enter键 自动收回键盘 + * 2. 代码控制 InputMethodManager + * requestFocus + * showSoftInput:显示键盘 必须先让这个view成为焦点requestFocus + * + * hideSoftInputFromWindow 隐藏键盘 + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN){ + //隐藏键盘 + //1.获取系统输入的管理器 + InputMethodManager inputManager = + (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + + //2.隐藏键盘 + inputManager.hideSoftInputFromWindow(user.getWindowToken(),0); + + //3.取消焦点 + View focusView = getCurrentFocus(); + if (focusView != null) { + focusView.clearFocus(); //取消焦点 + } + + //getCurrentFocus().clearFocus(); + + //focusView.requestFocus();//请求焦点 + } + return true; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + //禁止输入中文 + StringUtils.isNotChinese(s); + //判断两个输入框是否有内容 + if (user.getEditText().getText().toString().length() > 0 && + password.getEditText().getText().toString().length() > 0 && + etCaptcha.getEditText().getText().toString().length() > 0){ + //按钮可以点击 + loginBtn.setEnabled(true); + }else{ + //按钮不能点击 + loginBtn.setEnabled(false); + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java index d07ae88..36b6ccd 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java @@ -6,8 +6,10 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; + import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; @@ -16,19 +18,26 @@ import androidx.core.content.FileProvider; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; + import butterknife.BindView; + import com.google.android.material.bottomnavigation.BottomNavigationView; + import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.SharedBook; +import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.ui.dialog.DialogCreator; import xyz.fycz.myreader.ui.fragment.BookcaseFragment; import xyz.fycz.myreader.ui.fragment.FindFragment; import xyz.fycz.myreader.ui.fragment.MineFragment; import xyz.fycz.myreader.util.SharedPreUtils; +import xyz.fycz.myreader.util.StringHelper; import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.GsonExtensionsKt; import java.io.File; import java.util.ArrayList; @@ -41,6 +50,7 @@ import static androidx.fragment.app.FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CU * @date 2020/9/13 13:03 */ public class MainActivity extends BaseActivity { + public static final String TAG = MainActivity.class.getSimpleName(); @BindView(R.id.bottom_navigation_view) BottomNavigationView bottomNavigation; @BindView(R.id.view_pager_main) @@ -138,9 +148,9 @@ public class MainActivity extends BaseActivity { //将滑动到的页面对应的 menu 设置为选中状态 bottomNavigation.getMenu().getItem(i).setChecked(true); getSupportActionBar().setTitle(titles[i]); - if (i == 0) { + if (i == 0) { getSupportActionBar().setSubtitle(groupName); - }else { + } else { getSupportActionBar().setSubtitle(""); } invalidateOptionsMenu(); @@ -162,11 +172,23 @@ public class MainActivity extends BaseActivity { super.processLogic(); try { int settingVersion = SysManager.getSetting().getSettingVersion(); - if (settingVersion < APPCONST.SETTING_VERSION){ + if (settingVersion < APPCONST.SETTING_VERSION) { SysManager.resetSetting(); + Log.d(TAG, "resetSetting"); + } + } catch (Exception e) { + ToastUtils.showError(e.getLocalizedMessage()); + e.printStackTrace(); + } + try { + int sourceVersion = SysManager.getSetting().getSourceVersion(); + if (sourceVersion < APPCONST.SOURCE_VERSION) { + SysManager.resetSource(); + Log.d(TAG, "resetSource"); } - }catch (Exception e){ - SysManager.resetSetting(); + } catch (Exception e) { + ToastUtils.showError(e.getLocalizedMessage()); + e.printStackTrace(); } } @@ -232,7 +254,8 @@ public class MainActivity extends BaseActivity { mBookcaseFragment.getmBookcasePresenter().cancelEdit(); invalidateOptionsMenu(); return true; - case R.id.action_change_group: case R.id.action_group_man: + case R.id.action_change_group: + case R.id.action_group_man: if (!mBookcaseFragment.getmBookcasePresenter().hasOnGroupChangeListener()) { mBookcaseFragment.getmBookcasePresenter().addOnGroupChangeListener(() -> { groupName = SharedPreUtils.getInstance().getString(getString(R.string.curBookGroupName), "所有书籍"); @@ -243,6 +266,10 @@ public class MainActivity extends BaseActivity { case R.id.action_edit: invalidateOptionsMenu(); break; + case R.id.action_qr_scan: + Intent intent = new Intent(this, QRCodeScanActivity.class); + startActivityForResult(intent, APPCONST.REQUEST_QR_SCAN); + break; } return mBookcaseFragment.getmBookcasePresenter().onOptionsItemSelected(item); } @@ -271,17 +298,46 @@ public class MainActivity extends BaseActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); if ((resultCode == RESULT_OK || resultCode == RESULT_CANCELED) && requestCode == APPCONST.APP_INSTALL_CODE) { installProcess(appFile, isForceUpdate);//再次执行安装流程,包含权限判等 } if (resultCode == RESULT_OK) { switch (requestCode) { case APPCONST.REQUEST_LOGIN: - if (mMineFragment.isRecreate()){ + if (mMineFragment.isRecreate()) { reLoadFragment(); } mMineFragment.onActivityResult(requestCode, resultCode, data); break; + case APPCONST.REQUEST_QR_SCAN: + if (data != null) { + String result = data.getStringExtra("result"); + if (!StringHelper.isEmpty(result)) { + String[] string = result.split("#", 2); + if (string.length == 2) { + SharedBook sharedBook = GsonExtensionsKt.getGSON().fromJson(string[1], SharedBook.class); + if (sharedBook != null && !StringHelper.isEmpty(sharedBook.getChapterUrl())){ + Book book = SharedBook.sharedBookToBook(sharedBook); + Intent intent = new Intent(this, BookDetailedActivity.class); + intent.putExtra(APPCONST.BOOK, book); + startActivity(intent); + }else { + ToastUtils.showError("书籍加载失败"); + } + }else { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri uri = Uri.parse(result); + intent.setData(uri); + startActivity(intent); + }catch (Exception e){ + ToastUtils.showError(e.getLocalizedMessage()); + } + } + } + } + break; } } } @@ -368,7 +424,7 @@ public class MainActivity extends BaseActivity { } } - public interface OnGroupChangeListener{ + public interface OnGroupChangeListener { void onChange(); } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java index 860520e..b83bc83 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java @@ -36,65 +36,91 @@ import java.util.Iterator; import static xyz.fycz.myreader.common.APPCONST.BOOK_CACHE_PATH; /** - * Created by newbiechen on 17-6-6. + * Created by fengyue on 17-6-6. * 阅读界面的更多设置 */ public class MoreSettingActivity extends BaseActivity { - @BindView(R.id.more_setting_ll_webdav) + @BindView(R.id.ll_webdav) LinearLayout mLlWebdav; - @BindView(R.id.more_setting_rl_volume) + @BindView(R.id.rl_volume) RelativeLayout mRlVolume; - @BindView(R.id.more_setting_sc_volume) + @BindView(R.id.sc_volume) SwitchCompat mScVolume; - @BindView(R.id.more_setting_rl_reset_screen) + @BindView(R.id.rl_always_next) + RelativeLayout mRlAlwaysNext; + @BindView(R.id.sc_always_next) + SwitchCompat mScAlwaysNext; + @BindView(R.id.rl_show_status) + RelativeLayout mRlShowStatus; + @BindView(R.id.sc_show_status) + SwitchCompat mScShowStatus; + @BindView(R.id.rl_read_aloud_volume_turn_page) + RelativeLayout mRlReadAloudVolumeTurnPage; + @BindView(R.id.sc_read_aloud_volume_turn_page) + SwitchCompat mScReadAloudVolumeTurnPage; + @BindView(R.id.rl_no_menu_title) + RelativeLayout mRlNoMenuTitle; + @BindView(R.id.sc_no_menu_title) + SwitchCompat mScNoMenuTitle; + @BindView(R.id.rl_reset_screen) RelativeLayout mRlResetScreen; - @BindView(R.id.more_setting_sc_reset_screen) + @BindView(R.id.sc_reset_screen) Spinner mScResetScreen; - @BindView(R.id.more_setting_rl_auto_refresh) + @BindView(R.id.rl_auto_refresh) RelativeLayout mRlAutoRefresh; - @BindView(R.id.more_setting_sc_auto_refresh) + @BindView(R.id.sc_auto_refresh) SwitchCompat mScAutoRefresh; - @BindView(R.id.more_setting_ll_close_refresh) + @BindView(R.id.ll_book_sort) + LinearLayout mLlBookSort; + @BindView(R.id.tv_book_sort) + TextView mTvBookSort; + @BindView(R.id.ll_close_refresh) LinearLayout mLlCloseRefresh; - @BindView(R.id.more_setting_ll_disable_source) + @BindView(R.id.ll_disable_source) LinearLayout mLlDisableSource; - @BindView(R.id.more_setting_ll_thread_num) + @BindView(R.id.ll_thread_num) LinearLayout mLlThreadNum; - @BindView(R.id.more_setting_tv_thread_num) + @BindView(R.id.tv_thread_num) TextView mTvThreadNum; - @BindView(R.id.more_setting_iv_match_chapter_tip) + @BindView(R.id.iv_match_chapter_tip) ImageView mIvMatchChapterTip; - @BindView(R.id.more_setting_rl_match_chapter) + @BindView(R.id.rl_match_chapter) RelativeLayout mRlMatchChapter; - @BindView(R.id.more_setting_sc_match_chapter) + @BindView(R.id.sc_match_chapter) SwitchCompat mScMatchChapter; - @BindView(R.id.more_setting_rl_match_chapter_suitability) + @BindView(R.id.rl_match_chapter_suitability) RelativeLayout mRlMatchChapterSuitability; - @BindView(R.id.more_setting_sc_match_chapter_suitability) + @BindView(R.id.sc_match_chapter_suitability) Spinner mScMatchChapterSuitability; - @BindView(R.id.more_setting_rl_cathe_gap) + @BindView(R.id.rl_cathe_gap) RelativeLayout mRlCatheGap; - @BindView(R.id.more_setting_sc_cathe_gap) + @BindView(R.id.sc_cathe_gap) Spinner mScCatheGap; - @BindView(R.id.more_setting_rl_delete_cathe) + @BindView(R.id.rl_delete_cathe) RelativeLayout mRlDeleteCathe; - @BindView(R.id.more_setting_ll_download_all) + @BindView(R.id.ll_download_all) LinearLayout mLlDownloadAll; - /*@BindView(R.id.more_setting_rl_bookstore) + /*@BindView(R.id.rl_bookstore) RelativeLayout mRlBookstore; - @BindView(R.id.more_setting_sc_bookstore) + @BindView(R.id.sc_bookstore) SwitchCompat mScBookstore;*/ + private boolean needRefresh; + private boolean upMenu; private Setting mSetting; private boolean isVolumeTurnPage; private int resetScreenTime; + private int sortStyle; private boolean autoRefresh; private boolean isMatchChapter; - private boolean openBookStore; private float matchChapterSuitability; private int catheCap; + private boolean isShowStatusBar; + private boolean alwaysNext; + private boolean noMenuTitle; + private boolean readAloudVolumeTurnPage; private ArrayList mBooks; int booksCount; @@ -105,12 +131,8 @@ public class MoreSettingActivity extends BaseActivity { private AlertDialog mCloseRefreshDia; //选择禁用更新书源对话框 private AlertDialog mDisableSourceDia; - //线程选择 - private AlertDialog mThreadSelectDia; //选择一键缓存书籍对话框 private AlertDialog mDownloadAllDia; - //选择清除缓存对话框 - private AlertDialog mDeleteCatheDia; @Override @@ -121,14 +143,19 @@ public class MoreSettingActivity extends BaseActivity { @Override protected void initData(Bundle savedInstanceState) { super.initData(savedInstanceState); + needRefresh = false; mSetting = SysManager.getSetting(); isVolumeTurnPage = mSetting.isVolumeTurnPage(); + alwaysNext = mSetting.isAlwaysNext(); resetScreenTime = mSetting.getResetScreen(); isMatchChapter = mSetting.isMatchChapter(); matchChapterSuitability = mSetting.getMatchChapterSuitability(); catheCap = mSetting.getCatheGap(); + sortStyle = mSetting.getSortStyle(); autoRefresh = mSetting.isRefreshWhenStart(); - openBookStore = mSetting.isOpenBookStore(); + isShowStatusBar = mSetting.isShowStatusBar(); + noMenuTitle = mSetting.isNoMenuChTitle(); + readAloudVolumeTurnPage = mSetting.isReadAloudVolumeTurnPage(); threadNum = SharedPreUtils.getInstance().getInt(getString(R.string.threadNum), 8); } @@ -143,6 +170,11 @@ public class MoreSettingActivity extends BaseActivity { protected void initWidget() { super.initWidget(); initSwitchStatus(); + if (sortStyle == 1){ + mTvBookSort.setText(getString(R.string.time_sort)); + }else if (sortStyle == 2){ + mTvBookSort.setText(getString(R.string.book_name_sort)); + } if (isMatchChapter) { mRlMatchChapterSuitability.setVisibility(View.VISIBLE); } else { @@ -154,9 +186,12 @@ public class MoreSettingActivity extends BaseActivity { private void initSwitchStatus() { mScVolume.setChecked(isVolumeTurnPage); + mScAlwaysNext.setChecked(alwaysNext); mScMatchChapter.setChecked(isMatchChapter); mScAutoRefresh.setChecked(autoRefresh); - //mScBookstore.setChecked(openBookStore); + mScShowStatus.setChecked(isShowStatusBar); + mScNoMenuTitle.setChecked(noMenuTitle); + mScReadAloudVolumeTurnPage.setChecked(readAloudVolumeTurnPage); } @Override @@ -176,6 +211,79 @@ public class MoreSettingActivity extends BaseActivity { SysManager.saveSetting(mSetting); } ); + mRlAlwaysNext.setOnClickListener( + (v) -> { + if (alwaysNext) { + alwaysNext = false; + } else { + alwaysNext = true; + } + mScAlwaysNext.setChecked(alwaysNext); + mSetting.setAlwaysNext(alwaysNext); + SysManager.saveSetting(mSetting); + } + ); + mRlShowStatus.setOnClickListener( + (v) -> { + needRefresh = true; + if (isShowStatusBar) { + isShowStatusBar = false; + } else { + isShowStatusBar = true; + } + mScShowStatus.setChecked(isShowStatusBar); + mSetting.setShowStatusBar(isShowStatusBar); + SysManager.saveSetting(mSetting); + } + ); + mRlReadAloudVolumeTurnPage.setOnClickListener( + (v) -> { + if (readAloudVolumeTurnPage) { + readAloudVolumeTurnPage = false; + } else { + readAloudVolumeTurnPage = true; + } + mScReadAloudVolumeTurnPage.setChecked(readAloudVolumeTurnPage); + mSetting.setReadAloudVolumeTurnPage(readAloudVolumeTurnPage); + SysManager.saveSetting(mSetting); + } + ); + mRlNoMenuTitle.setOnClickListener( + (v) -> { + upMenu = true; + if (noMenuTitle) { + noMenuTitle = false; + } else { + noMenuTitle = true; + } + mScNoMenuTitle.setChecked(noMenuTitle); + mSetting.setNoMenuChTitle(noMenuTitle); + SysManager.saveSetting(mSetting); + } + ); + + mLlBookSort.setOnClickListener(v -> { + MyAlertDialog.build(this) + .setTitle(getString(R.string.book_sort)) + .setSingleChoiceItems(R.array.book_sort, sortStyle, (dialog, which) -> { + sortStyle = which; + mSetting.setSortStyle(sortStyle); + SysManager.saveSetting(mSetting); + if (sortStyle == 0){ + mTvBookSort.setText(getString(R.string.manual_sort)); + if (!SharedPreUtils.getInstance().getBoolean("manualSortTip")){ + DialogCreator.createTipDialog(this, "可在书架编辑状态下长按移动书籍进行排序!"); + SharedPreUtils.getInstance().putBoolean("manualSortTip", true); + } + }else if (sortStyle == 1){ + mTvBookSort.setText(getString(R.string.time_sort)); + }else if (sortStyle == 2){ + mTvBookSort.setText(getString(R.string.book_name_sort)); + } + dialog.dismiss(); + }).setNegativeButton("取消", null).show(); + }); + mRlAutoRefresh.setOnClickListener( (v) -> { if (autoRefresh) { @@ -289,7 +397,7 @@ public class MoreSettingActivity extends BaseActivity { threadPick.setOnScrollListener((view1, scrollState) -> { }); - mThreadSelectDia = MyAlertDialog.build(this) + MyAlertDialog.build(this) .setTitle("搜索线程数") .setView(view) .setPositiveButton("确定", (dialog, which) -> { @@ -383,7 +491,7 @@ public class MoreSettingActivity extends BaseActivity { } CharSequence[] cathes = {"章节缓存:" + eCatheFileSize, "图片缓存:" + catheFileSize}; boolean[] catheCheck = {true, true}; - mDeleteCatheDia = new MultiChoiceDialog().create(this, "清除缓存", cathes, catheCheck, 2, + new MultiChoiceDialog().create(this, "清除缓存", cathes, catheCheck, 2, (dialog, which) -> { String tip = ""; if (catheCheck[0]) { @@ -411,6 +519,15 @@ public class MoreSettingActivity extends BaseActivity { initSpinner(); } + @Override + public void finish() { + Intent result = new Intent(); + result.putExtra(APPCONST.RESULT_NEED_REFRESH, needRefresh); + result.putExtra(APPCONST.RESULT_UP_MENU, upMenu); + setResult(AppCompatActivity.RESULT_OK, result); + super.finish(); + } + private void initSpinner() { // initSwitchStatus() be called earlier than onCreate(), so setSelection() won't work ArrayAdapter resetScreenAdapter = ArrayAdapter.createFromResource(this, @@ -453,9 +570,6 @@ public class MoreSettingActivity extends BaseActivity { } mSetting.setResetScreen(resetScreenTime); SysManager.saveSetting(mSetting); - Intent result = new Intent(); - result.putExtra(APPCONST.RESULT_RESET_SCREEN, resetScreenTime); - setResult(AppCompatActivity.RESULT_OK, result); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java new file mode 100644 index 0000000..6404e66 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java @@ -0,0 +1,261 @@ +package xyz.fycz.myreader.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.util.Objects; + +import butterknife.BindView; +import cn.bingoogolapple.qrcode.core.QRCodeView; +import cn.bingoogolapple.qrcode.zxing.ZXingView; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.base.observer.MySingleObserver; +import xyz.fycz.myreader.util.PermissionsChecker; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.RxUtils; + +import static xyz.fycz.myreader.util.UriFileUtil.getPath; + +/** + * @author fengyue + * @date 2020/11/30 8:31 + */ + +public class QRCodeScanActivity extends BaseActivity implements QRCodeView.Delegate { + + @BindView(R.id.zxingview) + ZXingView zxingview; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.ll_flashlight) + LinearLayout llFlashLight; + @BindView(R.id.fab_flashlight) + FloatingActionButton fabFlashlight; + @BindView(R.id.tv_flashlight) + TextView tvFlashlight; + + private final int REQUEST_QR_IMAGE = 202; + private static final String CAMERA = "android.permission.CAMERA"; + private static final int PERMISSIONS_REQUEST_CAMERA = 101; + private boolean flashlightIsOpen; + private boolean needScale = true; + private String picPath; + + @Override + protected int getContentId() { + return R.layout.activity_qrcode_capture; + } + + + @Override + protected void setUpToolbar(Toolbar toolbar) { + super.setUpToolbar(toolbar); + setStatusBarColor(R.color.colorPrimary, true); + getSupportActionBar().setTitle("扫一扫"); + } + + /** + * 数据初始化 + */ + @Override + protected void initData(Bundle savedInstanceState) { + zxingview.setDelegate(this); + fabFlashlight.setOnClickListener(view -> { + if (flashlightIsOpen) { + flashlightIsOpen = false; + zxingview.closeFlashlight(); + tvFlashlight.setText(getString(R.string.light_contact)); + } else { + flashlightIsOpen = true; + zxingview.openFlashlight(); + tvFlashlight.setText(getString(R.string.close_contact)); + } + }); + } + + @Override + protected void onStart() { + super.onStart(); + startCamera(); + } + + private void startCamera() { + requestPermission(); + } + + @Override + protected void onStop() { + zxingview.stopCamera(); // 关闭摄像头预览,并且隐藏扫描框 + super.onStop(); + } + + @Override + protected void onDestroy() { + zxingview.onDestroy(); // 销毁二维码扫描控件 + super.onDestroy(); + } + + @Override + public void onScanQRCodeSuccess(String result) { + Log.d("onScanQRCodeSuccess", needScale + ""); + if (result == null) { + if (!needScale){ + needScale = true; + if (StringHelper.isEmpty(picPath)) { + return; + } + scanFromPath(picPath); + }else { + ToastUtils.showError("二维码扫描失败"); + } + }else { + Intent intent = new Intent(); + Log.d("result", result); + intent.putExtra("result", result); + setResult(RESULT_OK, intent); + finish(); + } + } + + @Override + public void onCameraAmbientBrightnessChanged(boolean isDark) { + if (isDark){ + llFlashLight.setVisibility(View.VISIBLE); + }else { + if (!flashlightIsOpen) { + llFlashLight.setVisibility(View.GONE); + } + } + } + + @Override + public void onScanQRCodeOpenCameraError() { + + } + + private void startScan() { + zxingview.setVisibility(View.VISIBLE); + zxingview.startSpotAndShowRect(); // 显示扫描框,并开始识别 + } + + private void requestPermission() { + //获取读取和写入SD卡的权限 + if (new PermissionsChecker(this).lacksPermissions(CAMERA)) { + ActivityCompat.requestPermissions(this, new String[]{CAMERA}, PERMISSIONS_REQUEST_CAMERA); + } else { + startScan(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case PERMISSIONS_REQUEST_CAMERA: { + // 如果取消权限,则返回的值为0 + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + //申请权限成功 + startScan(); + } else { + //申请权限失败 + finish(); + ToastUtils.showWarring("请给予相机权限,否则无法进行扫码!"); + } + return; + } + } + } + + // 添加菜单 + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_qr_code_scan, menu); + return super.onCreateOptionsMenu(menu); + } + + //菜单 + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case R.id.action_choose_from_gallery: + chooseFromGallery(); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + zxingview.startSpotAndShowRect(); // 显示扫描框,并开始识别 + + if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_QR_IMAGE) { + picPath = getPath(this, data.getData()); + if (StringHelper.isEmpty(picPath)) { + return; + } + scanFromPath(picPath); + } + } + + private void scanFromPath(String path){ + // 本来就用到 QRCodeView 时可直接调 QRCodeView 的方法,走通用的回调 + Single.create((SingleOnSubscribe) emitter -> { + Bitmap bitmap = BitmapFactory.decodeFile(path); + if (bitmap == null) + return; + if (needScale) { + int size = 360; + bitmap = Bitmap.createBitmap(bitmap, bitmap.getWidth() - size, bitmap.getHeight() - size, size, size); + } + emitter.onSuccess(bitmap); + }).compose(RxUtils::toSimpleSingle) + .subscribe(new MySingleObserver() { + @Override + public void onSuccess(Bitmap bitmap) { + zxingview.decodeQRCode(bitmap); + } + }); + } + + private void chooseFromGallery() { + try { + if (needScale){ + ToastUtils.showInfo("选择图片仅支持扫描书籍分享图片"); + } + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + startActivityForResult(intent, REQUEST_QR_IMAGE); + } catch (Exception e) { + e.printStackTrace(); + ToastUtils.showError(Objects.requireNonNull(e.getLocalizedMessage())); + } + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java index 00a22cf..4a7de94 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java @@ -1,19 +1,21 @@ package xyz.fycz.myreader.ui.activity; import android.annotation.SuppressLint; -import android.app.Dialog; import android.app.Notification; import android.content.*; +import android.content.pm.ActivityInfo; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.util.Log; import android.view.*; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.SeekBar; @@ -22,12 +24,15 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; + import butterknife.BindView; import butterknife.OnClick; + import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.gyf.immersionbar.ImmersionBar; -import com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar; +import com.jaredrummler.android.colorpicker.ColorPickerDialogListener; + import xyz.fycz.myreader.ActivityManage; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; @@ -37,7 +42,6 @@ import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.enums.BookSource; import xyz.fycz.myreader.enums.Font; -import xyz.fycz.myreader.enums.ReadStyle; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.BookMark; import xyz.fycz.myreader.greendao.entity.Chapter; @@ -50,8 +54,15 @@ import xyz.fycz.myreader.ui.dialog.AudioPlayerDialog; import xyz.fycz.myreader.ui.dialog.CopyContentDialog; import xyz.fycz.myreader.ui.dialog.DialogCreator; import xyz.fycz.myreader.ui.dialog.MyAlertDialog; +import xyz.fycz.myreader.ui.popmenu.AutoPageMenu; +import xyz.fycz.myreader.ui.popmenu.BrightnessEyeMenu; +import xyz.fycz.myreader.ui.popmenu.CustomizeComMenu; +import xyz.fycz.myreader.ui.popmenu.CustomizeLayoutMenu; +import xyz.fycz.myreader.ui.popmenu.ReadSettingMenu; import xyz.fycz.myreader.ui.dialog.SourceExchangeDialog; +import xyz.fycz.myreader.ui.presenter.CatalogPresenter; import xyz.fycz.myreader.util.*; +import xyz.fycz.myreader.util.llog.LLog; import xyz.fycz.myreader.util.notification.NotificationClickReceiver; import xyz.fycz.myreader.util.notification.NotificationUtil; import xyz.fycz.myreader.util.utils.ColorUtil; @@ -69,6 +80,7 @@ import xyz.fycz.myreader.widget.page.PageView; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -78,7 +90,8 @@ import static xyz.fycz.myreader.util.UriFileUtil.getPath; * @author fengyue * @date 2020/10/21 16:46 */ -public class ReadActivity extends BaseActivity { +public class ReadActivity extends BaseActivity implements ColorPickerDialogListener { + private static final String TAG = ReadActivity.class.getSimpleName(); /*****************************View***********************************/ @BindView(R.id.toolbar) @@ -95,8 +108,6 @@ public class ReadActivity extends BaseActivity { PageView pageView; @BindView(R.id.pb_loading) ProgressBar pbLoading; - @BindView(R.id.pb_nextPage) - VerticalSeekBar pbNextPage; @BindView(R.id.read_tv_page_tip) TextView readTvPageTip; @BindView(R.id.read_tv_pre_chapter) @@ -107,19 +118,30 @@ public class ReadActivity extends BaseActivity { TextView readTvNextChapter; @BindView(R.id.read_tv_category) TextView readTvCategory; - @BindView(R.id.read_tv_night_mode) - TextView readTvNightMode; - @BindView(R.id.read_tv_download) - TextView readTvDownload; + @BindView(R.id.btn_night_mode) + FloatingActionButton readBtnNightMode; + @BindView(R.id.read_tv_brightness_eye) + TextView readTvBrightnessEye; @BindView(R.id.read_tv_setting) TextView readTvSetting; @BindView(R.id.read_ll_bottom_menu) LinearLayout readLlBottomMenu; - @BindView(R.id.iv_listen_book) - FloatingActionButton readBtnListenBook; + @BindView(R.id.read_tv_listen_book) + TextView readTvListenBook; + @BindView(R.id.read_setting_menu) + ReadSettingMenu readSettingMenu; + @BindView(R.id.read_customize_menu) + CustomizeComMenu customizeComMenu; + @BindView(R.id.read_auto_page_menu) + AutoPageMenu autoPageMenu; + @BindView(R.id.read_customize_layout_menu) + CustomizeLayoutMenu customizeLayoutMenu; + @BindView(R.id.read_brightness_eye_menu) + BrightnessEyeMenu brightnessEyeMenu; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; /***************************variable*****************************/ - private ImmersionBar immersionBar; private Book mBook; private ArrayList mChapters = new ArrayList<>(); private ChapterService mChapterService; @@ -136,8 +158,6 @@ public class ReadActivity extends BaseActivity { private boolean loadFinish = false; - private Dialog mPageModeDialog;//翻页模式视图 - private int curCacheChapterNum = 0;//缓存章节数 private int needCacheChapterNum;//需要缓存的章节 @@ -148,7 +168,6 @@ public class ReadActivity extends BaseActivity { private Runnable keepScreenRunnable;//息屏线程 private Runnable autoPageRunnable;//自动翻页 - private Runnable upHpbNextPage;//更新自动翻页进度条 private Runnable sendDownloadNotification; private static boolean isStopDownload = true; @@ -158,18 +177,11 @@ public class ReadActivity extends BaseActivity { private ReadCrawler mReadCrawler; - private int nextPageTime;//下次翻页时间 - private int upHpbInterval = 30;//更新翻页进度速度 private int downloadInterval = 150; - private final CharSequence[] pageMode = { - "覆盖", "仿真", "滑动", "滚动", "无动画" - }; - private SourceExchangeDialog mSourceDialog; - private Dialog mSettingDialog; private AudioPlayerDialog mAudioPlayerDialog; private boolean hasChangeSource; @@ -211,14 +223,19 @@ public class ReadActivity extends BaseActivity { init(); break; case 2: - int chapterPos = msg.arg1; - int pagePos = msg.arg2; - mPageLoader.skipToChapter(chapterPos); - mPageLoader.skipToPage(pagePos); + try { + int chapterPos = msg.arg1; + int pagePos = msg.arg2; + mPageLoader.skipToChapter(chapterPos); + mPageLoader.skipToPage(pagePos); + } catch (Exception e) { + ToastUtils.showError("章节跳转失败,请截图联系作者。\n" + + Objects.requireNonNull(e.getMessage())); + e.printStackTrace(); + } pbLoading.setVisibility(View.GONE); break; case 3: - updateDownloadProgress((TextView) msg.obj); break; case 4: saveLastChapterReadPosition(); @@ -226,7 +243,9 @@ public class ReadActivity extends BaseActivity { initMenu(); break; case 5: - + if (mPageLoader != null) { + mPageLoader.refreshUi(); + } break; case 6: mPageLoader.openChapter(); @@ -264,6 +283,7 @@ public class ReadActivity extends BaseActivity { return R.layout.activity_read; } + @Override protected void setUpToolbar(Toolbar toolbar) { super.setUpToolbar(toolbar); @@ -277,7 +297,6 @@ public class ReadActivity extends BaseActivity { mChapterService = ChapterService.getInstance(); mBookMarkService = BookMarkService.getInstance(); mSetting = SysManager.getSetting(); - if (!loadBook()) { finish(); return; @@ -290,7 +309,6 @@ public class ReadActivity extends BaseActivity { //保持屏幕常亮 keepScreenRunnable = this::unKeepScreenOn; autoPageRunnable = this::nextPage; - upHpbNextPage = this::upHpbNextPage; sendDownloadNotification = this::sendNotification; notificationUtil = NotificationUtil.getInstance(); @@ -313,14 +331,17 @@ public class ReadActivity extends BaseActivity { this::hideSystemBar ); if (!mSetting.isBrightFollowSystem()) { - BrightUtil.setBrightness(this, BrightUtil.progressToBright(mSetting.getBrightProgress())); + BrightUtil.setBrightness(this, mSetting.getBrightProgress()); } pbLoading.setVisibility(View.VISIBLE); - createSettingDetailView(); + initEyeView(); + initSettingListener(); initTopMenu(); initBottomMenu(); + setOrientation(mSetting.isHorizontalScreen()); } + @Override protected void initClick() { super.initClick(); @@ -333,9 +354,6 @@ public class ReadActivity extends BaseActivity { @Override public void center() { toggleMenu(true); - if (autoPage) { - autoPageStop(); - } } @Override @@ -368,6 +386,9 @@ public class ReadActivity extends BaseActivity { } mBook.setHistoryChapterId(mChapters.get(pos).getTitle()); MyApplication.getApplication().newThread(() -> { + if (mPageLoader == null) { + return; + } if (mPageLoader.getPageStatus() == PageLoader.STATUS_LOADING) { if (!NetworkUtils.isNetWorkAvailable()) { mHandler.sendMessage(mHandler.obtainMessage(7)); @@ -401,16 +422,16 @@ public class ReadActivity extends BaseActivity { if (mPageLoader.hasChapterData(mChapters.get(mPageLoader.getChapterPos()))) { if (resetRead) { if (mAudioPlayerDialog != null) { - mHandler.postDelayed(() -> mAudioPlayerDialog.readAloud(), 500); + mHandler.postDelayed(() -> mAudioPlayerDialog.readAloud(), 400); } return; } if (pos == 0) { if (mAudioPlayerDialog != null) { - mHandler.postDelayed(() -> mAudioPlayerDialog.readAloud(), 500); + mHandler.postDelayed(() -> mAudioPlayerDialog.readAloud(), 400); } } - }else { + } else { ReadAloudService.stop(ReadActivity.this); } } @@ -441,7 +462,7 @@ public class ReadActivity extends BaseActivity { }); mSourceDialog.setOnSourceChangeListener((bean, pos) -> { - Book bookTem = new Book(mBook); + Book bookTem = (Book) mBook.clone(); bookTem.setChapterUrl(bean.getChapterUrl()); bookTem.setSource(bean.getSource()); if (!StringHelper.isEmpty(bean.getImgUrl())) { @@ -468,7 +489,7 @@ public class ReadActivity extends BaseActivity { startActivity(intent); }); - readBtnListenBook.setOnClickListener(v -> { + readTvListenBook.setOnClickListener(v -> { if (mSetting.getPageMode() == PageMode.SCROLL) { ToastUtils.showWarring("朗读暂不支持滚动翻页模式!"); return; @@ -499,6 +520,8 @@ public class ReadActivity extends BaseActivity { //保存上次阅读信息 SharedPreUtils.getInstance().putString(getString(R.string.lastRead), mBook.getId()); } + //保存最近阅读时间 + mBook.setLastReadTime(DateHelper.getLongDate()); getData(); } @@ -511,32 +534,42 @@ public class ReadActivity extends BaseActivity { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean isVolumeTurnPage = SysManager.getSetting().isVolumeTurnPage(); - if (readAblTopMenu.getVisibility() != View.VISIBLE) { + if (readAblTopMenu.getVisibility() != View.VISIBLE && + customizeLayoutMenu.getVisibility() != View.VISIBLE && + autoPageMenu.getVisibility() != View.VISIBLE && + customizeComMenu.getVisibility() != View.VISIBLE && + readSettingMenu.getVisibility() != View.VISIBLE && + brightnessEyeMenu.getVisibility() != View.VISIBLE) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: - if (isVolumeTurnPage) { - return mPageLoader.skipToPrePage(); + if (ReadAloudService.running) { + if (mSetting.isReadAloudVolumeTurnPage()) { + return mPageLoader.skipToPrePage(); + } + } else { + if (isVolumeTurnPage) { + return mPageLoader.skipToPrePage(); + } } case KeyEvent.KEYCODE_VOLUME_DOWN: - if (isVolumeTurnPage) { - return mPageLoader.skipToNextPage(); + if (ReadAloudService.running) { + if (mSetting.isReadAloudVolumeTurnPage()) { + return mPageLoader.skipToNextPage(); + } + } else { + if (isVolumeTurnPage) { + return mPageLoader.skipToNextPage(); + } } } } + return super.onKeyDown(keyCode, event); } @Override public void onBackPressed() { - if (readAblTopMenu.getVisibility() == View.VISIBLE) { - // 非全屏下才收缩,全屏下直接退出 - //if (!ReadSettingManager.getInstance().isFullScreen()) { - if (true) { - toggleMenu(true); - return; - } - } else if (mSettingDialog.isShowing()) { - mSettingDialog.dismiss(); + if (hideReadMenu()) { return; } else if (ReadAloudService.running && mAudioPlayerDialog.aloudStatus == ReadAloudService.Status.PLAY) { ReadAloudService.pause(this); @@ -589,7 +622,6 @@ public class ReadActivity extends BaseActivity { super.onDestroy(); unregisterReceiver(mReceiver); mHandler.removeCallbacks(keepScreenRunnable); - mHandler.removeCallbacks(upHpbNextPage); mHandler.removeCallbacks(autoPageRunnable); /*mHandler.removeCallbacks(sendDownloadNotification); notificationUtil.cancelAll(); @@ -615,12 +647,12 @@ public class ReadActivity extends BaseActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { + menu.setGroupVisible(R.id.action_load_finish, loadFinish); if ("本地书籍".equals(mBook.getType())) { menu.findItem(R.id.action_change_source).setVisible(false); menu.findItem(R.id.action_open_link).setVisible(false); + menu.findItem(R.id.action_download).setVisible(false); } - menu.setGroupVisible(R.id.action_load_finish, loadFinish); - return super.onPrepareOptionsMenu(menu); } @@ -665,6 +697,9 @@ public class ReadActivity extends BaseActivity { Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); break; + case R.id.action_download: + download(); + break; } return super.onOptionsItemSelected(item); } @@ -688,18 +723,77 @@ public class ReadActivity extends BaseActivity { break; case APPCONST.REQUEST_CHAPTER_PAGE: int[] chapterAndPage = data.getIntArrayExtra(APPCONST.CHAPTER_PAGE); - assert chapterAndPage != null; - skipToChapterAndPage(chapterAndPage[0], chapterAndPage[1]); + /*LLog.i(TAG, "chapterAndPage == null" + (chapterAndPage == null)); + LLog.i(TAG, "chapterAndPage.length" + (chapterAndPage.length));*/ + if (chapterAndPage == null) { + ToastUtils.showError("章节跳转失败!"); + return; + } + try { + skipToChapterAndPage(chapterAndPage[0], chapterAndPage[1]); + } catch (Exception e) { + ToastUtils.showError("章节跳转失败,请截图联系作者。\n" + + Objects.requireNonNull(e.getMessage())); + e.printStackTrace(); + } break; - case APPCONST.REQUEST_RESET_SCREEN_TIME: - int resetScreen = data.getIntExtra(APPCONST.RESULT_RESET_SCREEN, 0); - screenTimeOut = resetScreen * 60; + case APPCONST.REQUEST_REFRESH_READ_UI: + screenTimeOut = mSetting.getResetScreen() * 60; screenOffTimerStart(); + boolean needRefresh = data.getBooleanExtra(APPCONST.RESULT_NEED_REFRESH, false); + boolean upMenu = data.getBooleanExtra(APPCONST.RESULT_UP_MENU, false); + if (needRefresh) { + mHandler.sendEmptyMessage(5); + } + if (upMenu) { + initTopMenu(); + } + break; + case APPCONST.REQUEST_SELECT_BG: + String bgPath = getPath(this, data.getData()); + customizeLayoutMenu.setCustomBg(bgPath); + break; + case APPCONST.REQUEST_IMPORT_LAYOUT: + String zipPath = getPath(this, data.getData()); + customizeLayoutMenu.zip2Layout(zipPath); break; } } super.onActivityResult(requestCode, resultCode, data); } + + /** + * Callback that is invoked when a color is selected from the color picker dialog. + * + * @param dialogId The dialog id used to create the dialog instance. + * @param color The selected color + */ + @Override + public void onColorSelected(int dialogId, int color) { + switch (dialogId) { + case APPCONST.SELECT_TEXT_COLOR: + mSetting.setTextColor(color); + mPageLoader.setTextSize(); + break; + case APPCONST.SELECT_BG_COLOR: + mSetting.setBgIsColor(true); + mSetting.setBgColor(color); + mPageLoader.refreshUi(); + break; + } + SysManager.saveSetting(mSetting); + customizeLayoutMenu.upColor(); + } + + /** + * Callback that is invoked when the color picker dialog was dismissed. + * + * @param dialogId The dialog id used to create the dialog instance. + */ + @Override + public void onDialogDismissed(int dialogId) { + + } /**************************method*********************************/ /** * 初始化 @@ -801,14 +895,12 @@ public class ReadActivity extends BaseActivity { /** * 添加到书架并缓存书籍 - * - * @param tvDownloadProgress */ - private void addBookToCaseAndDownload(final TextView tvDownloadProgress) { + private void addBookToCaseAndDownload() { DialogCreator.createCommonDialog(this, this.getString(R.string.tip), this.getString(R.string.download_no_add_tips), true, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - downloadBook(tvDownloadProgress); + downloadBook(); isCollected = true; } }, (dialog, which) -> dialog.dismiss()); @@ -1040,8 +1132,14 @@ public class ReadActivity extends BaseActivity { * 初始化顶部菜单 */ private void initTopMenu() { - if (Build.VERSION.SDK_INT >= 19) { - readAblTopMenu.setPadding(0, ImmersionBar.getStatusBarHeight(this), 0, 0); + int statusBarHeight = ImmersionBar.getStatusBarHeight(this); + readAblTopMenu.setPadding(0, statusBarHeight, 0, 0); + if (mSetting.isNoMenuChTitle()) { + chapterView.setVisibility(GONE); + toolbar.getLayoutParams().height = 60 + ImmersionBar.getStatusBarHeight(this); + } else { + chapterView.setVisibility(VISIBLE); + toolbar.getLayoutParams().height = 45 + ImmersionBar.getStatusBarHeight(this); } } @@ -1052,20 +1150,23 @@ public class ReadActivity extends BaseActivity { //判断是否全屏 //if (mSetting.getHideStatusBar()) { if (!mSetting.isDayStyle()) { - readTvNightMode.setText("白天"); - Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.z4); - readTvNightMode.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null); + readBtnNightMode.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_sun)); } + readBtnNightMode.setOnClickListener(v -> changeNightAndDaySetting(mSetting.isDayStyle())); if (true) { //还需要设置mBottomMenu的底部高度 - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) readLlBottomMenu.getLayoutParams(); - params.bottomMargin = ImmersionBar.getNavigationBarHeight(this); - readLlBottomMenu.setLayoutParams(params); + if (ImmersionBar.hasNavigationBar(this)) { + int height = ImmersionBar.getNavigationBarHeight(this); + vwNavigationBar.getLayoutParams().height = height; + readSettingMenu.setNavigationBarHeight(height); + customizeComMenu.setNavigationBarHeight(height); + customizeLayoutMenu.setNavigationBarHeight(height); + autoPageMenu.setNavigationBarHeight(height); + brightnessEyeMenu.setNavigationBarHeight(height); + } } else { //设置mBottomMenu的底部距离 - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) readLlBottomMenu.getLayoutParams(); - params.bottomMargin = 0; - readLlBottomMenu.setLayoutParams(params); + vwNavigationBar.getLayoutParams().height = 0; } } @@ -1076,14 +1177,37 @@ public class ReadActivity extends BaseActivity { */ private boolean hideReadMenu() { hideSystemBar(); + boolean flag = false; if (readAblTopMenu.getVisibility() == VISIBLE) { toggleMenu(true); - return true; - } else if (mSettingDialog.isShowing()) { - mSettingDialog.dismiss(); - return true; + flag = true; + } + if (readSettingMenu.getVisibility() == View.VISIBLE) { + readSettingMenu.setVisibility(GONE); + readSettingMenu.startAnimation(mBottomOutAnim); + flag = true; + } + if (customizeComMenu.getVisibility() == VISIBLE) { + customizeComMenu.setVisibility(GONE); + customizeComMenu.startAnimation(mBottomOutAnim); + flag = true; + } + if (customizeLayoutMenu.getVisibility() == VISIBLE) { + customizeLayoutMenu.setVisibility(GONE); + customizeLayoutMenu.startAnimation(mBottomOutAnim); + flag = true; + } + if (autoPageMenu.getVisibility() == VISIBLE) { + autoPageMenu.setVisibility(GONE); + autoPageMenu.startAnimation(mBottomOutAnim); + flag = true; + } + if (brightnessEyeMenu.getVisibility() == VISIBLE) { + brightnessEyeMenu.setVisibility(GONE); + brightnessEyeMenu.startAnimation(mBottomOutAnim); + flag = true; } - return false; + return flag; } /** @@ -1093,6 +1217,7 @@ public class ReadActivity extends BaseActivity { private void toggleMenu(boolean hideStatusBar) { toggleMenu(hideStatusBar, false); } + public void toggleMenu(boolean hideStatusBar, boolean home) { initMenuAnim(); if (readAblTopMenu.getVisibility() == View.VISIBLE) { @@ -1106,12 +1231,17 @@ public class ReadActivity extends BaseActivity { } return; } - if (ReadAloudService.running && !home){ + if (ReadAloudService.running && !home) { if (mAudioPlayerDialog != null) { mAudioPlayerDialog.show(); return; } } + if (autoPage) { + autoPageMenu.setVisibility(VISIBLE); + autoPageMenu.startAnimation(mBottomInAnim); + return; + } readAblTopMenu.setVisibility(View.VISIBLE); readLlBottomMenu.setVisibility(View.VISIBLE); readAblTopMenu.startAnimation(mTopInAnim); @@ -1137,101 +1267,161 @@ public class ReadActivity extends BaseActivity { private void hideSystemBar() { //隐藏 if (readAblTopMenu.getVisibility() != VISIBLE || (mAudioPlayerDialog != null && !mAudioPlayerDialog.isShowing())) { - SystemBarUtils.hideStableStatusBar(this); + if (!mSetting.isShowStatusBar()) { + SystemBarUtils.hideStableStatusBar(this); + } SystemBarUtils.hideStableNavBar(this); } } /******************设置相关*****************/ - /** - * 创建详细设置视图 + * 初始化详细设置 */ - private void createSettingDetailView() { - mSettingDialog = DialogCreator.createReadDetailSetting(this, mSetting, - this::changeStyle, v -> reduceTextSize(), v -> increaseTextSize(), v -> { - if (mSetting.isVolumeTurnPage()) { - mSetting.setVolumeTurnPage(false); - ToastUtils.showSuccess("音量键翻页已关闭!"); - } else { - mSetting.setVolumeTurnPage(true); - ToastUtils.showSuccess("音量键翻页已开启!"); - } - SysManager.saveSetting(mSetting); - }, v -> { - Intent intent = new Intent(this, FontsActivity.class); + private void initSettingListener() { + readSettingMenu.setOnClickListener(null); + readSettingMenu.setListener(this, new ReadSettingMenu.Callback() { + @Override + public void onRefreshUI() { + mHandler.sendEmptyMessage(5); + } + + @Override + public void onStyleChange() { + changeStyle(); + } + + @Override + public void onTextSizeChange() { + mPageLoader.setTextSize(); + } + + @Override + public void onFontClick() { + hideReadMenu(); + mHandler.postDelayed(() -> { + Intent intent = new Intent(ReadActivity.this, FontsActivity.class); startActivityForResult(intent, APPCONST.REQUEST_FONT); - mSettingDialog.dismiss(); - }, this::showPageModeDialog, v -> { - if (mSetting.getPageMode() == PageMode.SCROLL) { - ToastUtils.showWarring("滚动暂时不支持自动翻页!"); - return; - } - if (ReadAloudService.running){ - ToastUtils.showWarring("请先关闭语音朗读!"); - return; - } - mSettingDialog.dismiss(); - autoPage = !autoPage; + }, mBottomOutAnim.getDuration()); + } + + @Override + public void onAutoPageClick() { + if (ReadAloudService.running) { + ToastUtils.showWarring("请先关闭语音朗读!"); + return; + } + hideReadMenu(); + ToastUtils.showInfo("自动翻页开启"); + autoPage = !autoPage; + autoPage(); + } + + @Override + public void onHVChange() { + setOrientation(mSetting.isHorizontalScreen()); + } + + @Override + public void onMoreSettingClick() { + hideReadMenu(); + mHandler.postDelayed(() -> { + Intent intent = new Intent(ReadActivity.this, MoreSettingActivity.class); + startActivityForResult(intent, APPCONST.REQUEST_REFRESH_READ_UI); + }, mBottomOutAnim.getDuration()); + } + }); + customizeComMenu.setOnClickListener(null); + customizeComMenu.setListener(new CustomizeComMenu.Callback() { + @Override + public void onTextPChange() { + mPageLoader.setTextSize(); + mSetting.setComposition(0); + SysManager.saveSetting(mSetting); + readSettingMenu.initComposition(); + } + + @Override + public void onMarginChange() { + mPageLoader.upMargin(); + mSetting.setComposition(0); + SysManager.saveSetting(mSetting); + readSettingMenu.initComposition(); + } + + @Override + public void onRefreshUI() { + mPageLoader.refreshUi(); + } + + @Override + public void onReset() { + mPageLoader.setTextSize(); + mPageLoader.upMargin(); + mSetting.setComposition(1); + SysManager.saveSetting(mSetting); + readSettingMenu.initComposition(); + ToastUtils.showInfo("已重置各间距为默认值"); + } + }); + autoPageMenu.setOnClickListener(null); + autoPageMenu.setListener(new AutoPageMenu.Callback() { + @Override + public void onSpeedChange() { + if (pageView != null) { + pageView.autoPageOnSpeedChange(); autoPage(); - }, v -> { - Intent intent = new Intent(this, MoreSettingActivity.class); - startActivityForResult(intent, APPCONST.REQUEST_RESET_SCREEN_TIME); - mSettingDialog.dismiss(); - }); + } + } + + @Override + public void onExitClick() { + ToastUtils.showInfo("自动翻页关闭"); + autoPageStop(); + hideReadMenu(); + } + }); + customizeLayoutMenu.setOnClickListener(null); + customizeLayoutMenu.setListener(this, new CustomizeLayoutMenu.Callback() { + @Override + public void upBg() { + mPageLoader.refreshUi(); + } + + @Override + public void upStyle() { + readSettingMenu.initStyleImage(); + } + + }); + brightnessEyeMenu.setOnClickListener(null); + brightnessEyeMenu.setListener(this, new BrightnessEyeMenu.Callback() { + @Override + public void onProtectEyeChange() { + if (mSetting.isProtectEye()) { + openEye(); + } else { + closeEye(); + } + } + + @Override + public void upProtectEye() { + openEye(); + } + }); } - private void showPageModeDialog(final TextView tvPageMode) { - if (mPageModeDialog != null) { - mPageModeDialog.show(); + /** + * 设置屏幕方向 + */ + @SuppressLint("SourceLockedOrientationActivity") + public void setOrientation(boolean isHorizontalScreen) { + if (isHorizontalScreen) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { - //显示翻页模式视图 - int checkedItem; - switch (mSetting.getPageMode()) { - case COVER: - checkedItem = 0; - break; - case SIMULATION: - checkedItem = 1; - break; - case SLIDE: - checkedItem = 2; - break; - case SCROLL: - checkedItem = 3; - break; - case NONE: - checkedItem = 4; - break; - default: - checkedItem = 0; - } - mPageModeDialog = MyAlertDialog.build(this) - .setTitle("翻页模式") - .setSingleChoiceItems(pageMode, checkedItem, (dialog, which) -> { - switch (which) { - case 0: - mSetting.setPageMode(PageMode.COVER); - break; - case 1: - mSetting.setPageMode(PageMode.SIMULATION); - break; - case 2: - mSetting.setPageMode(PageMode.SLIDE); - break; - case 3: - mSetting.setPageMode(PageMode.SCROLL); - break; - case 4: - mSetting.setPageMode(PageMode.NONE); - break; - } - mPageModeDialog.dismiss(); - SysManager.saveSetting(mSetting); - MyApplication.runOnUiThread(() -> mPageLoader.setPageMode(mSetting.getPageMode())); - tvPageMode.setText(pageMode[which]); - }).show(); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } @@ -1245,52 +1435,42 @@ public class ReadActivity extends BaseActivity { //mPageLoader.setPageStyle(!isCurDayStyle); } - /** - * 缩小字体 - */ - private void reduceTextSize() { - if (mSetting.getReadWordSize() > 1) { - mSetting.setReadWordSize(mSetting.getReadWordSize() - 1); - SysManager.saveSetting(mSetting); - mPageLoader.setTextSize((int) mSetting.getReadWordSize()); - } - } /** - * 增大字体 + * 改变阅读风格 */ - private void increaseTextSize() { - if (mSetting.getReadWordSize() < 41) { - mSetting.setReadWordSize(mSetting.getReadWordSize() + 1); - SysManager.saveSetting(mSetting); - mPageLoader.setTextSize((int) mSetting.getReadWordSize()); + private void changeStyle() { + if (!mSetting.isDayStyle()) { + changeNightAndDaySetting(false); } + upBrightnessEye(); + mPageLoader.refreshUi(); } /** - * 改变阅读风格 - * - * @param readStyle + * 更新亮度和护眼 */ - private void changeStyle(ReadStyle readStyle) { - mSetting.setReadStyle(readStyle); - SysManager.saveSetting(mSetting); - if (!mSetting.isDayStyle()) { - DialogCreator.createCommonDialog(this, "提示", "是否希望切换为日间模式?", - false, "确定", "取消", (dialog, which) -> { - changeNightAndDaySetting(false); - }, null); + private void upBrightnessEye() { + if (!mSetting.isBrightFollowSystem()) { + BrightUtil.setBrightness(this, mSetting.getBrightProgress()); + } else { + BrightUtil.followSystemBright(this); + } + if (mSetting.isProtectEye()) { + openEye(); + } else { + closeEye(); } - MyApplication.runOnUiThread(() -> mPageLoader.setPageStyle(true)); } @OnClick({R.id.read_tv_setting, R.id.read_tv_pre_chapter - , R.id.read_tv_next_chapter, R.id.read_tv_night_mode}) + , R.id.read_tv_next_chapter, R.id.read_tv_brightness_eye}) protected void onClick(View view) { switch (view.getId()) { case R.id.read_tv_setting: //设置 - toggleMenu(true); - mSettingDialog.show(); + toggleMenu(false); + readSettingMenu.startAnimation(mBottomInAnim); + readSettingMenu.setVisibility(VISIBLE); break; case R.id.read_tv_pre_chapter: //前一章 mPageLoader.skipPreChapter(); @@ -1298,8 +1478,11 @@ public class ReadActivity extends BaseActivity { case R.id.read_tv_next_chapter: //后一章 mPageLoader.skipNextChapter(); break; - case R.id.read_tv_night_mode: //夜间模式 - changeNightAndDaySetting(mSetting.isDayStyle()); + case R.id.read_tv_brightness_eye: + hideReadMenu(); + brightnessEyeMenu.initWidget(); + brightnessEyeMenu.setVisibility(VISIBLE); + brightnessEyeMenu.startAnimation(mBottomInAnim); break; } } @@ -1312,9 +1495,11 @@ public class ReadActivity extends BaseActivity { //切换菜单 toggleMenu(true); //跳转 - Intent intent = new Intent(this, CatalogActivity.class); - intent.putExtra(APPCONST.BOOK, mBook); - this.startActivityForResult(intent, APPCONST.REQUEST_CHAPTER_PAGE); + mHandler.postDelayed(() -> { + Intent intent = new Intent(this, CatalogActivity.class); + intent.putExtra(APPCONST.BOOK, mBook); + this.startActivityForResult(intent, APPCONST.REQUEST_CHAPTER_PAGE); + }, mBottomOutAnim.getDuration()); } @OnClick(R.id.ll_chapter_view) @@ -1331,14 +1516,28 @@ public class ReadActivity extends BaseActivity { } } - @OnClick(R.id.read_tv_download) protected void download() { if (!isCollected) { - addBookToCaseAndDownload(readTvDownload); + addBookToCaseAndDownload(); } else { - downloadBook(readTvDownload); + downloadBook(); } } + + public void showCustomizeMenu() { + customizeComMenu.initWidget(); + customizeComMenu.setVisibility(VISIBLE); + customizeComMenu.startAnimation(mBottomInAnim); + } + + public void showCustomizeLayoutMenu() { + hideReadMenu(); + + customizeLayoutMenu.upColor(); + customizeLayoutMenu.setVisibility(VISIBLE); + customizeLayoutMenu.startAnimation(mBottomInAnim); + } + /****************息屏相关*****************/ /** * 取消亮屏保持 @@ -1379,7 +1578,7 @@ public class ReadActivity extends BaseActivity { /***************************缓存相关***************************/ private int selectedIndex;//对话框选择下标 - protected void downloadBook(final TextView tvDownloadProgress) { + protected void downloadBook() { if ("本地书籍".equals(mBook.getType())) { ToastUtils.showWarring("《" + mBook.getName() + "》是本地书籍,不能缓存"); return; @@ -1395,23 +1594,23 @@ public class ReadActivity extends BaseActivity { (dialog, which) -> { switch (selectedIndex) { case 0: - addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mPageLoader.getChapterPos() + 50); + addDownload(mPageLoader.getChapterPos(), mPageLoader.getChapterPos() + 50); break; case 1: - addDownload(tvDownloadProgress, mPageLoader.getChapterPos() - 50, mPageLoader.getChapterPos() + 50); + addDownload(mPageLoader.getChapterPos() - 50, mPageLoader.getChapterPos() + 50); break; case 2: - addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mChapters.size()); + addDownload(mPageLoader.getChapterPos(), mChapters.size()); break; case 3: - addDownload(tvDownloadProgress, 0, mChapters.size()); + addDownload(0, mChapters.size()); break; } }).show(); }); } - private void addDownload(final TextView tvDownloadProgress, int begin, int end) { + private void addDownload(int begin, int end) { /*//取消之前下载 if (!isStopDownload) { isStopDownload = true; @@ -1450,13 +1649,11 @@ public class ReadActivity extends BaseActivity { downloadingChapter = chapter.getTitle(); mChapterService.saveOrUpdateChapter(chapter, (String) o); curCacheChapterNum++; - mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress)); } @Override public void onError(Exception e) { curCacheChapterNum++; - mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress)); } }); try { @@ -1474,15 +1671,6 @@ public class ReadActivity extends BaseActivity { }); } - - private void updateDownloadProgress(TextView tvDownloadProgress) { - try { - tvDownloadProgress.setText(curCacheChapterNum * 100 / needCacheChapterNum + " %"); - } catch (Exception e) { - e.printStackTrace(); - } - } - /** * 发送通知 */ @@ -1535,38 +1723,20 @@ public class ReadActivity extends BaseActivity { * 自动翻页 */ private void autoPage() { - mHandler.removeCallbacks(upHpbNextPage); mHandler.removeCallbacks(autoPageRunnable); if (autoPage) { - pbNextPage.setVisibility(View.VISIBLE); - //每页按字数计算一次时间 - nextPageTime = mPageLoader.curPageLength() * 60 * 1000 / mSetting.getAutoScrollSpeed(); - if (0 == nextPageTime) nextPageTime = 1000; - pbNextPage.setMax(nextPageTime); - mHandler.postDelayed(autoPageRunnable, nextPageTime); - nextPageTime = nextPageTime - upHpbInterval * 10; - mHandler.postDelayed(upHpbNextPage, upHpbInterval); - } else { - pbNextPage.setVisibility(View.INVISIBLE); + mPageLoader.setPageMode(PageMode.AUTO); + mPageLoader.skipToNextPage(); + mHandler.postDelayed(autoPageRunnable, mSetting.getAutoScrollSpeed() * 1000); } } - /** - * 更新自动翻页进度条 - */ - private void upHpbNextPage() { - nextPageTime = nextPageTime - upHpbInterval; - if (nextPageTime >= 0) { - pbNextPage.setProgress(nextPageTime); - } - mHandler.postDelayed(upHpbNextPage, upHpbInterval); - } - /** * 停止自动翻页 */ public void autoPageStop() { autoPage = false; + mPageLoader.setPageMode(mSetting.getPageMode()); autoPage(); } @@ -1576,11 +1746,64 @@ public class ReadActivity extends BaseActivity { private void nextPage() { MyApplication.runOnUiThread(() -> { screenOffTimerStart(); - if (mPageLoader != null) { - mPageLoader.skipToNextPage(); - } autoPage(); }); } + /**************************护眼相关*********************************/ + private View vProtectEye; + + private void initEyeView() { + ViewGroup content = findViewById(android.R.id.content); + vProtectEye = new FrameLayout(this); + vProtectEye.setBackgroundColor(mSetting.isProtectEye() ? getFilterColor(mSetting.getBlueFilterPercent()) : Color.TRANSPARENT); //设置透明 + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL //不触碰 + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //不可定焦 + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; //不可触 + params.width = WindowManager.LayoutParams.MATCH_PARENT; + params.height = WindowManager.LayoutParams.MATCH_PARENT; + content.addView(vProtectEye, params); + } + + + /** + * 开启护眼模式 + */ + public void openEye() { + if (vProtectEye == null) { + initEyeView(); + } + vProtectEye.setBackgroundColor(getFilterColor(mSetting.getBlueFilterPercent())); + } + + /** + * 关闭护眼模式 + */ + public void closeEye() { + if (vProtectEye == null) { + initEyeView(); + } + vProtectEye.setBackgroundColor(Color.TRANSPARENT); + } + + /** + * 过滤蓝光 + * + * @param blueFilterPercent 蓝光过滤比例[10-30-80] + */ + public int getFilterColor(int blueFilterPercent) { + int realFilter = blueFilterPercent; + if (realFilter < 10) { + realFilter = 10; + } else if (realFilter > 80) { + realFilter = 80; + } + int a = (int) (realFilter / 80f * 180); + int r = (int) (200 - (realFilter / 80f) * 190); + int g = (int) (180 - (realFilter / 80f) * 170); + int b = (int) (60 - realFilter / 80f * 60); + return Color.argb(a, r, g, b); + } + } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/RegisterActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/RegisterActivity.java new file mode 100644 index 0000000..0e3f265 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/RegisterActivity.java @@ -0,0 +1,271 @@ +package xyz.fycz.myreader.ui.activity; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.appcompat.widget.Toolbar; +import butterknife.BindView; +import com.google.android.material.textfield.TextInputLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.model.backup.UserService; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import xyz.fycz.myreader.ui.dialog.DialogCreator; +import xyz.fycz.myreader.util.CodeUtil; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author fengyue + * @date 2020/9/18 22:37 + */ +public class RegisterActivity extends BaseActivity { + @BindView(R.id.et_username) + TextInputLayout etUsername; + @BindView(R.id.et_password) + TextInputLayout etPassword; + @BindView(R.id.et_rp_password) + TextInputLayout etRpPassword; + @BindView(R.id.et_captcha) + TextInputLayout etCaptcha; + @BindView(R.id.iv_captcha) + ImageView ivCaptcha; + @BindView(R.id.bt_register) + Button btRegister; + @BindView(R.id.tv_register_tip) + TextView tvRegisterTip; + @BindView(R.id.cb_agreement) + CheckBox cbAgreement; + @BindView(R.id.tv_agreement) + TextView tvAgreement; + private String code; + private String username = ""; + private String password = ""; + private String rpPassword = ""; + private String inputCode = ""; + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @SuppressLint("HandlerLeak") + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + createCaptcha(); + break; + case 2: + showTip((String) msg.obj); + break; + case 3: + tvRegisterTip.setVisibility(View.GONE); + break; + } + } + }; + + + @Override + protected int getContentId() { + return R.layout.activity_register; + } + + @Override + protected void setUpToolbar(Toolbar toolbar) { + super.setUpToolbar(toolbar); + setStatusBarColor(R.color.colorPrimary, true); + getSupportActionBar().setTitle("注册"); + } + + @Override + protected void initWidget() { + super.initWidget(); + mHandler.sendMessage(mHandler.obtainMessage(1)); + etUsername.requestFocus(); + etUsername.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + StringUtils.isNotChinese(s); + username = s.toString(); + if (username.length() < 6 || username.length() >14){ + mHandler.sendMessage(mHandler.obtainMessage(2, "用户名必须在6-14位之间")); + } else if(!username.substring(0, 1).matches("^[A-Za-z]$")){ + mHandler.sendMessage(mHandler.obtainMessage(2, + "用户名只能以字母开头")); + }else if(!username.matches("^[A-Za-z0-9-_]+$")){ + mHandler.sendMessage(mHandler.obtainMessage(2, + "用户名只能由数字、字母、下划线、减号组成")); + }else { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + checkNotNone(); + } + }); + + etPassword.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + password = s.toString(); + if (password.length() < 8 || password.length() > 16){ + mHandler.sendMessage(mHandler.obtainMessage(2, "密码必须在8-16位之间")); + } else if(password.matches("^\\d+$")){ + mHandler.sendMessage(mHandler.obtainMessage(2, "密码不能是纯数字")); + } else { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + checkNotNone(); + } + }); + + etRpPassword.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + rpPassword = s.toString(); + if (!rpPassword.equals(password)){ + mHandler.sendMessage(mHandler.obtainMessage(2, "两次输入的密码不一致")); + } else { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + checkNotNone(); + } + }); + + etCaptcha.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + inputCode = s.toString().trim().toLowerCase(); + if (!inputCode.equals(code.toLowerCase())){ + mHandler.sendMessage(mHandler.obtainMessage(2, "验证码错误")); + } else { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + checkNotNone(); + } + }); + + tvAgreement.setMovementMethod(LinkMovementMethod.getInstance()); + + } + + @Override + protected void initClick() { + super.initClick(); + ivCaptcha.setOnClickListener(v -> mHandler.sendMessage(mHandler.obtainMessage(1))); + + btRegister.setOnClickListener(v -> { + if (!username.matches("^[A-Za-z][A-Za-z0-9]{5,13}$")){ + DialogCreator.createTipDialog(this, "用户名格式错误", + "用户名必须在6-14位之间\n用户名只能以字母开头\n用户名只能由数字、字母、下划线、减号组成"); + }else if(password.matches("^\\d+$") || !password.matches("^.{8,16}$")){ + DialogCreator.createTipDialog(this, "密码格式错误", + "密码必须在8-16位之间\n密码不能是纯数字"); + }else if(!password.equals(rpPassword)){ + DialogCreator.createTipDialog(this, "重复密码错误", + "两次输入的密码不一致"); + }else if(!inputCode.trim().toLowerCase().equals(code.toLowerCase())){ + DialogCreator.createTipDialog(this, "验证码错误"); + }else if(!cbAgreement.isChecked()){ + DialogCreator.createTipDialog(this, "请勾选同意《用户服务协议》"); + }else { + ProgressDialog dialog = DialogCreator.createProgressDialog(this, null, "正在注册..."); + Map userRegisterInfo = new HashMap<>(); + userRegisterInfo.put("username", username); + userRegisterInfo.put("password", password); + UserService.register(userRegisterInfo, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + String[] info = ((String) o).split(":"); + int result = Integer.parseInt(info[0].trim()); + if (result == 101){ + UserService.writeUsername(username); + ToastUtils.showSuccess(info[1]); + finish(); + }else { + ToastUtils.showWarring(info[1]); + } + dialog.dismiss(); + } + @Override + public void onError(Exception e) { + ToastUtils.showError("注册失败:\n" + e.getLocalizedMessage()); + dialog.dismiss(); + } + }); + } + mHandler.sendMessage(mHandler.obtainMessage(1)); + }); + + } + + public void createCaptcha() { + code = CodeUtil.getInstance().createCode(); + Bitmap codeBitmap = CodeUtil.getInstance().createBitmap(code); + ivCaptcha.setImageBitmap(codeBitmap); + } + + public void showTip(String tip) { + tvRegisterTip.setVisibility(View.VISIBLE); + tvRegisterTip.setText(tip); + } + + public void checkNotNone(){ + if ("".equals(username) || "".equals(password) || "".equals(rpPassword) || "".equals(inputCode)){ + btRegister.setEnabled(false); + }else { + btRegister.setEnabled(true); + } + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java index 603a760..99f683f 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java @@ -13,16 +13,24 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.*; + +import androidx.appcompat.widget.AppCompatRadioButton; import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import butterknife.BindView; + import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.scwang.smartrefresh.layout.SmartRefreshLayout; + import me.gujun.android.taggroup.TagGroup; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.util.utils.StringUtils; import xyz.fycz.myreader.webapi.callback.ResultCallback; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.model.SearchEngine; @@ -44,6 +52,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -79,6 +88,14 @@ public class SearchBookActivity extends BaseActivity { SmartRefreshLayout srlSearchBookList; @BindView(R.id.fabSearchStop) FloatingActionButton fabSearchStop; + @BindView(R.id.rg_search_filter) + RadioGroup rbSearchFilter; + @BindView(R.id.rb_all_search) + AppCompatRadioButton rbAllSearch; + @BindView(R.id.rb_fuzzy_search) + AppCompatRadioButton rbFuzzySearch; + @BindView(R.id.rb_precise_search) + AppCompatRadioButton rbPreciseSearch; private SearchBookAdapter mSearchBookAdapter; @@ -103,6 +120,8 @@ public class SearchBookActivity extends BaseActivity { private SearchEngine searchEngine; + private Setting mSetting; + private static String[] suggestion = {"第一序列", "大道朝天", "伏天氏", "终极斗罗", "我师兄实在太稳健了", "烂柯棋缘", "诡秘之主"}; private static String[] suggestion2 = {"不朽凡人", "圣墟", "我是至尊", "龙王传说", "太古神王", "一念永恒", "雪鹰领主", "大主宰"}; @@ -145,10 +164,9 @@ public class SearchBookActivity extends BaseActivity { @Override protected void initData(Bundle savedInstanceState) { super.initData(savedInstanceState); + mSetting = SysManager.getSetting(); mSearchHistoryService = SearchHistoryService.getInstance(); - for (int i = 0; i < suggestion.length; i++) { - mSuggestions.add(suggestion[i]); - } + Collections.addAll(mSuggestions, suggestion); searchEngine = new SearchEngine(); searchEngine.setOnSearchListener(new SearchEngine.OnSearchListener() { @@ -193,6 +211,38 @@ public class SearchBookActivity extends BaseActivity { } return false; }); + + switch (mSetting.getSearchFilter()) { + case 0: + rbAllSearch.setChecked(true); + break; + case 1: + default: + rbFuzzySearch.setChecked(true); + break; + case 2: + rbPreciseSearch.setChecked(true); + break; + } + + rbSearchFilter.setOnCheckedChangeListener((group, checkedId) -> { + int searchFilter; + switch (checkedId){ + case R.id.rb_all_search: + default: + searchFilter = 0; + break; + case R.id.rb_fuzzy_search: + searchFilter = 1; + break; + case R.id.rb_precise_search: + searchFilter = 2; + break; + } + mSetting.setSearchFilter(searchFilter); + SysManager.saveSetting(mSetting); + }); + //搜索框改变事件 etSearchKey.addTextChangedListener(new TextWatcher() { @@ -216,6 +266,7 @@ public class SearchBookActivity extends BaseActivity { } }); + rvSearchBooksList.setLayoutManager(new LinearLayoutManager(this)); //上拉刷新 @@ -314,8 +365,6 @@ public class SearchBookActivity extends BaseActivity { } - - /** * 获取搜索数据 */ diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java index a893cdb..14cd59e 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java @@ -75,6 +75,11 @@ public class SplashActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 避免从桌面启动程序后,会重新实例化入口类的activity + if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { + finish(); + return; + } ImmersionBar.with(this) .fullScreen(true) .init(); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookStoreBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookStoreBookAdapter.java index b34722d..b623183 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookStoreBookAdapter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookStoreBookAdapter.java @@ -1,6 +1,10 @@ package xyz.fycz.myreader.ui.adapter; import android.app.Activity; +import android.os.Handler; + +import java.util.List; + import xyz.fycz.myreader.base.adapter.BaseListAdapter; import xyz.fycz.myreader.base.adapter.IViewHolder; import xyz.fycz.myreader.greendao.entity.Book; @@ -21,4 +25,17 @@ public class BookStoreBookAdapter extends BaseListAdapter { protected IViewHolder createViewHolder(int viewType) { return new BookStoreBookHolder(hasImg, mActivity); } + + @Override + public void addItems(List values) { + mList.addAll(values); + + Handler handler = new Handler(); + handler.post(new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }); + } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java index 0e47e0a..cd8cfb5 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java @@ -1,5 +1,6 @@ package xyz.fycz.myreader.ui.adapter; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java index 225cbb6..4bce276 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java @@ -1,5 +1,6 @@ package xyz.fycz.myreader.ui.adapter; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/FontsAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/FontsAdapter.java index 1c31672..1c0df86 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/FontsAdapter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/FontsAdapter.java @@ -4,12 +4,14 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.BitmapFactory; import android.os.Handler; import android.os.Message; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -94,7 +96,7 @@ public class FontsAdapter extends ArrayAdapter { convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null); viewHolder.tvFontName = (TextView) convertView.findViewById(R.id.tv_font_name); viewHolder.btnFontUse = (ProgressButton) convertView.findViewById(R.id.btn_font_use); - //viewHolder.tvExample = (TextView)convertView.findViewById(R.id.tv_font_example); + viewHolder.ivExample = (ImageView) convertView.findViewById(R.id.iv_font_example); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); @@ -105,20 +107,22 @@ public class FontsAdapter extends ArrayAdapter { private void initView(int position, final ViewHolder viewHolder) { final Font font = getItem(position); - /*Typeface typeFace = null; - if (font != Font.默认字体) { - if (!mTypefaceMap.containsKey(font)){ - typeFace = Typeface.createFromAsset(getContext().getAssets(), font.path); - mTypefaceMap.put(font,typeFace); - }else { - typeFace = mTypefaceMap.get(font); + + if (font != Font.本地字体 && font != Font.默认字体) { + try { + viewHolder.ivExample.setVisibility(View.VISIBLE); + viewHolder.ivExample.setImageBitmap(BitmapFactory.decodeStream(mFontsActivity.getAssets().open("font_img/" + font.toString() + ".png"))); + } catch (IOException e) { + e.printStackTrace(); + viewHolder.ivExample.setVisibility(View.GONE); } - }*/ - //viewHolder.tvExample.setTypeface(typeFace); -// viewHolder.tvExample.setTypeface(mTypefaceMap.get(font)); + } else { + viewHolder.ivExample.setVisibility(View.GONE); + } + viewHolder.tvFontName.setText(font.toString()); viewHolder.tvFontName.setTextColor(mFontsActivity.getResources().getColor(R.color.textPrimary)); - File fontFile = new File(APPCONST.FONT_BOOK_DIR + font.fileName); + File fontFile = new File(APPCONST.FONT_BOOK_DIR + font.toString() + ".ttf"); if (font == Font.本地字体) { if (setting.getFont() == Font.本地字体) { viewHolder.tvFontName.setText(setting.getLocalFontName()); @@ -129,50 +133,46 @@ public class FontsAdapter extends ArrayAdapter { viewHolder.btnFontUse.setButtonColor(mFontsActivity.getResources().getColor(R.color.sys_blue_littler)); } viewHolder.btnFontUse.setEnabled(true); - viewHolder.btnFontUse.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - /*ArrayList localFontFiles = getLocalFontList(); - if (localFontFiles == null || localFontFiles.size() == 0) { - DialogCreator.createTipDialog(getContext(), getContext().getString(R.string.font_select_tip)); - return; - } - final CharSequence[] fontNames = new CharSequence[localFontFiles.size()]; - int checkedItem = 0; - for (int i = 0; i < fontNames.length; i++) { - fontNames[i] = localFontFiles.get(i).getName(); - if (font.fileName.equals(fontNames[i])) { - checkedItem = i; - } + viewHolder.btnFontUse.setOnClickListener(v -> { + /*ArrayList localFontFiles = getLocalFontList(); + if (localFontFiles == null || localFontFiles.size() == 0) { + DialogCreator.createTipDialog(getContext(), getContext().getString(R.string.font_select_tip)); + return; + } + final CharSequence[] fontNames = new CharSequence[localFontFiles.size()]; + int checkedItem = 0; + for (int i = 0; i < fontNames.length; i++) { + fontNames[i] = localFontFiles.get(i).getName(); + if (font.fileName.equals(fontNames[i])) { + checkedItem = i; } - MyAlertDialog dialog = new MyAlertDialog.Builder(getContext()) - .setTitle(getContext().getString(R.string.font_select)) - .setCancelable(true) - .setSingleChoiceItems(fontNames, checkedItem, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - font.fileName = (String) fontNames[which]; - setting.setFont(font); - setting.setLocalFontName(font.fileName); - SysManager.saveSetting(setting); - notifyDataSetChanged(); - Intent intent = new Intent(); - intent.putExtra(APPCONST.FONT, font); - ((Activity) getContext()).setResult(Activity.RESULT_OK, intent); - dialog.dismiss(); - } - }).create(); - dialog.show();*/ - ToastUtils.showInfo("请选择一个ttf格式的字体文件"); - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - mFontsActivity.startActivityForResult(intent, APPCONST.SELECT_FILE_CODE); } + MyAlertDialog dialog = new MyAlertDialog.Builder(getContext()) + .setTitle(getContext().getString(R.string.font_select)) + .setCancelable(true) + .setSingleChoiceItems(fontNames, checkedItem, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + font.fileName = (String) fontNames[which]; + setting.setFont(font); + setting.setLocalFontName(font.fileName); + SysManager.saveSetting(setting); + notifyDataSetChanged(); + Intent intent = new Intent(); + intent.putExtra(APPCONST.FONT, font); + ((Activity) getContext()).setResult(Activity.RESULT_OK, intent); + dialog.dismiss(); + } + }).create(); + dialog.show();*/ + ToastUtils.showInfo("请选择一个ttf格式的字体文件"); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + mFontsActivity.startActivityForResult(intent, APPCONST.SELECT_FILE_CODE); }); return; } - if (font != Font.默认字体 && !fontFile.exists()) { viewHolder.btnFontUse.setEnabled(true); viewHolder.btnFontUse.setButtonColor(mFontsActivity.getResources().getColor(R.color.sys_blue_littler)); @@ -206,7 +206,7 @@ public class FontsAdapter extends ArrayAdapter { mHandler.sendMessage(mHandler.obtainMessage(3)); return; } - final String[] url = {URLCONST.FONT_DOWNLOAD_URL + font.fileName}; + final String[] url = {URLCONST.FONT_DOWNLOAD_URL + font.toString() + ".ttf"}; viewHolder.btnFontUse.setText("获取连接..."); CommonApi.getUrl(font.downloadPath, new ResultCallback() { @Override @@ -237,7 +237,7 @@ public class FontsAdapter extends ArrayAdapter { con = (HttpURLConnection) webUrl.openConnection(); is = con.getInputStream(); int fileLength = con.getContentLength(); - String filePath = APPCONST.FONT_BOOK_DIR + font.fileName + ".temp"; + String filePath = APPCONST.FONT_BOOK_DIR + font.toString() + ".ttf.temp"; fontFile = FileUtils.getFile(filePath); fos = new FileOutputStream(fontFile); byte[] tem = new byte[1024]; @@ -300,7 +300,7 @@ public class FontsAdapter extends ArrayAdapter { if (font == Font.本地字体) { continue; } - if (font.fileName.equals(fontFile.getName())) { + if ((font.toString() + ".ttf").equals(fontFile.getName())) { continue fontFilesFor; } } @@ -311,7 +311,7 @@ public class FontsAdapter extends ArrayAdapter { return localFontFiles; } - public void saveLocalFontName(String fontName){ + public void saveLocalFontName(String fontName) { setting.setFont(Font.本地字体); setting.setLocalFontName(fontName); SysManager.saveSetting(setting); @@ -322,7 +322,7 @@ public class FontsAdapter extends ArrayAdapter { } class ViewHolder { - //TextView tvExample; + ImageView ivExample; TextView tvFontName; ProgressButton btnFontUse; } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java index f31dce3..8196d87 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java @@ -2,7 +2,9 @@ package xyz.fycz.myreader.ui.adapter; import android.app.Activity; import android.text.TextUtils; + import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.base.adapter.BaseListAdapter; import xyz.fycz.myreader.base.adapter.IViewHolder; import xyz.fycz.myreader.entity.SearchBookBean; @@ -10,6 +12,7 @@ import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.model.SearchEngine; import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; import xyz.fycz.myreader.ui.adapter.holder.SearchBookHolder; +import xyz.fycz.myreader.util.utils.StringUtils; import java.util.ArrayList; import java.util.List; @@ -38,13 +41,38 @@ public class SearchBookAdapter extends BaseListAdapter { public synchronized void addAll(List newDataS, String keyWord) { List copyDataS = mList; - if (newDataS != null && newDataS.size() > 0) { + List filterDataS = new ArrayList<>(); + + switch (SysManager.getSetting().getSearchFilter()) { + case 0: + filterDataS.addAll(newDataS); + break; + case 1: + default: + for (SearchBookBean ssb : newDataS) { + if (StringUtils.isContainEachOther(ssb.getName(), keyWord) || + StringUtils.isContainEachOther(ssb.getAuthor(), keyWord)) { + filterDataS.add(ssb); + } + } + break; + case 2: + for (SearchBookBean ssb : newDataS) { + if (StringUtils.isEqual(ssb.getName(), keyWord) || + StringUtils.isEqual(ssb.getAuthor(), keyWord)) { + filterDataS.add(ssb); + } + } + break; + } + + if (filterDataS != null && filterDataS.size() > 0) { List searchBookBeansAdd = new ArrayList<>(); if (copyDataS.size() == 0) { - copyDataS.addAll(newDataS); + copyDataS.addAll(filterDataS); } else { //存在 - for (SearchBookBean temp : newDataS) { + for (SearchBookBean temp : filterDataS) { boolean hasSame = false; for (int i = 0, size = copyDataS.size(); i < size; i++) { SearchBookBean searchBook = copyDataS.get(i); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java index 7fed604..a096889 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java @@ -16,6 +16,7 @@ import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.enums.BookSource; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.ui.activity.BookDetailedActivity; +import xyz.fycz.myreader.util.StringHelper; import xyz.fycz.myreader.widget.CoverImageView; /** @@ -59,7 +60,8 @@ public class BookStoreBookHolder extends ViewHolderImpl { public void onBind(Book data, int pos) { tvBookName.setText(data.getName()); tvBookAuthor.setText(data.getAuthor()); - tvBookNewestChapter.setText(data.getNewestChapterTitle()); + tvBookNewestChapter.setText(StringHelper.isEmpty(data.getNewestChapterTitle()) ? + data.getDesc() : data.getNewestChapterTitle()); tvBookTime.setText(data.getUpdateDate()); if (hasImg){ tvBookImg.setVisibility(View.VISIBLE); @@ -68,7 +70,7 @@ public class BookStoreBookHolder extends ViewHolderImpl { } } if (data.getSource() != null) { - tvBookSource.setText("书源:" + BookSource.fromString(data.getSource()).text); + tvBookSource.setText(String.format("书源:%s", BookSource.fromString(data.getSource()).text)); }else { tvBookSource.setText(data.getNewestChapterId()); } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java index 81c3052..f3fc9cb 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java @@ -4,22 +4,17 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.os.Handler; import android.util.Log; -import android.widget.ImageView; import android.widget.TextView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; -import com.bumptech.glide.request.RequestOptions; + import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.base.adapter.ViewHolderImpl; import xyz.fycz.myreader.model.SearchEngine; -import xyz.fycz.myreader.webapi.callback.ResultCallback; import xyz.fycz.myreader.entity.SearchBookBean; import xyz.fycz.myreader.enums.BookSource; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; import xyz.fycz.myreader.util.StringHelper; -import xyz.fycz.myreader.webapi.CommonApi; import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; @@ -40,7 +35,7 @@ public class SearchBookHolder extends ViewHolderImpl { case 1: Book book = (Book) message.obj; int pos = message.arg1; - initOtherInfo(book, pos); + initOtherInfo(book); break; } return false; @@ -84,7 +79,6 @@ public class SearchBookHolder extends ViewHolderImpl { if (StringHelper.isEmpty(book.getImgUrl())){ book.setImgUrl(""); } - if (!StringHelper.isEmpty(book.getDesc())) { tvDesc.setText("简介:" + book.getDesc()); } @@ -104,7 +98,7 @@ public class SearchBookHolder extends ViewHolderImpl { if (tvBookName.getTag() == null || !(Boolean) tvBookName.getTag()) { tvBookName.setTag(true); } else { - initOtherInfo(book, pos); + initOtherInfo(book); return; } Log.i(book.getName(), "initOtherInfo"); @@ -116,11 +110,11 @@ public class SearchBookHolder extends ViewHolderImpl { tvBookName.setTag(false); }); }else { - initOtherInfo(book, pos); + initOtherInfo(book); } } - private void initOtherInfo(Book book, int pos){ + private void initOtherInfo(Book book){ //简介 if (StringHelper.isEmpty(tvDesc.getText().toString())) { tvDesc.setText("简介:" + book.getDesc()); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/dialog/AudioPlayerDialog.java b/app/src/main/java/xyz/fycz/myreader/ui/dialog/AudioPlayerDialog.java index 565e295..1ef742c 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/dialog/AudioPlayerDialog.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/dialog/AudioPlayerDialog.java @@ -254,6 +254,7 @@ public class AudioPlayerDialog extends Dialog{ public void readAloud() { + mPageLoader.resetReadAloudParagraph(); aloudNextPage = false; String unReadContent = mPageLoader.getUnReadContent(); ReadAloudService.setReadEvent(new ReadEvent()); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/dialog/DialogCreator.java b/app/src/main/java/xyz/fycz/myreader/ui/dialog/DialogCreator.java index 5b00cc6..e133e9c 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/dialog/DialogCreator.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/dialog/DialogCreator.java @@ -24,6 +24,7 @@ import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.common.URLCONST; import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.enums.Language; import xyz.fycz.myreader.enums.ReadStyle; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.Chapter; @@ -35,280 +36,8 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import static xyz.fycz.myreader.enums.ReadStyle.blueDeep; -import static xyz.fycz.myreader.enums.ReadStyle.breen; -import static xyz.fycz.myreader.enums.ReadStyle.common; -import static xyz.fycz.myreader.enums.ReadStyle.leather; -import static xyz.fycz.myreader.enums.ReadStyle.protectedEye; - - - public class DialogCreator { - private static ImageView ivLastSelectd = null; - - - /** - * 阅读详细设置对话框 - * @param context - * @param setting - * @param onReadStyleChangeListener - * @param reduceSizeListener - * @param increaseSizeListener - * @param languageChangeListener - * @param onFontClickListener - * @return - */ - public static Dialog createReadDetailSetting(final Context context, final Setting setting, - final OnReadStyleChangeListener onReadStyleChangeListener, - final View.OnClickListener reduceSizeListener, - final View.OnClickListener increaseSizeListener, - final View.OnClickListener languageChangeListener, - final View.OnClickListener onFontClickListener, - final OnPageModeChangeListener onModeClickListener, - View.OnClickListener autoScrollListener, - View.OnClickListener moreSettingListener) { - final Dialog dialog = new Dialog(context, R.style.jmui_default_dialog_style); - final View view = LayoutInflater.from(context).inflate(R.layout.dialog_read_setting_detail, null); - dialog.setContentView(view); - dialog.setCancelable(true); - dialog.setCanceledOnTouchOutside(true); - - //触摸外部关闭 - view.findViewById(R.id.ll_bottom_view).setOnClickListener(null); - view.setOnTouchListener(new View.OnTouchListener() { - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - dialog.dismiss(); - return false; - } - }); - //设置全屏 - Window window = dialog.getWindow(); - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - window.setWindowAnimations(R.style.dialogWindowAnim); - //阅读背景风格 - final ImageView ivCommonStyle = (ImageView) view.findViewById(R.id.iv_common_style); - final ImageView ivLeatherStyle = (ImageView) view.findViewById(R.id.iv_leather_style); - final ImageView ivProtectEyeStyle = (ImageView) view.findViewById(R.id.iv_protect_eye_style); - final ImageView ivBreenStyle = (ImageView) view.findViewById(R.id.iv_breen_style); - final ImageView ivBlueDeepStyle = (ImageView) view.findViewById(R.id.iv_blue_deep_style); - switch (setting.getReadStyle()) { - case common: - ivCommonStyle.setSelected(true); - ivLastSelectd = ivCommonStyle; - break; - case leather: - ivLeatherStyle.setSelected(true); - ivLastSelectd = ivLeatherStyle; - break; - case protectedEye: - ivProtectEyeStyle.setSelected(true); - ivLastSelectd = ivProtectEyeStyle; - break; - case breen: - ivBreenStyle.setSelected(true); - ivLastSelectd = ivBreenStyle; - break; - case blueDeep: - ivBlueDeepStyle.setSelected(true); - ivLastSelectd = ivBlueDeepStyle; - break; - } - ivCommonStyle.setOnClickListener(v -> selectedStyle(ivCommonStyle, common, onReadStyleChangeListener)); - ivLeatherStyle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - selectedStyle(ivLeatherStyle, leather, onReadStyleChangeListener); - } - }); - ivProtectEyeStyle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - selectedStyle(ivProtectEyeStyle, protectedEye, onReadStyleChangeListener); - } - }); - ivBlueDeepStyle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - selectedStyle(ivBlueDeepStyle, blueDeep, onReadStyleChangeListener); - } - }); - ivBreenStyle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - selectedStyle(ivBreenStyle, breen, onReadStyleChangeListener); - } - }); - - //字体大小 - TextView tvSizeReduce = (TextView) view.findViewById(R.id.tv_reduce_text_size); - TextView tvSizeIncrease = (TextView) view.findViewById(R.id.tv_increase_text_size); - final TextView tvSize = (TextView) view.findViewById(R.id.tv_text_size); - tvSize.setText(String.valueOf((int) setting.getReadWordSize())); - tvSizeReduce.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (setting.getReadWordSize() > 1) { - tvSize.setText(String.valueOf((int) setting.getReadWordSize() - 1)); - if (reduceSizeListener != null) { - reduceSizeListener.onClick(v); - } - } - } - }); - tvSizeIncrease.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (setting.getReadWordSize() < 41) { - tvSize.setText(String.valueOf((int) setting.getReadWordSize() + 1)); - if (increaseSizeListener != null) { - increaseSizeListener.onClick(v); - } - } - } - }); - - //亮度调节 - SeekBar seekBar = (SeekBar) view.findViewById(R.id.sb_brightness_progress); - final TextView tvBrightFollowSystem = (TextView) view.findViewById(R.id.tv_system_brightness); - seekBar.setProgress(setting.getBrightProgress()); - tvBrightFollowSystem.setSelected(setting.isBrightFollowSystem()); - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - BrightUtil.setBrightness((AppCompatActivity) context, BrightUtil.progressToBright(progress)); - tvBrightFollowSystem.setSelected(false); - setting.setBrightProgress(progress); - setting.setBrightFollowSystem(false); - SysManager.saveSetting(setting); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - }); - - //亮度跟随系统 - tvBrightFollowSystem.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - tvBrightFollowSystem.setSelected(!tvBrightFollowSystem.isSelected()); - if (tvBrightFollowSystem.isSelected()) { - BrightUtil.followSystemBright((AppCompatActivity) context); - setting.setBrightFollowSystem(true); - SysManager.saveSetting(setting); - } else { - BrightUtil.setBrightness((AppCompatActivity) context, BrightUtil.progressToBright(setting.getBrightProgress())); - setting.setBrightFollowSystem(false); - SysManager.saveSetting(setting); - } - } - }); - //音量键翻页 - final TextView tvIsVolumeTurnPage = (TextView) view.findViewById(R.id.tv_isVolumeTurnPage); - if (setting.isVolumeTurnPage()) { - tvIsVolumeTurnPage.setText("关"); - } else { - tvIsVolumeTurnPage.setText("开"); - } - tvIsVolumeTurnPage.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (tvIsVolumeTurnPage.getText().toString().equals("关")) { - tvIsVolumeTurnPage.setText("开"); - } else { - tvIsVolumeTurnPage.setText("关"); - } - if (languageChangeListener != null) { - languageChangeListener.onClick(v); - } - } - }); - - //选择字体 - TextView tvFont = (TextView)view.findViewById(R.id.tv_text_font); - tvFont.setOnClickListener(onFontClickListener); - - //选择翻页模式 - TextView tvMode = view.findViewById(R.id.tv_page_mode); - tvMode.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (onModeClickListener != null){ - onModeClickListener.onChange((TextView) v); - } - } - }); - switch (setting.getPageMode()) { - case COVER: - tvMode.setText("覆盖"); - break; - case SIMULATION: - tvMode.setText("仿真"); - break; - case SLIDE: - tvMode.setText("滑动"); - break; - case SCROLL: - tvMode.setText("滚动"); - break; - case NONE: - tvMode.setText("无"); - break; - } - - //自动滚屏速度 - SeekBar sbScrollSpeed = view.findViewById(R.id.sb_auto_scroll_progress); - TextView tvAutoScroll = view.findViewById(R.id.tv_auto_scroll); - TextView tvAutoScrollSpeed = view.findViewById(R.id.tv_auto_scroll_speed); - sbScrollSpeed.setProgress(setting.getAutoScrollSpeed() / 3); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - sbScrollSpeed.setMin(100); - } - tvAutoScrollSpeed.setText("每分钟阅读字数(CPM):" + setting.getAutoScrollSpeed() + "CPM"); - sbScrollSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - int speed = progress == 0 ? 300 : progress * 3; - setting.setAutoScrollSpeed(speed); - tvAutoScrollSpeed.setText("每分钟阅读字数(CPM):" + speed + "CPM"); - SysManager.saveSetting(setting); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - }); - tvAutoScroll.setOnClickListener(autoScrollListener); - - view.findViewById(R.id.tv_read_setting_more).setOnClickListener(moreSettingListener); - - return dialog; - } - - private static void selectedStyle(ImageView curSelected, ReadStyle readStyle, OnReadStyleChangeListener listener) { - ivLastSelectd.setSelected(false); - ivLastSelectd = curSelected; - curSelected.setSelected(true); - if (listener != null) { - listener.onChange(readStyle); - } - } - /** * 创建一个普通对话框(包含确定、取消按键) * diff --git a/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java b/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java index 8562a5a..ba063ee 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java @@ -11,6 +11,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import androidx.appcompat.app.AlertDialog; + import com.google.android.material.textfield.TextInputLayout; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; @@ -83,6 +84,7 @@ public class MyAlertDialog { return createInputDia(context, title, hint, initText, InputType.TYPE_CLASS_TEXT, cancelable, maxLen, oic, posListener); } + public interface onInputChangeListener{ void onChange(String text); } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java index 5f591e6..431d43f 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java @@ -8,8 +8,12 @@ import xyz.fycz.myreader.base.BaseFragment; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.ui.activity.BookstoreActivity; import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.find.XS7Rank; +import xyz.fycz.myreader.webapi.crawler.read.Ben100ReadCrawler; +import xyz.fycz.myreader.webapi.crawler.read.MiaoBiReadCrawler; import xyz.fycz.myreader.webapi.crawler.read.QB5ReadCrawler; import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; +import xyz.fycz.myreader.webapi.crawler.read.XS7ReadCrawler; /** * @author fengyue @@ -20,14 +24,20 @@ public class FindFragment extends BaseFragment { RelativeLayout mRlQiDianTop; @BindView(R.id.find_rl_qidian_ns_top) RelativeLayout mRlQiDianNSTop; + @BindView(R.id.find_rl_xs7_top) + RelativeLayout mRlXS7Top; @BindView(R.id.find_rl_qidian_sort) RelativeLayout mRlQiDianSort; @BindView(R.id.find_rl_qidian_ns_sort) RelativeLayout mRlQiDianNSSort; @BindView(R.id.find_rl_qb5_store) RelativeLayout mRlQB5Store; - @BindView(R.id.find_rl_biquge_store) - RelativeLayout mRlBiQuGeStore; + @BindView(R.id.find_rl_ben100_store) + RelativeLayout mRlBen100Store; + @BindView(R.id.find_rl_miaoqu_store) + RelativeLayout mRlMiaoQuStore; + @BindView(R.id.find_rl_xs7_store) + RelativeLayout mRlXS7Store; @Override protected int getContentId() { @@ -39,9 +49,13 @@ public class FindFragment extends BaseFragment { super.initClick(); mRlQiDianTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(false))); mRlQiDianNSTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(true))); + mRlXS7Top.setOnClickListener(v -> comeToBookstore(new XS7Rank())); mRlQiDianSort.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(false, true))); mRlQiDianNSSort.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(true, true))); mRlQB5Store.setOnClickListener(v -> comeToBookstore(new QB5ReadCrawler())); + mRlBen100Store.setOnClickListener(v -> comeToBookstore(new Ben100ReadCrawler())); + mRlMiaoQuStore.setOnClickListener(v -> comeToBookstore(new MiaoBiReadCrawler())); + mRlXS7Store.setOnClickListener(v -> comeToBookstore(new XS7ReadCrawler())); } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java index a6ed8aa..ae4cfe8 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java @@ -8,11 +8,13 @@ import android.os.Message; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import butterknife.BindView; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.model.backup.UserService; import xyz.fycz.myreader.base.BaseFragment; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.model.storage.Backup; @@ -23,28 +25,45 @@ import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.service.BookService; import xyz.fycz.myreader.ui.activity.AboutActivity; +import xyz.fycz.myreader.ui.activity.LoginActivity; import xyz.fycz.myreader.ui.activity.MoreSettingActivity; import xyz.fycz.myreader.util.SharedPreUtils; import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import java.io.File; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; + +import static android.app.Activity.RESULT_OK; /** * @author fengyue * @date 2020/9/13 13:20 */ public class MineFragment extends BaseFragment { + @BindView(R.id.mine_rl_user) + RelativeLayout mRlUser; + @BindView(R.id.tv_user) + TextView mTvUser; @BindView(R.id.mine_rl_backup) RelativeLayout mRlBackup; + @BindView(R.id.mine_rl_syn) + RelativeLayout mRlSyn; @BindView(R.id.mine_rl_setting) RelativeLayout mRlSetting; @BindView(R.id.mine_rl_theme_mode) RelativeLayout mRlThemeMode; @BindView(R.id.tv_theme_mode_select) TextView tvThemeModeSelect; + @BindView(R.id.mine_rl_feedback) + RelativeLayout mRlFeedback; @BindView(R.id.mine_rl_about) RelativeLayout mRlAbout; + private boolean isLogin; private Setting mSetting; private String[] webSynMenu; private String[] backupMenu; @@ -57,7 +76,7 @@ public class MineFragment extends BaseFragment { public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 1: - + mTvUser.setText("登录/注册"); break; case 2: backup(); @@ -80,6 +99,7 @@ public class MineFragment extends BaseFragment { @Override protected void initData(Bundle savedInstanceState) { super.initData(savedInstanceState); + isLogin = UserService.isLogin(); mSetting = SysManager.getSetting(); webSynMenu = new String[]{ MyApplication.getmContext().getString(R.string.menu_backup_webBackup), @@ -97,12 +117,35 @@ public class MineFragment extends BaseFragment { @Override protected void initWidget(Bundle savedInstanceState) { super.initWidget(savedInstanceState); + if (isLogin) { + mTvUser.setText(UserService.readUsername()); + } tvThemeModeSelect.setText(themeModeArr[themeMode]); } @Override protected void initClick() { super.initClick(); + mRlUser.setOnClickListener(v -> { + if (isLogin) { + DialogCreator.createCommonDialog(getActivity(), "退出登录", "确定要退出登录吗?" + , true, (dialog, which) -> { + File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy"); + if (file.delete()) { + ToastUtils.showSuccess("退出成功"); + isLogin = false; + mHandler.sendEmptyMessage(1); + Intent intent = new Intent(getActivity(), LoginActivity.class); + getActivity().startActivityForResult(intent, APPCONST.REQUEST_LOGIN); + } else { + ToastUtils.showError("退出失败(Error:file.delete())"); + } + }, (dialog, which) -> dialog.dismiss()); + } else { + Intent intent = new Intent(getActivity(), LoginActivity.class); + getActivity().startActivityForResult(intent, APPCONST.REQUEST_LOGIN); + } + }); mRlBackup.setOnClickListener(v -> { AlertDialog bookDialog = MyAlertDialog.build(getContext()) .setTitle(getContext().getResources().getString(R.string.menu_bookcase_backup)) @@ -121,6 +164,46 @@ public class MineFragment extends BaseFragment { .create(); bookDialog.show(); }); + mRlSyn.setOnClickListener(v -> { + if (!UserService.isLogin()) { + ToastUtils.showWarring("请先登录!"); + Intent loginIntent = new Intent(getActivity(), LoginActivity.class); + getActivity().startActivityForResult(loginIntent, APPCONST.REQUEST_LOGIN); + return; + } + if (mSetting.isAutoSyn()) { + webSynMenu[2] = MyApplication.getmContext().getString(R.string.menu_backup_autoSyn) + "已开启"; + } else { + webSynMenu[2] = MyApplication.getmContext().getString(R.string.menu_backup_autoSyn) + "已关闭"; + } + MyAlertDialog.build(getContext()) + .setTitle(getActivity().getString(R.string.menu_bookcase_syn)) + .setItems(webSynMenu, (dialog, which) -> { + switch (which) { + case 0: + synBookcaseToWeb(false); + break; + case 1: + webRestore(); + break; + case 2: + String tip = ""; + if (mSetting.isAutoSyn()) { + mSetting.setAutoSyn(false); + tip = "每日自动同步已关闭!"; + } else { + mSetting.setAutoSyn(true); + tip = "每日自动同步已开启!"; + } + SysManager.saveSetting(mSetting); + ToastUtils.showSuccess(tip); + break; + } + }) + .setNegativeButton(null, null) + .setPositiveButton(null, null) + .show(); + }); mRlSetting.setOnClickListener(v -> { Intent settingIntent = new Intent(getActivity(), MoreSettingActivity.class); startActivity(settingIntent); @@ -169,6 +252,20 @@ public class MineFragment extends BaseFragment { startActivity(aboutIntent); }); + mRlFeedback.setOnClickListener(v -> { + DialogCreator.createCommonDialog(getContext(), "问题反馈", "请加入QQ群(1085028304)反馈问题!", true, + "加入QQ群", "取消", (dialog, which) -> { + if (!MyApplication.joinQQGroup(getContext(), "8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")) { + ClipboardManager mClipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + //数据 + ClipData mClipData = ClipData.newPlainText("Label", "1085028304"); + //把数据设置到剪切板上 + assert mClipboardManager != null; + mClipboardManager.setPrimaryClip(mClipData); + ToastUtils.showError("未安装手Q或安装的版本不支持!\n已复制QQ群号,您可自行前往QQ添加!"); + } + }, null); + }); } @Override @@ -229,7 +326,7 @@ public class MineFragment extends BaseFragment { mHandler.sendMessage(mHandler.obtainMessage(7)); // DialogCreator.createTipDialog(mMainActivity, // "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!"); - mSetting = SysManager.getSetting(); + SysManager.regetmSetting(); ToastUtils.showSuccess("书架恢复成功!"); } @@ -241,6 +338,112 @@ public class MineFragment extends BaseFragment { }, (dialogInterface, i) -> dialogInterface.dismiss()); } + /** + * 同步书架 + */ + private void synBookcaseToWeb(boolean isAutoSyn) { + if (!NetworkUtils.isNetWorkAvailable()) { + if (!isAutoSyn) { + ToastUtils.showWarring("无网络连接!"); + } + return; + } + ArrayList mBooks = (ArrayList) BookService.getInstance().getAllBooks(); + if (mBooks.size() == 0) { + if (!isAutoSyn) { + ToastUtils.showWarring("当前书架无任何书籍,无法同步!"); + } + return; + } + Date nowTime = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd"); + String nowTimeStr = sdf.format(nowTime); + SharedPreUtils spb = SharedPreUtils.getInstance(); + String synTime = spb.getString(getString(R.string.synTime)); + if (!nowTimeStr.equals(synTime) || !isAutoSyn) { + UserService.webBackup(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + if ((boolean) o) { + spb.putString(getString(R.string.synTime), nowTimeStr); + if (!isAutoSyn) { + DialogCreator.createTipDialog(getContext(), "成功将书架同步至网络!"); + } + } else { + if (!isAutoSyn) { + DialogCreator.createTipDialog(getContext(), "同步失败,请重试!"); + } + } + } + + @Override + public void onError(Exception e) { + if (!isAutoSyn) { + DialogCreator.createTipDialog(getContext(), "同步失败,请重试!\n" + e.getLocalizedMessage()); + } + } + }); + } + } + + /** + * 恢复 + */ + private void webRestore() { + if (!NetworkUtils.isNetWorkAvailable()) { + ToastUtils.showWarring("无网络连接!"); + return; + } + DialogCreator.createCommonDialog(getContext(), "确认同步吗?", "将书架从网络同步至本地会覆盖原有书架!", true, + (dialogInterface, i) -> { + dialogInterface.dismiss(); + MyApplication.getApplication().newThread(() -> { + /*if (UserService.webRestore()) { + mHandler.sendMessage(mHandler.obtainMessage(7)); +// DialogCreator.createTipDialog(mMainActivity, +// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");、 + mSetting = SysManager.getSetting(); + ToastUtils.showSuccess("成功将书架从网络同步至本地!"); + } else { + DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!"); + }*/ + UserService.webRestore(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + if ((boolean) o) { + mHandler.sendMessage(mHandler.obtainMessage(7)); +// DialogCreator.createTipDialog(mMainActivity, +// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");、 + SysManager.regetmSetting(); + ToastUtils.showSuccess("成功将书架从网络同步至本地!"); + } else { + DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!"); + } + } + + @Override + public void onError(Exception e) { + DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!\n" + e.getLocalizedMessage()); + } + }); + }); + }, (dialogInterface, i) -> dialogInterface.dismiss()); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (resultCode == RESULT_OK) { + switch (requestCode) { + case APPCONST.REQUEST_LOGIN: + assert data != null; + isLogin = data.getBooleanExtra("isLogin", false); + if (isLogin) { + mTvUser.setText(UserService.readUsername()); + } + break; + } + } + } public boolean isRecreate() { return unbinder == null; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/popmenu/AutoPageMenu.java b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/AutoPageMenu.java new file mode 100644 index 0000000..12dea15 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/AutoPageMenu.java @@ -0,0 +1,86 @@ +package xyz.fycz.myreader.ui.popmenu; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.entity.Setting; + +public class AutoPageMenu extends FrameLayout { + + @BindView(R.id.tv_auto_scroll_speed) + TextView tvAutoPageSpeed; + @BindView(R.id.sb_auto_scroll_progress) + SeekBar sbAutoPageSpeed; + @BindView(R.id.tv_exit_auto_page) + TextView tvExitAutoPage; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; + + public AutoPageMenu(@NonNull Context context) { + super(context); + init(context); + } + + public AutoPageMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public AutoPageMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.menu_auto_page, this); + ButterKnife.bind(this, view); + } + + public void setListener(Callback callback) { + Setting setting = SysManager.getSetting(); + sbAutoPageSpeed.setProgress(110 - setting.getAutoScrollSpeed()); + tvAutoPageSpeed.setText(String.format("翻页速度:%s %%", 110 - setting.getAutoScrollSpeed())); + sbAutoPageSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + tvAutoPageSpeed.setText(String.format("翻页速度:%s %%", progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + int progress = seekBar.getProgress(); + int speed = 110 - progress; + setting.setAutoScrollSpeed(speed); + SysManager.saveSetting(setting); + callback.onSpeedChange(); + } + }); + tvExitAutoPage.setOnClickListener(v -> callback.onExitClick()); + } + + public void setNavigationBarHeight(int height) { + vwNavigationBar.getLayoutParams().height = height; + } + + public interface Callback{ + void onSpeedChange(); + void onExitClick(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/popmenu/BrightnessEyeMenu.java b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/BrightnessEyeMenu.java new file mode 100644 index 0000000..9350817 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/BrightnessEyeMenu.java @@ -0,0 +1,158 @@ +package xyz.fycz.myreader.ui.popmenu; + +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.util.BrightUtil; +import xyz.fycz.myreader.widget.CircleCheckBox; + +public class BrightnessEyeMenu extends FrameLayout { + @BindView(R.id.ll_follow_sys) + LinearLayout llFollowSys; + @BindView(R.id.cb_follow_sys) + CircleCheckBox cbFollowSys; + @BindView(R.id.sb_brightness_progress) + SeekBar sbBrightness; + @BindView(R.id.ll_protect_eye) + LinearLayout llProtectEye; + @BindView(R.id.cb_protect_eye) + CircleCheckBox cbProtectEye; + @BindView(R.id.sb_protect_eye) + SeekBar sbProtectEye; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; + + private Callback callback; + + private Activity context; + + private Setting setting = SysManager.getSetting(); + + public BrightnessEyeMenu(@NonNull Context context) { + super(context); + init(context); + } + + public BrightnessEyeMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public BrightnessEyeMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.menu_brightness_eye, this); + ButterKnife.bind(this, view); + } + + public void setListener(Activity activity, Callback callback) { + this.context = activity; + this.callback = callback; + initListener(); + } + + public void initWidget() { + sbBrightness.setProgress(setting.getBrightProgress()); + cbFollowSys.setChecked(setting.isBrightFollowSystem()); + + sbProtectEye.setProgress(setting.getBlueFilterPercent() - 10); + cbProtectEye.setChecked(setting.isProtectEye()); + sbProtectEye.setEnabled(cbProtectEye.isChecked()); + } + + private void initListener() { + //设置亮度 + sbBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + BrightUtil.setBrightness((AppCompatActivity) context, progress); + cbFollowSys.setChecked(false); + setting.setBrightProgress(progress); + setting.setBrightFollowSystem(false); + SysManager.saveSetting(setting); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + //亮度跟随系统 + cbFollowSys.setOnClickListener(v -> { + cbFollowSys.setChecked(!cbFollowSys.isChecked()); + if (cbFollowSys.isChecked()) { + BrightUtil.followSystemBright((AppCompatActivity) context); + setting.setBrightFollowSystem(true); + } else { + BrightUtil.setBrightness((AppCompatActivity) context, setting.getBrightProgress()); + setting.setBrightFollowSystem(false); + } + SysManager.saveSetting(setting); + }); + + //设置蓝光过滤 + sbProtectEye.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + setting.setBlueFilterPercent(progress + 10); + SysManager.saveSetting(setting); + callback.upProtectEye(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + //是否开启护眼 + cbProtectEye.setOnClickListener(v -> { + cbProtectEye.setChecked(!cbProtectEye.isChecked()); + sbProtectEye.setEnabled(cbProtectEye.isChecked()); + setting.setProtectEye(cbProtectEye.isChecked()); + SysManager.saveSetting(setting); + callback.onProtectEyeChange(); + }); + } + + public void setNavigationBarHeight(int height) { + vwNavigationBar.getLayoutParams().height = height; + } + + public interface Callback { + void onProtectEyeChange(); + + void upProtectEye(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeComMenu.java b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeComMenu.java new file mode 100644 index 0000000..f3236e2 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeComMenu.java @@ -0,0 +1,414 @@ +package xyz.fycz.myreader.ui.popmenu; + +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.ui.dialog.DialogCreator; + +import static xyz.fycz.myreader.widget.page.PageLoader.DEFAULT_MARGIN_HEIGHT; +import static xyz.fycz.myreader.widget.page.PageLoader.DEFAULT_MARGIN_WIDTH; + +public class CustomizeComMenu extends FrameLayout { + @BindView(R.id.tv_line_spacing) + TextView tvLineSpacing; + @BindView(R.id.iv_line_minus) + ImageView ivLineMinus; + @BindView(R.id.sb_line_progress) + SeekBar sbLineProgress; + @BindView(R.id.iv_line_add) + ImageView ivLineAdd; + @BindView(R.id.tv_para_spacing) + TextView tvParaSpacing; + @BindView(R.id.iv_para_minus) + ImageView ivParaMinus; + @BindView(R.id.sb_para_progress) + SeekBar sbParaProgress; + @BindView(R.id.iv_para_add) + ImageView ivParaAdd; + @BindView(R.id.tv_text_spacing) + TextView tvTextSpacing; + @BindView(R.id.iv_text_minus) + ImageView ivTextMinus; + @BindView(R.id.sb_text_progress) + SeekBar sbTextProgress; + @BindView(R.id.iv_text_add) + ImageView ivTextAdd; + @BindView(R.id.tv_left_spacing) + TextView tvLeftSpacing; + @BindView(R.id.iv_left_minus) + ImageView ivLeftMinus; + @BindView(R.id.sb_left_progress) + SeekBar sbLeftProgress; + @BindView(R.id.iv_left_add) + ImageView ivLeftAdd; + @BindView(R.id.tv_right_spacing) + TextView tvRightSpacing; + @BindView(R.id.iv_right_minus) + ImageView ivRightMinus; + @BindView(R.id.sb_right_progress) + SeekBar sbRightProgress; + @BindView(R.id.iv_right_add) + ImageView ivRightAdd; + @BindView(R.id.tv_top_spacing) + TextView tvTopSpacing; + @BindView(R.id.iv_top_minus) + ImageView ivTopMinus; + @BindView(R.id.sb_top_progress) + SeekBar sbTopProgress; + @BindView(R.id.iv_top_add) + ImageView ivTopAdd; + @BindView(R.id.tv_bottom_spacing) + TextView tvBottomSpacing; + @BindView(R.id.iv_bottom_minus) + ImageView ivBottomMinus; + @BindView(R.id.sb_bottom_progress) + SeekBar sbBottomProgress; + @BindView(R.id.iv_bottom_add) + ImageView ivBottomAdd; + @BindView(R.id.tv_normal_com) + TextView tvNormalCom; + @BindView(R.id.tv_tight_com) + TextView tvTightCom; + @BindView(R.id.tv_reset) + TextView tvReset; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; + + private Callback callback; + + private Setting setting = SysManager.getSetting(); + + public CustomizeComMenu(@NonNull Context context) { + super(context); + init(context); + } + + public CustomizeComMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public CustomizeComMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.menu_customize_com, this); + ButterKnife.bind(this, view); + } + + public void setListener(Callback callback) { + this.callback = callback; + initWidget(); + initListener(); + } + + public void setNavigationBarHeight(int height) { + vwNavigationBar.getLayoutParams().height = height; + } + + /** + * 行间距:-0.3~3.0 + * 段间距:0.0~3.0 + * 字间距:-0.20~0.50 + * padding:0~100 + * + */ + + public void initWidget() { + initLine(); + initPara(); + initText(); + initLeft(); + initRight(); + initTop(); + initBottom(); + initSelect(); + } + + private void initListener() { + ivLineAdd.setOnClickListener(v -> { + float tem = setting.getLineMultiplier() + 0.1f; + if (tem >= -0.3f && tem <= 3.0f) { + setting.setLineMultiplier(tem); + initLine(); + callback.onTextPChange(); + } + }); + ivLineMinus.setOnClickListener(v -> { + float tem = setting.getLineMultiplier() - 0.1f; + if (tem >= -0.3f && tem <= 3.0f) { + setting.setLineMultiplier(tem); + initLine(); + callback.onTextPChange(); + } + }); + sbLineProgress.setOnSeekBarChangeListener(new OnSeekBarChange() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser){ + setting.setLineMultiplier((progress - 3f) / 10f); + initLine(); + callback.onTextPChange(); + } + } + }); + ivParaAdd.setOnClickListener(v -> { + float tem = setting.getParagraphSize() + 0.1f; + if (tem >= 0.0f && tem <= 3.0f) { + setting.setParagraphSize(tem); + initPara(); + callback.onTextPChange(); + } + }); + ivParaMinus.setOnClickListener(v -> { + float tem = setting.getParagraphSize() - 0.1f; + if (tem >= 0.0f && tem <=3.0f) { + setting.setParagraphSize(tem); + initPara(); + callback.onTextPChange(); + } + }); + sbParaProgress.setOnSeekBarChangeListener(new OnSeekBarChange() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser){ + setting.setParagraphSize(progress / 10f); + initPara(); + callback.onTextPChange(); + } + } + }); + ivTextAdd.setOnClickListener(v -> { + float tem = setting.getTextLetterSpacing() + 0.01f; + if (tem >= -0.20f && tem <= 0.50f) { + setting.setTextLetterSpacing(tem); + initText(); + callback.onTextPChange(); + } + }); + ivTextMinus.setOnClickListener(v -> { + float tem = setting.getTextLetterSpacing() - 0.01f; + if (tem >= -0.20f && tem <= 0.50f) { + setting.setTextLetterSpacing(tem); + initText(); + callback.onTextPChange(); + } + }); + sbTextProgress.setOnSeekBarChangeListener(new OnSeekBarChange() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser){ + setting.setTextLetterSpacing((progress - 20f) / 100f); + initText(); + callback.onTextPChange(); + } + } + }); + ivLeftAdd.setOnClickListener(v -> { + int tem = setting.getPaddingLeft() + 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingLeft(tem); + initLeft(); + callback.onMarginChange(); + } + }); + ivLeftMinus.setOnClickListener(v -> { + int tem = setting.getPaddingLeft() - 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingLeft(tem); + initLeft(); + callback.onMarginChange(); + } + }); + ivRightAdd.setOnClickListener(v -> { + int tem = setting.getPaddingRight() + 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingRight(tem); + initRight(); + callback.onMarginChange(); + } + }); + ivRightMinus.setOnClickListener(v -> { + int tem = setting.getPaddingRight() - 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingRight(tem); + initRight(); + callback.onMarginChange(); + } + }); + ivTopAdd.setOnClickListener(v -> { + int tem = setting.getPaddingTop() + 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingTop(tem); + initTop(); + callback.onMarginChange(); + } + }); + ivTopMinus.setOnClickListener(v -> { + int tem = setting.getPaddingTop() - 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingTop(tem); + initTop(); + callback.onMarginChange(); + } + }); + ivBottomAdd.setOnClickListener(v -> { + int tem = setting.getPaddingBottom() + 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingBottom(tem); + initBottom(); + callback.onMarginChange(); + } + }); + ivBottomMinus.setOnClickListener(v -> { + int tem = setting.getPaddingBottom() - 1; + if (tem >= 0 && tem <= 100) { + setting.setPaddingBottom(tem); + initBottom(); + callback.onMarginChange(); + } + }); + sbLeftProgress.setOnSeekBarChangeListener(onPaddingChange); + sbRightProgress.setOnSeekBarChangeListener(onPaddingChange); + sbTopProgress.setOnSeekBarChangeListener(onPaddingChange); + sbBottomProgress.setOnSeekBarChangeListener(onPaddingChange); + + tvNormalCom.setOnClickListener(v -> { + setting.setTightCom(false); + initSelect(); + callback.onRefreshUI(); + }); + tvTightCom.setOnClickListener(v -> { + setting.setTightCom(true); + initSelect(); + callback.onRefreshUI(); + DialogCreator.createTipDialog(getContext(), getContext().getResources().getString(R.string.tight_com_tip)); + }); + tvReset.setOnClickListener(v -> { + setting.setLineMultiplier(1); + setting.setParagraphSize(0.9f); + setting.setTextLetterSpacing(0); + setting.setPaddingLeft(DEFAULT_MARGIN_WIDTH); + setting.setPaddingRight(DEFAULT_MARGIN_WIDTH); + setting.setPaddingTop(0); + setting.setPaddingBottom(0); + setting.setTightCom(false); + callback.onReset(); + initWidget(); + }); + } + + private void initLine(){ + int line = (int) (setting.getLineMultiplier() * 10); + initProgress(tvLineSpacing, sbLineProgress, String.format("行间距(%s)", line), line + 3); + } + + private void initPara(){ + int para = (int) (setting.getParagraphSize() * 10); + initProgress(tvParaSpacing, sbParaProgress, String.format("段间距(%s)", para), para); + } + + private void initText(){ + int text = (int) (setting.getTextLetterSpacing() * 100); + initProgress(tvTextSpacing, sbTextProgress, String.format("字间距(%s)", text), text + 20); + } + + private void initLeft(){ + int left = setting.getPaddingLeft(); + initProgress(tvLeftSpacing, sbLeftProgress, String.format("左边距(%s)", left), left); + } + + private void initRight(){ + int right = setting.getPaddingRight(); + initProgress(tvRightSpacing, sbRightProgress, String.format("右边距(%s)", right), right); + } + + private void initTop(){ + int top = setting.getPaddingTop(); + initProgress(tvTopSpacing, sbTopProgress, String.format("上边距(%s)", top), top); + } + + private void initBottom(){ + int bottom = setting.getPaddingBottom(); + initProgress(tvBottomSpacing, sbBottomProgress, String.format("下边距(%s)", bottom), bottom); + } + + private void initSelect(){ + tvTightCom.setSelected(setting.isTightCom()); + tvNormalCom.setSelected(!setting.isTightCom()); + } + + private void initProgress(TextView tvSpacing, SeekBar sbProgress, String text, int value){ + tvSpacing.setText(text); + if (sbProgress.getMax() >= value) + sbProgress.setProgress(value); + } + + private OnSeekBarChange onPaddingChange = new OnSeekBarChange() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser){ + switch (seekBar.getId()){ + case R.id.sb_left_progress: + setting.setPaddingLeft(progress); + initLeft(); + break; + case R.id.sb_right_progress: + setting.setPaddingRight(progress); + initRight(); + break; + case R.id.sb_top_progress: + setting.setPaddingTop(progress); + initTop(); + break; + case R.id.sb_bottom_progress: + setting.setPaddingBottom(progress); + initBottom(); + break; + } + callback.onMarginChange(); + } + } + }; + + + public abstract class OnSeekBarChange implements SeekBar.OnSeekBarChangeListener{ + + @Override + public abstract void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser); + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + } + + public interface Callback{ + void onTextPChange(); + void onMarginChange(); + void onRefreshUI(); + void onReset(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeLayoutMenu.java b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeLayoutMenu.java new file mode 100644 index 0000000..5063bfc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/CustomizeLayoutMenu.java @@ -0,0 +1,393 @@ +package xyz.fycz.myreader.ui.popmenu; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import com.jaredrummler.android.colorpicker.ColorPickerDialog; +import com.jaredrummler.android.colorpicker.ColorPickerDialogListener; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.ReadStyle; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.ui.dialog.DialogCreator; +import xyz.fycz.myreader.ui.dialog.MyAlertDialog; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.ZipUtils; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.GsonExtensionsKt; +import xyz.fycz.myreader.util.utils.MeUtils; +import xyz.fycz.myreader.widget.CircleCheckBox; +import xyz.fycz.myreader.widget.HorizontalListView; + +public class CustomizeLayoutMenu extends FrameLayout { + @BindView(R.id.iv_bg_color) + ImageView ivBgColor; + @BindView(R.id.iv_font_color) + ImageView ivFontColor; + @BindView(R.id.cb_share_layout) + CircleCheckBox cbShareLayout; + @BindView(R.id.bgImgList) + HorizontalListView bgImgList; + @BindView(R.id.tv_save_layout) + TextView tvSaveLayout; + @BindView(R.id.tv_import_layout) + TextView tvImportLayout; + @BindView(R.id.tv_export_layout) + TextView tvExportLayout; + @BindView(R.id.tv_reset_layout) + TextView tvResetLayout; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; + + private Callback callback; + + private Activity context; + + private Setting setting = SysManager.getSetting(); + private String bgPath; + private BgImgListAdapter bgImgListAdapter; + + public CustomizeLayoutMenu(@NonNull Context context) { + super(context); + init(context); + } + + public CustomizeLayoutMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public CustomizeLayoutMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.menu_cumtomize_layout, this); + ButterKnife.bind(this, view); + } + + public void setListener(Activity activity, Callback callback) { + this.context = activity; + this.callback = callback; + initWidget(); + initListener(); + } + + private void initWidget() { + ivBgColor.setImageDrawable(setting.getBgDrawable(setting.getCurReadStyleIndex(), context, 200, 120)); + ivFontColor.setImageDrawable(new ColorDrawable(setting.getTextColor())); + cbShareLayout.setChecked(setting.isSharedLayout()); + + //背景图列表 + bgImgListAdapter = new BgImgListAdapter(context); + bgImgListAdapter.initList(); + bgImgList.setAdapter(bgImgListAdapter); + } + + public void upColor() { + int curStyleIndex = setting.getCurReadStyleIndex(); + if (!setting.isDayStyle()){ + ToastUtils.showInfo("当前正在编辑夜间模式"); + curStyleIndex = 6; + } + ivFontColor.setImageDrawable(new ColorDrawable(setting.getTextColor())); + ivBgColor.setImageDrawable(setting.getBgDrawable(curStyleIndex, context, 200, 120)); + } + + private void initListener() { + ivBgColor.setOnClickListener(v -> { + ColorPickerDialog.newBuilder() + .setColor(setting.getBgColor()) + .setShowAlphaSlider(false) + .setDialogType(ColorPickerDialog.TYPE_PRESETS) + .setDialogId(APPCONST.SELECT_BG_COLOR) + .show((FragmentActivity) context); + }); + //选择文字颜色 + ivFontColor.setOnClickListener(view -> + ColorPickerDialog.newBuilder() + .setColor(setting.getTextColor()) + .setShowAlphaSlider(false) + .setDialogType(ColorPickerDialog.TYPE_PRESETS) + .setDialogId(APPCONST.SELECT_TEXT_COLOR) + .show((FragmentActivity) context)); + cbShareLayout.setOnClickListener(v -> { + if (!setting.isSharedLayout()) { + DialogCreator.createCommonDialog(context, "提示", + context.getString(R.string.share_layout_tip), + true, (dialog, which) -> { + setting.setSharedLayout(true); + setting.sharedLayout(); + SysManager.saveSetting(setting); + cbShareLayout.setChecked(true); + }, null); + } else { + setting.setSharedLayout(false); + setting.sharedLayout(); + SysManager.saveSetting(setting); + cbShareLayout.setChecked(false); + } + }); + bgImgList.setOnItemClickListener((adapterView, view, i, l) -> { + if (i == 0) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + context.startActivityForResult(intent, APPCONST.REQUEST_SELECT_BG); + } else { + bgPath = bgImgListAdapter.getItemAssetsFile(i - 1); + setAssetsBg(bgPath); + } + }); + tvSaveLayout.setOnClickListener(v -> { + CharSequence[] items = new CharSequence[6]; + for (int i = 0; i < items.length; i++) { + if (i == 5) { + items[i] = "夜间模式"; + break; + } + items[i] = "布局" + (i + 1); + } + MyAlertDialog.build(context) + .setTitle("选择布局保存的位置") + .setItems(items, (dialog, which) -> { + if (which == 5) which = 6; + setting.saveLayout(which); + SysManager.saveSetting(setting); + callback.upStyle(); + ToastUtils.showSuccess("布局保存成功"); + }).show(); + }); + tvImportLayout.setOnClickListener(v -> importLayout()); + tvExportLayout.setOnClickListener(v -> exportLayout()); + tvResetLayout.setOnClickListener(v -> { + DialogCreator.createCommonDialog(context, "重置布局", + "重置布局将会恢复所有布局为默认状态,确定要重置吗?", + true, (dialog, which) -> { + setting.resetLayout(); + SysManager.saveSetting(setting); + callback.upBg(); + callback.upStyle(); + upColor(); + ToastUtils.showSuccess("布局重置成功"); + }, null); + }); + } + + public void setNavigationBarHeight(int height) { + vwNavigationBar.getLayoutParams().height = height; + } + + public void setAssetsBg(String path) { + setting.setBgIsColor(false); + setting.setBgIsAssert(true); + setting.setBgPath(path); + SysManager.saveSetting(setting); + upColor(); + callback.upBg(); + } + + public void setCustomBg(String path) { + setting.setBgIsColor(false); + setting.setBgIsAssert(false); + setting.setBgPath(path); + SysManager.saveSetting(setting); + upColor(); + callback.upBg(); + } + + private void importLayout() { + ToastUtils.showInfo("请选择布局配置压缩文件"); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/zip"); + context.startActivityForResult(intent, APPCONST.REQUEST_IMPORT_LAYOUT); + } + + private void exportLayout() { + CharSequence[] items = new CharSequence[5]; + for (int i = 0; i < items.length; i++) { + items[i] = "布局" + (i + 1); + } + MyAlertDialog.build(context) + .setTitle("选择需要导出的布局") + .setItems(items, (dialog, which) -> { + if (setting.exportLayout(which)){ + DialogCreator.createTipDialog(context, + "布局配置导出成功,导出位置:" + APPCONST.FILE_DIR + "readConfig.zip"); + } + }).show(); + } + + public void zip2Layout(String zipPath) { + try { + ZipUtils.unzipFile(zipPath, APPCONST.TEM_FILE_DIR); + String json = FileUtils.readText(APPCONST.TEM_FILE_DIR + "readConfig.fyl"); + if (json == null) { + ToastUtils.showError("这也许不是一个配置文件,配置导入失败"); + return; + } + ReadStyle readStyle = GsonExtensionsKt.getGSON().fromJson(json, ReadStyle.class); + CharSequence[] items = new CharSequence[5]; + for (int i = 0; i < items.length; i++) { + items[i] = "布局" + (i + 1); + } + MyAlertDialog.build(context) + .setTitle("选择要覆盖的布局") + .setItems(items, (dialog, which) -> { + setting.importLayout(which, readStyle); + if (!readStyle.bgIsColor() && !readStyle.bgIsAssert()){ + FileUtils.copy(APPCONST.TEM_FILE_DIR + "bg.fyl",APPCONST.BG_FILE_DIR + "bg" + which + ".fyl"); + readStyle.setBgPath(APPCONST.BG_FILE_DIR + "bg" + which + ".fyl"); + } + FileUtils.deleteFile(APPCONST.TEM_FILE_DIR + "readConfig.fyl"); + FileUtils.deleteFile(APPCONST.TEM_FILE_DIR + "bg.fyl"); + ToastUtils.showSuccess("配置导入成功"); + callback.upStyle(); + }).show(); + } catch (IOException e) { + e.printStackTrace(); + ToastUtils.showError("配置导入失败" + e.getLocalizedMessage()); + } + } + + public interface Callback { + void upBg(); + void upStyle(); + } + + private static class BgImgListAdapter extends BaseAdapter { + private Context context; + private LayoutInflater mInflater; + private List assetsFiles; + final BitmapFactory.Options options = new BitmapFactory.Options(); + + BgImgListAdapter(Context context) { + this.context = context; + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + options.inJustDecodeBounds = false; + options.inSampleSize = 4; + } + + void initList() { + AssetManager am = context.getAssets(); + String[] path; + try { + path = am.list("bg"); //获取所有,填入目录获取该目录下所有资源 + } catch (IOException e) { + e.printStackTrace(); + return; + } + + assetsFiles = new ArrayList<>(); + Collections.addAll(assetsFiles, path); + } + + @Override + public int getCount() { + return assetsFiles.size() + 1; + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + return position; + } + + String getItemAssetsFile(int position) { + return "bg/" + assetsFiles.get(position); + } + + @SuppressLint("InflateParams") + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = mInflater.inflate(R.layout.item_read_bg, null); + holder.mImage = convertView.findViewById(R.id.iv_bg); + holder.mTitle = convertView.findViewById(R.id.tv_desc); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + if (position == 0) { + holder.mTitle.setText("选择背景"); + holder.mTitle.setTextColor(Color.parseColor("#CBCBCB")); + holder.mImage.setImageBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_image)); + } else { + String path = assetsFiles.get(position - 1); + holder.mTitle.setText(getFileName(path)); + holder.mTitle.setTextColor(Color.parseColor("#909090")); + try { + BitmapDrawable bitmapDrawable = (BitmapDrawable) holder.mImage.getDrawable(); + //如果图片还未回收,先强制回收该图片 + if (bitmapDrawable != null && !bitmapDrawable.getBitmap().isRecycled()) { + bitmapDrawable.getBitmap().recycle(); + } + //该变现实的图片 + Bitmap bmp = MeUtils.getFitAssetsSampleBitmap(context.getAssets(), getItemAssetsFile(position - 1), 256, 256); + holder.mImage.setImageBitmap(bmp); + } catch (Exception e) { + e.printStackTrace(); + holder.mImage.setImageBitmap(null); + } + } + return convertView; + } + + String getFileName(String path) { + int start = path.lastIndexOf("/"); + int end = path.lastIndexOf("."); + if (end < 0) end = path.length(); + return path.substring(start + 1, end); + } + + private static class ViewHolder { + private TextView mTitle; + private ImageView mImage; + } + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/popmenu/ReadSettingMenu.java b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/ReadSettingMenu.java new file mode 100644 index 0000000..a4492e6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/popmenu/ReadSettingMenu.java @@ -0,0 +1,405 @@ +package xyz.fycz.myreader.ui.popmenu; + +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.enums.Language; +import xyz.fycz.myreader.ui.activity.ReadActivity; +import xyz.fycz.myreader.ui.dialog.DialogCreator; +import xyz.fycz.myreader.ui.dialog.MyAlertDialog; +import xyz.fycz.myreader.util.BrightUtil; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.widget.custom.CircleImageView; +import xyz.fycz.myreader.widget.page.PageMode; + +public class ReadSettingMenu extends FrameLayout { + @BindView(R.id.tv_reduce_text_size) + TextView tvReduceTextSize; + @BindView(R.id.tv_text_size) + TextView tvTextSize; + @BindView(R.id.tv_increase_text_size) + TextView tvIncreaseTextSize; + @BindView(R.id.tv_switch_st) + TextView tvSwitchST; + @BindView(R.id.tv_text_font) + TextView tvTextFont; + @BindView(R.id.iv_line_spacing4) + View ivLineSpacing4; + @BindView(R.id.iv_line_spacing3) + View ivLineSpacing3; + @BindView(R.id.iv_line_spacing2) + View ivLineSpacing2; + @BindView(R.id.tv_line_spacing1) + View tvLineSpacing1; + @BindView(R.id.tv_line_spacing0) + View tvLineSpacing0; + @BindView(R.id.tv_intent) + TextView tvIntent; + @BindView(R.id.iv_common_style) + CircleImageView ivCommonStyle; + @BindView(R.id.iv_leather_style) + CircleImageView ivLeatherStyle; + @BindView(R.id.iv_protect_eye_style) + CircleImageView ivProtectEyeStyle; + @BindView(R.id.iv_breen_style) + CircleImageView ivBreenStyle; + @BindView(R.id.iv_blue_deep_style) + CircleImageView ivBlueStyle; + @BindView(R.id.iv_custom_style) + ImageView ivCustomStyle; + @BindView(R.id.read_tv_auto_page) + TextView tvAutoPage; + @BindView(R.id.read_tv_page_mode) + TextView tvPageMode; + @BindView(R.id.read_tv_hv_screen) + TextView tvHVScreen; + @BindView(R.id.read_tv_more_setting) + TextView tvMoreSetting; + @BindView(R.id.vwNavigationBar) + View vwNavigationBar; + + private View vLastLineSpacing = null; + + private Callback callback; + + private Activity context; + + private Setting setting = SysManager.getSetting(); + + + public ReadSettingMenu(@NonNull Context context) { + super(context); + init(context); + } + + public ReadSettingMenu(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ReadSettingMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.menu_read_setting, this); + ButterKnife.bind(this, view); + } + + public void setListener(Activity activity, Callback callback) { + this.context = activity; + this.callback = callback; + initWidget(); + initListener(); + } + + public void setNavigationBarHeight(int height) { + vwNavigationBar.getLayoutParams().height = height; + } + + private void initWidget() { + tvTextSize.setText(String.valueOf(setting.getReadWordSize())); + initSwitchST(false); + initComposition(); + initStyleImage(); + initStyle(); + initHVScreen(); + } + + private void initListener() { + //减小字体 + tvReduceTextSize.setOnClickListener(v -> { + if (setting.getReadWordSize() > 10) { + tvTextSize.setText(String.valueOf(setting.getReadWordSize() - 1)); + setting.setReadWordSize(setting.getReadWordSize() - 1); + SysManager.saveSetting(setting); + if (callback != null) { + callback.onTextSizeChange(); + } + } + }); + //增大字体 + tvIncreaseTextSize.setOnClickListener(v -> { + if (setting.getReadWordSize() < 60) { + tvTextSize.setText(String.valueOf(setting.getReadWordSize() + 1)); + setting.setReadWordSize(setting.getReadWordSize() + 1); + SysManager.saveSetting(setting); + if (callback != null) { + callback.onTextSizeChange(); + } + } + }); + //繁简切换 + tvSwitchST.setOnClickListener(v -> { + initSwitchST(true); + callback.onRefreshUI(); + }); + //字体选择 + tvTextFont.setOnClickListener(v -> callback.onFontClick()); + //行距单倍 + ivLineSpacing4.setOnClickListener(v -> setLineSpacing(0.6f, 0.4f, 4)); + //行距双倍 + ivLineSpacing3.setOnClickListener(v -> setLineSpacing(1.2f, 1.1f, 3)); + //行距三倍 + ivLineSpacing2.setOnClickListener(v -> setLineSpacing(1.8f, 1.8f, 2)); + //行距默认 + tvLineSpacing1.setOnClickListener(v -> setLineSpacing(1.0f, 0.9f, 1)); + //自定义行距 + tvLineSpacing0.setOnClickListener(v -> ((ReadActivity) context).showCustomizeMenu()); + //缩进 + tvIntent.setOnClickListener(v -> { + AlertDialog dialog = new AlertDialog.Builder(context, R.style.alertDialogTheme) + .setTitle("缩进") + .setSingleChoiceItems(context.getResources().getStringArray(R.array.indent), + setting.getIntent(), + (dialogInterface, i) -> { + setting.setIntent(i); + SysManager.saveSetting(setting); + callback.onRefreshUI(); + dialogInterface.dismiss(); + }) + .create(); + dialog.show(); + }); + //样式选择 + ivCommonStyle.setOnClickListener(v -> selectedStyle(0)); + ivLeatherStyle.setOnClickListener(v -> selectedStyle(1)); + ivProtectEyeStyle.setOnClickListener(v -> selectedStyle(2)); + ivBreenStyle.setOnClickListener(v -> selectedStyle(3)); + ivBlueStyle.setOnClickListener(v -> selectedStyle(4)); + ivCustomStyle.setOnClickListener(v -> { + setting.saveLayout(5); + if (setting.isDayStyle()) { + selectedStyle(5); + } + ((ReadActivity) context).showCustomizeLayoutMenu(); + }); + //自动翻页 + tvAutoPage.setOnClickListener(v -> callback.onAutoPageClick()); + //翻页模式 + tvPageMode.setOnClickListener(v -> { + //显示翻页模式视图 + int checkedItem; + switch (setting.getPageMode()) { + case COVER: + checkedItem = 0; + break; + case SIMULATION: + checkedItem = 1; + break; + case SLIDE: + checkedItem = 2; + break; + case VERTICAL_COVER: + checkedItem = 3; + break; + case SCROLL: + checkedItem = 4; + break; + case NONE: + checkedItem = 5; + break; + default: + checkedItem = 0; + } + MyAlertDialog.build(context) + .setTitle("翻页模式") + .setSingleChoiceItems(R.array.page_mode, checkedItem, (dialog, which) -> { + switch (which) { + case 0: + setting.setPageMode(PageMode.COVER); + break; + case 1: + setting.setPageMode(PageMode.SIMULATION); + break; + case 2: + setting.setPageMode(PageMode.SLIDE); + break; + case 3: + setting.setPageMode(PageMode.VERTICAL_COVER); + break; + case 4: + setting.setPageMode(PageMode.SCROLL); + break; + case 5: + setting.setPageMode(PageMode.NONE); + break; + } + dialog.dismiss(); + SysManager.saveSetting(setting); + callback.onRefreshUI(); + }).show(); + }); + //横屏竖屏切换 + tvHVScreen.setOnClickListener(v -> { + setting.setHorizontalScreen(!setting.isHorizontalScreen()); + initHVScreen(); + SysManager.saveSetting(setting); + callback.onHVChange(); + }); + //更多设置 + tvMoreSetting.setOnClickListener(v -> callback.onMoreSettingClick()); + } + + private void initSwitchST(boolean isChange) { + switch (setting.getLanguage()){ + case normal: + tvSwitchST.setSelected(false); + tvSwitchST.setText("繁"); + if (isChange){ + setting.setLanguage(Language.traditional); + ToastUtils.showInfo("已设置文本为简转繁"); + initSwitchST(false); + } + break; + case traditional: + tvSwitchST.setSelected(true); + tvSwitchST.setText("繁"); + if (isChange){ + setting.setLanguage(Language.simplified); + DialogCreator.createTipDialog(context, context.getString(R.string.traditional_to_simplified_tip)); + initSwitchST(false); + } + break; + case simplified: + tvSwitchST.setSelected(true); + tvSwitchST.setText("简"); + if (isChange){ + setting.setLanguage(Language.normal); + ToastUtils.showInfo("已取消简转繁/繁转简,当前为原始文本"); + initSwitchST(false); + } + break; + } + if (isChange){ + SysManager.saveSetting(setting); + } + } + + public void initStyleImage() { + ivCommonStyle.setImageDrawable(setting.getBgDrawable(0, context, 50, 50)); + ivLeatherStyle.setImageDrawable(setting.getBgDrawable(1, context, 50, 50)); + ivProtectEyeStyle.setImageDrawable(setting.getBgDrawable(2, context, 50, 50)); + ivBreenStyle.setImageDrawable(setting.getBgDrawable(3, context, 50, 50)); + ivBlueStyle.setImageDrawable(setting.getBgDrawable(4, context, 50, 50)); + } + + public void initStyle() { + if (!setting.isDayStyle()){ + return; + } + ivCommonStyle.setBorderColor(context.getResources().getColor(R.color.read_menu_text)); + ivLeatherStyle.setBorderColor(context.getResources().getColor(R.color.read_menu_text)); + ivProtectEyeStyle.setBorderColor(context.getResources().getColor(R.color.read_menu_text)); + ivBreenStyle.setBorderColor(context.getResources().getColor(R.color.read_menu_text)); + ivBlueStyle.setBorderColor(context.getResources().getColor(R.color.read_menu_text)); + ivCustomStyle.setSelected(false); + switch (setting.getCurReadStyleIndex()) { + case 0: + ivCommonStyle.setBorderColor(context.getResources().getColor(R.color.sys_dialog_setting_word_red)); + break; + case 1: + ivLeatherStyle.setBorderColor(context.getResources().getColor(R.color.sys_dialog_setting_word_red)); + break; + case 2: + ivProtectEyeStyle.setBorderColor(context.getResources().getColor(R.color.sys_dialog_setting_word_red)); + break; + case 3: + ivBreenStyle.setBorderColor(context.getResources().getColor(R.color.sys_dialog_setting_word_red)); + break; + case 4: + ivBlueStyle.setBorderColor(context.getResources().getColor(R.color.sys_dialog_setting_word_red)); + break; + case 5: + ivCustomStyle.setSelected(true); + break; + } + } + + public void initComposition(){ + if (vLastLineSpacing != null) { + vLastLineSpacing.setSelected(false); + } + switch (setting.getComposition()){ + case 0: + tvLineSpacing0.setSelected(true); + vLastLineSpacing = tvLineSpacing0; + break; + case 1: + tvLineSpacing1.setSelected(true); + vLastLineSpacing = tvLineSpacing1; + break; + case 2: + ivLineSpacing2.setSelected(true); + vLastLineSpacing = ivLineSpacing2; + break; + case 3: + ivLineSpacing3.setSelected(true); + vLastLineSpacing = ivLineSpacing3; + break; + case 4: + ivLineSpacing4.setSelected(true); + vLastLineSpacing = ivLineSpacing4; + break; + default: + tvLineSpacing1.setSelected(true); + vLastLineSpacing = tvLineSpacing1; + break; + } + } + + + private void initHVScreen(){ + if (setting.isHorizontalScreen()){ + tvHVScreen.setText("竖屏阅读"); + }else { + tvHVScreen.setText("横屏阅读"); + } + } + + private void setLineSpacing(float lineMultiplier, float paragraphSize, int composition){ + setting.setLineMultiplier(lineMultiplier); + setting.setParagraphSize(paragraphSize); + setting.setComposition(composition); + SysManager.saveSetting(setting); + initComposition(); + callback.onTextSizeChange(); + } + + private void selectedStyle(int readStyleIndex) { + setting.setCurReadStyleIndex(readStyleIndex); + SysManager.saveSetting(setting); + initWidget(); + if (callback != null) { + callback.onStyleChange(); + } + } + + public interface Callback{ + void onRefreshUI(); + void onStyleChange(); + void onTextSizeChange(); + void onFontClick(); + void onAutoPageClick(); + void onHVChange(); + void onMoreSettingClick(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookStorePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookStorePresenter.java index 8b2ddfb..ae8bc89 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookStorePresenter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookStorePresenter.java @@ -102,7 +102,7 @@ public class BookStorePresenter implements BasePresenter { mBookStoreFragment.getRvBookList().setAdapter(mBookStoreBookAdapter); mBookStoreBookAdapter.setOnItemClickListener((view, pos) -> { Book book = bookList.get(pos); - if (!findCrawler.hasImg()) { + if (!findCrawler.needSearch()) { Intent intent = new Intent(mMainActivity, BookDetailedActivity.class); intent.putExtra(APPCONST.BOOK, book); mMainActivity.startActivity(intent); @@ -156,7 +156,7 @@ public class BookStorePresenter implements BasePresenter { mHandler.sendEmptyMessage(5); } });*/ - mBookTypes = findCrawler.getRankTypes(); + mBookTypes = findCrawler.getBookTypes(); curType = mBookTypes.get(0); mHandler.sendMessage(mHandler.obtainMessage(1)); page = 1; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java index fd6a81c..42f7bb6 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java @@ -23,7 +23,11 @@ import android.widget.PopupMenu; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -39,6 +43,7 @@ import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog; import xyz.fycz.myreader.ui.dialog.MyAlertDialog; import xyz.fycz.myreader.greendao.entity.BookGroup; import xyz.fycz.myreader.greendao.service.BookGroupService; +import xyz.fycz.myreader.model.backup.UserService; import xyz.fycz.myreader.ui.activity.*; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; @@ -79,6 +84,7 @@ public class BookcasePresenter implements BasePresenter { private Setting mSetting; private final List errorLoadingBooks = new ArrayList<>(); private int finishLoadBookCount = 0; + // private int notifyId = 11; private ExecutorService es = Executors.newFixedThreadPool(1);//更新/下载线程池 public ExecutorService getEs() { @@ -188,6 +194,10 @@ public class BookcasePresenter implements BasePresenter { getData(); + if (mSetting.isAutoSyn() && UserService.isLogin()) { + synBookcaseToWeb(true); + } + //是否启用下拉刷新(默认启用) if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { mBookcaseFragment.getSrlContent().setEnableRefresh(false); @@ -289,7 +299,6 @@ public class BookcasePresenter implements BasePresenter { //初始化 public void init() { initBook(); - mSetting = SysManager.getSetting(); if (mBooks.size() == 0) { mBookcaseFragment.getGvBook().setVisibility(View.GONE); mBookcaseFragment.getLlNoDataTips().setVisibility(View.VISIBLE); @@ -297,11 +306,11 @@ public class BookcasePresenter implements BasePresenter { if (mBookcaseAdapter == null || isBookcaseStyleChange) { switch (mSetting.getBookcaseStyle()) { case listMode: - mBookcaseAdapter = new BookcaseDetailedAdapter(mBookcaseFragment.getContext(), R.layout.gridview_book_detailed_item, mBooks, false, this, isGroup); + mBookcaseAdapter = new BookcaseDetailedAdapter(mMainActivity, R.layout.gridview_book_detailed_item, mBooks, false, this, isGroup); mBookcaseFragment.getGvBook().setNumColumns(1); break; case threePalaceMode: - mBookcaseAdapter = new BookcaseDragAdapter(mBookcaseFragment.getContext(), R.layout.gridview_book_item, mBooks, false, this, isGroup); + mBookcaseAdapter = new BookcaseDragAdapter(mMainActivity, R.layout.gridview_book_item, mBooks, false, this, isGroup); mBookcaseFragment.getGvBook().setNumColumns(3); break; } @@ -331,6 +340,20 @@ public class BookcasePresenter implements BasePresenter { mBookcaseAdapter.setGroup(isGroup); } mBooks.addAll(mBookService.getGroupBooks(curBookGroupId)); + + if (mSetting.getSortStyle() == 1) { + Collections.sort(mBooks, (o1, o2) -> { + if (o1.getLastReadTime() < o2.getLastReadTime()){ + return 1; + }else if (o1.getLastReadTime() > o2.getLastReadTime()){ + return -1; + } + return 0; + }); + }else if (mSetting.getSortStyle() == 2){ + Collections.sort(mBooks, (o1, o2) -> o2.getName().compareTo(o1.getName())); + } + for (int i = 0; i < mBooks.size(); i++) { int sort = !isGroup ? mBooks.get(i).getSortCode() : mBooks.get(i).getGroupSort(); if (sort != i + 1) { @@ -523,8 +546,9 @@ public class BookcasePresenter implements BasePresenter { if (mBooks.size() > 0) { mBookcaseFragment.getSrlContent().setEnableRefresh(false); mBookcaseAdapter.setmEditState(true); - mBookcaseFragment.getGvBook().setDragModel(DragSortGridView.DRAG_BY_LONG_CLICK); - + if (mSetting.getSortStyle() == 0) { + mBookcaseFragment.getGvBook().setDragModel(DragSortGridView.DRAG_BY_LONG_CLICK); + } mBookcaseFragment.getRlBookEdit().setVisibility(View.VISIBLE); setBtnClickable(false); changeCheckedAllStatus(); @@ -945,6 +969,35 @@ public class BookcasePresenter implements BasePresenter { } return; } + Date nowTime = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd"); + String nowTimeStr = sdf.format(nowTime); + SharedPreUtils spb = SharedPreUtils.getInstance(); + String synTime = spb.getString(mMainActivity.getString(R.string.synTime)); + if (!nowTimeStr.equals(synTime) || !isAutoSyn) { + UserService.webBackup(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + if ((boolean) o){ + spb.putString(mMainActivity.getString(R.string.synTime), nowTimeStr); + if (!isAutoSyn) { + DialogCreator.createTipDialog(mMainActivity, "成功将书架同步至网络!"); + } + }else { + if (!isAutoSyn) { + DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!"); + } + } + } + + @Override + public void onError(Exception e) { + if (!isAutoSyn) { + DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!\n" + e.getLocalizedMessage()); + } + } + }); + } } /*****************************************用于返回按钮判断*************************************/ diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java index 912078e..bb039cb 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java @@ -6,6 +6,7 @@ import android.view.View; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.util.llog.LLog; import xyz.fycz.myreader.webapi.callback.ResultCallback; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.ui.activity.CatalogActivity; @@ -26,12 +27,12 @@ import java.util.Collections; * @date 2020/7/22 9:14 */ public class CatalogPresenter implements BasePresenter { + private static final String TAG = CatalogPresenter.class.getSimpleName(); private CatalogFragment mCatalogFragment; private ChapterService mChapterService; private ArrayList mChapters = new ArrayList<>(); private ArrayList mConvertChapters = new ArrayList<>(); private int curSortflag = 0; //0正序 1倒序 - private boolean isDayStyle; private ChapterTitleAdapter mChapterTitleAdapter; private Book mBook; @@ -96,6 +97,8 @@ public class CatalogPresenter implements BasePresenter { } else { position = chapter.getNumber(); } + /*LLog.i(TAG, "position = " + position); + LLog.i(TAG, "mChapters.size() = " + mChapters.size());*/ Intent intent = new Intent(); intent.putExtra(APPCONST.CHAPTER_PAGE, new int[]{position, 0}); mCatalogFragment.getActivity().setResult(Activity.RESULT_OK, intent); diff --git a/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java b/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java index 573d1d3..c7bb393 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java +++ b/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java @@ -16,9 +16,7 @@ public class DateHelper { * 2147483647 */ public static long getLongDate(){ - long res = System.currentTimeMillis(); - return res; - + return System.currentTimeMillis(); } /** diff --git a/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java deleted file mode 100644 index 52b93e9..0000000 --- a/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java +++ /dev/null @@ -1,734 +0,0 @@ -package xyz.fycz.myreader.util; - -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * 文件操作工具包 - * @author lxp - */ -public class FileUtils { - /** - * 写文本文件 在Android系统中,文件保存在 /data/data/PACKAGE_NAME/files/ 目录下 - * - * @param context - */ - public static void write(Context context, String fileName, String content) { - if (content == null) - content = ""; - - try { - FileOutputStream fos = context.openFileOutput(fileName, - Context.MODE_PRIVATE); - fos.write(content.getBytes()); - - fos.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - /** - * 读取文本文件 - * - * @param context - * @param fileName - * @return - */ - public static String read(Context context, String fileName) { - try { - FileInputStream in = context.openFileInput(fileName); - return readInStream(in); - } catch (Exception e) { - e.printStackTrace(); - } - return ""; - } - - private static String readInStream(FileInputStream inStream) { - try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[512]; - int length = -1; - while ((length = inStream.read(buffer)) != -1) { - outStream.write(buffer, 0, length); - } - - outStream.close(); - inStream.close(); - return outStream.toString(); - } catch (IOException e) { - Log.i("FileTest", e.getMessage()); - } - return null; - } - - public static File createFile(String folderPath, String fileName) { - File destDir = new File(folderPath); - if (!destDir.exists()) { - destDir.mkdirs(); - } - return new File(folderPath, fileName + fileName); - } - - /** - * 向手机写图片 - * - * @param buffer - * @param folder - * @param fileName - * @return - */ - public static boolean writeFile(byte[] buffer, String folder, - String fileName) { - boolean writeSucc = false; - - File fileDir = new File(folder); - if (!fileDir.exists()) { - fileDir.mkdirs(); - } - - File file = new File(folder,fileName); - FileOutputStream out = null; - try { - out = new FileOutputStream(file); - out.write(buffer); - writeSucc = true; - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return writeSucc; - } - - public static boolean writeFile(byte[] buffer, File file) { - boolean writeSucc = false; - FileOutputStream out = null; - try { - out = new FileOutputStream(file); - out.write(buffer); - writeSucc = true; - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - return writeSucc; - } - - /** - * 根据文件绝对路径获取文件名 - * - * @param filePath - * @return - */ - public static String getFileName(String filePath) { - if (StringHelper.isEmpty(filePath)) - return ""; - //return filePath.substring(filePath.lastIndexOf("?") + 1); - return filePath.substring(filePath.lastIndexOf("/") + 1); - } - - /** - * 根据文件的绝对路径获取文件名但不包含扩展名 - * - * @param filePath - * @return - */ - public static String getFileNameNoFormat(String filePath) { - if (StringHelper.isEmpty(filePath)) { - return ""; - } - int point = filePath.lastIndexOf('.'); - return filePath.substring(filePath.lastIndexOf(File.separator) + 1, - point); - } - - /** - * 获取文件扩展名 - * - * @param fileName - * @return - */ - public static String getFileFormat(String fileName) { - if (StringHelper.isEmpty(fileName)) - return ""; - - int point = fileName.lastIndexOf('.'); - return fileName.substring(point + 1); - } - - /** - * 获取文件大小 - * - * @param filePath - * @return - */ - public static long getFileSize(String filePath) { - long size = 0; - - File file = new File(filePath); - if (file != null && file.exists()) { - size = file.length(); - } - return size; - } - - /** - * 获取文件大小 - * - * @param size - * 字节 - * @return - */ - public static String getFileSize(long size) { - if (size <= 0) - return "0"; - java.text.DecimalFormat df = new java.text.DecimalFormat("##.##"); - float temp = (float) size / 1024; - if (temp >= 1024) { - return df.format(temp / 1024) + "M"; - } else { - return df.format(temp) + "K"; - } - } - - /** - * 转换文件大小 - * - * @param fileS - * @return B/KB/MB/GB - */ - public static String formatFileSize(long fileS) { - java.text.DecimalFormat df = new java.text.DecimalFormat("#.00"); - String fileSizeString = ""; - if (fileS < 1024) { - fileSizeString = df.format((double) fileS) + "B"; - } else if (fileS < 1048576) { - fileSizeString = df.format((double) fileS / 1024) + "KB"; - } else if (fileS < 1073741824) { - fileSizeString = df.format((double) fileS / 1048576) + "MB"; - } else { - fileSizeString = df.format((double) fileS / 1073741824) + "G"; - } - return fileSizeString; - } - - /** - * 获取目录文件大小 - * - * @param dir - * @return - */ - public static long getDirSize(File dir) { - if (dir == null) { - return 0; - } - if (!dir.isDirectory()) { - return 0; - } - long dirSize = 0; - File[] files = dir.listFiles(); - for (File file : files) { - if (file.isFile()) { - dirSize += file.length(); - } else if (file.isDirectory()) { - dirSize += file.length(); - dirSize += getDirSize(file); // 递归调用继续统计 - } - } - return dirSize; - } - - /** - * 获取目录文件个数 - * - * @return - */ - public long getFileList(File dir) { - long count = 0; - File[] files = dir.listFiles(); - count = files.length; - for (File file : files) { - if (file.isDirectory()) { - count = count + getFileList(file);// 递归 - count--; - } - } - return count; - } - - public static byte[] toBytes(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int ch; - while ((ch = in.read()) != -1) { - out.write(ch); - } - byte buffer[] = out.toByteArray(); - out.close(); - return buffer; - } - - /** - * 检查文件是否存在 - * - * @param name - * @return - */ - public static boolean checkFileExists(String name) { - boolean status; - if (!name.equals("")) { - File path = Environment.getExternalStorageDirectory(); - File newPath = new File(path.toString() + name); - status = newPath.exists(); - } else { - status = false; - } - return status; - } - /** - * 返回项目的files目录 - * @return - */ - public static String getPath(){ - File path = Environment.getExternalStorageDirectory(); - return path.toString(); - } - - /** - * 检查路径是否存在 - * - * @param path - * @return - */ - public static boolean checkFilePathExists(String path) { - return new File(path).exists(); - } - - /** - * 计算SD卡的剩余空间 - * - * @return 返回-1,说明没有安装sd卡 - */ - public static long getFreeDiskSpace() { - String status = Environment.getExternalStorageState(); - long freeSpace = 0; - if (status.equals(Environment.MEDIA_MOUNTED)) { - try { - File path = Environment.getExternalStorageDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSize(); - long availableBlocks = stat.getAvailableBlocks(); - freeSpace = availableBlocks * blockSize / 1024; - } catch (Exception e) { - e.printStackTrace(); - } - } else { - return -1; - } - return (freeSpace); - } - - /** - * 新建目录 - * - * @param directoryName - * @return - */ - public static boolean createDirectory(String directoryName) { - boolean status; - if (!directoryName.equals("")) { - File path = Environment.getExternalStorageDirectory(); - File newPath = new File(path.toString() + directoryName); - status = newPath.mkdir(); - status = true; - } else - status = false; - return status; - } - - /** - * 检查是否安装SD卡 - * - * @return - */ - public static boolean checkSaveLocationExists() { - String sDCardStatus = Environment.getExternalStorageState(); - boolean status; - status = sDCardStatus.equals(Environment.MEDIA_MOUNTED); - return status; - } - - /** - * 删除目录(包括:目录里的所有文件) - * - * @param fileName - * @return - */ - public static boolean deleteDirectory(String fileName) { - boolean status; - SecurityManager checker = new SecurityManager(); - - if (!fileName.equals("")) { - - File path = Environment.getExternalStorageDirectory(); - File newPath = new File(path.toString() + fileName); - checker.checkDelete(newPath.toString()); - if (newPath.isDirectory()) { - String[] listfile = newPath.list(); - // delete all files within the specified directory and then - // delete the directory - try { - for (int i = 0; i < listfile.length; i++) { - File deletedFile = new File(newPath.toString() + "/" - + listfile[i].toString()); - deletedFile.delete(); - } - newPath.delete(); - Log.i("DirManager delDirectory", fileName); - status = true; - } catch (Exception e) { - e.printStackTrace(); - status = false; - } - - } else - status = false; - } else - status = false; - return status; - } - - /** - * 删除文件 - * - * @param fileName - * @return - */ - public static boolean deleteFile(String fileName) { - boolean status; - SecurityManager checker = new SecurityManager(); - - if (!fileName.equals("")) { - - File path = Environment.getExternalStorageDirectory(); - File newPath = new File(path.toString() + fileName); - checker.checkDelete(newPath.toString()); - if (newPath.isFile()) { - try { - Log.d("DirManager delFile", fileName); - newPath.delete(); - status = true; - } catch (SecurityException se) { - se.printStackTrace(); - status = false; - } - } else - status = false; - } else - status = false; - return status; - } - - /** - * 删除空目录 - * - * 返回 0代表成功 ,1 代表没有删除权限, 2代表不是空目录,3 代表未知错误 - * - * @return - */ - public static int deleteBlankPath(String path) { - File f = new File(path); - if (!f.canWrite()) { - return 1; - } - if (f.list() != null && f.list().length > 0) { - return 2; - } - if (f.delete()) { - return 0; - } - return 3; - } - - /** - * 重命名 - * - * @param oldName - * @param newName - * @return - */ - public static boolean reNamePath(String oldName, String newName) { - File f = new File(oldName); - return f.renameTo(new File(newName)); - } - - /** - * 删除文件 - * - * @param filePath - */ - public static boolean deleteFileWithPath(String filePath) { - SecurityManager checker = new SecurityManager(); - File f = new File(filePath); - checker.checkDelete(filePath); - if (f.isFile()) { - Log.i("DirManager delFile", filePath); - f.delete(); - return true; - } - return false; - } - - /** - * 获取SD卡的根目录,末尾带\ - * - * @return - */ - public static String getSDRoot() { - return Environment.getExternalStorageDirectory().getAbsolutePath() - + File.separator; - } - - /** - * 列出root目录下所有子目录 - * - - * @return 绝对路径 - */ - public static List listPath(String root) { - List allDir = new ArrayList(); - SecurityManager checker = new SecurityManager(); - File path = new File(root); - checker.checkRead(root); - if (path.isDirectory()) { - for (File f : path.listFiles()) { - if (f.isDirectory()) { - allDir.add(f.getAbsolutePath()); - } - } - } - return allDir; - } - - public enum PathStatus { - SUCCESS, EXITS, ERROR - } - - /** - * 创建目录 - * - */ - public static PathStatus createPath(String newPath) { - File path = new File(newPath); - if (path.exists()) { - return PathStatus.EXITS; - } - if (path.mkdir()) { - return PathStatus.SUCCESS; - } else { - return PathStatus.ERROR; - } - } - - /** - * 截取路径名 - * - * @return - */ - public static String getPathName(String absolutePath) { - int start = absolutePath.lastIndexOf(File.separator) + 1; - int end = absolutePath.length(); - return absolutePath.substring(start, end); - } - - - public static String getFilePathFromContentUri(Context context, Uri uri) { - String photoPath = ""; - if(context == null || uri == null) { - return photoPath; - } - - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { - String docId = DocumentsContract.getDocumentId(uri); - if(isExternalStorageDocument(uri)) { - String[] split = docId.split(":"); - if(split.length >= 2) { - String type = split[0]; - if("primary".equalsIgnoreCase(type)) { - photoPath = Environment.getExternalStorageDirectory() + "/" + split[1]; - } - } - } - else if(isDownloadsDocument(uri)) { - Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId)); - photoPath = getDataColumn(context, contentUri, null, null); - } - else if(isMediaDocument(uri)) { - String[] split = docId.split(":"); - if(split.length >= 2) { - String type = split[0]; - Uri contentUris = null; - if("image".equals(type)) { - contentUris = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } - else if("video".equals(type)) { - contentUris = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } - else if("audio".equals(type)) { - contentUris = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - String selection = MediaStore.Images.Media._ID + "=?"; - String[] selectionArgs = new String[] { split[1] }; - photoPath = getDataColumn(context, contentUris, selection, selectionArgs); - } - } - } - else if("file".equalsIgnoreCase(uri.getScheme())) { - photoPath = uri.getPath(); - } - else { - photoPath = getDataColumn(context, uri, null, null); - } - - return photoPath; - } - - private static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - private static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - private static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { - Cursor cursor = null; - String column = MediaStore.Images.Media.DATA; - String[] projection = { column }; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - int index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(index); - } - } finally { - if (cursor != null && !cursor.isClosed()) - cursor.close(); - } - return null; - } - - public static byte[] File2byte(String filePath) - { - byte[] buffer = null; - try - { - File file = new File(filePath); - FileInputStream fis = new FileInputStream(file); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] b = new byte[1024]; - int n; - while ((n = fis.read(b)) != -1) - { - bos.write(b, 0, n); - } - fis.close(); - bos.close(); - buffer = bos.toByteArray(); - } - catch (FileNotFoundException e) - { - e.printStackTrace(); - } - catch (IOException e) - { - e.printStackTrace(); - } - return buffer; - } - - public static void byte2File(byte[] buf, String filePath, String fileName) - { - BufferedOutputStream bos = null; - FileOutputStream fos = null; - File file = null; - try - { - File dir = new File(filePath); - if (!dir.exists() && dir.isDirectory()) - { - dir.mkdirs(); - } - file = new File(filePath + File.separator + fileName); - fos = new FileOutputStream(file); - bos = new BufferedOutputStream(fos); - bos.write(buf); - } - catch (Exception e) - { - e.printStackTrace(); - } - finally - { - if (bos != null) - { - try - { - bos.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - if (fos != null) - { - try - { - fos.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java index 57c9f98..bf9e46e 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java +++ b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java @@ -305,9 +305,11 @@ public class HttpUtil { }) .build();*/ Request.Builder requestBuilder = new Request.Builder() - .addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") - .addHeader("accept-language", "zh-CN,zh;q=0.9") - .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36"); + .addHeader("Accept", "*/*") + .addHeader("Connection", "Keep-Alive") + //.addHeader("Charsert", "utf-8") + .addHeader("Cache-Control", "no-cache") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36"); if (address.contains("qidian.com")) { SharedPreUtils spu = SharedPreUtils.getInstance(); String cookie = spu.getString(MyApplication.getmContext().getString(R.string.qdCookie), ""); diff --git a/app/src/main/java/xyz/fycz/myreader/util/help/ChapterContentHelp.java b/app/src/main/java/xyz/fycz/myreader/util/help/ChapterContentHelp.java new file mode 100644 index 0000000..6bd6501 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/help/ChapterContentHelp.java @@ -0,0 +1,63 @@ +package xyz.fycz.myreader.util.help; + +import android.text.TextUtils; + +import com.luhuiguo.chinese.ChineseUtils; + +import xyz.fycz.myreader.entity.ReadBookControl; +import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean; +import xyz.fycz.myreader.model.ReplaceRuleManager; + + +public class ChapterContentHelp { + private static ChapterContentHelp instance; + + public static synchronized ChapterContentHelp getInstance() { + if (instance == null) + instance = new ChapterContentHelp(); + return instance; + } + + /** + * 转繁体 + */ + private String toTraditional(String content) { + int convertCTS = ReadBookControl.getInstance().getTextConvert(); + switch (convertCTS) { + case 0: + break; + case 1: + content = ChineseUtils.toSimplified(content); + break; + case 2: + content = ChineseUtils.toTraditional(content); + break; + } + return content; + } + + /** + * 替换净化 + */ + public String replaceContent(String bookName, String bookTag, String content, Boolean replaceEnable) { + if (!replaceEnable) return toTraditional(content); + if (ReplaceRuleManager.getEnabled().size() == 0) return toTraditional(content); + //替换 + for (ReplaceRuleBean replaceRule : ReplaceRuleManager.getEnabled()) { + if (isUseTo(replaceRule.getUseTo(), bookTag, bookName)) { + try { + content = content.replaceAll(replaceRule.getFixedRegex(), replaceRule.getReplacement()); + } catch (Exception ignored) { + } + } + } + return toTraditional(content); + } + + private boolean isUseTo(String useTo, String bookTag, String bookName) { + return TextUtils.isEmpty(useTo) + || useTo.contains(bookTag) + || useTo.contains(bookName); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LExecutor.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LExecutor.java new file mode 100644 index 0000000..3888936 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LExecutor.java @@ -0,0 +1,149 @@ +package xyz.fycz.myreader.util.llog; +import android.os.Environment; + +import java.io.File; +import java.io.FileOutputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.BlockingQueue; + +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; +import static android.os.Process.setThreadPriority; + +/** + * @author luocan + * @version 1.0 + *

+ * Created on 15/12/25. + */ +public class LExecutor extends Thread { + + private final BlockingQueue mQueue; + + private volatile boolean mQuit = false; + + private static DateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + private static DateFormat mTimeFormat = new SimpleDateFormat("MM-dd HH:mm:ss"); + + public LExecutor(BlockingQueue mQueue) { + this.mQueue = mQueue; + } + + @Override + public void run() { + setThreadPriority(THREAD_PRIORITY_BACKGROUND); + + while (true) { + LMsg logMsg; + try { + + logMsg = mQueue.take(); + + } catch (InterruptedException e) { + + if (mQuit) { + return; + } + continue; + } + + String date = mDateFormat.format(Calendar.getInstance(Locale.CHINA).getTime()); + + String fileName = date + "log.html"; + + StringBuilder builder = new StringBuilder(); + + switch (logMsg.getPriority()) { + case LMsg.INFO: + builder.append(""); + break; + case LMsg.DEBUG: + builder.append(""); + break; + case LMsg.ERROR: + builder.append(""); + break; + case LMsg.WARNING: + builder.append(""); + break; + } + + Calendar calendar = Calendar.getInstance(); + builder.append("[ " + mTimeFormat.format(calendar.getTime()) + " ] "); + builder.append("[ " + logMsg.getTag() + " ] :"); + builder.append(logMsg.getMsg()); + builder.append("
"); + + try { + File file = getLogFile(fileName); + if (file == null) { + return; + } + FileOutputStream outputStream = new FileOutputStream(file, true); + outputStream.write(builder.toString().getBytes()); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + /** + * Forces this executor to quit immediately. If any requests are still in + * the queue, they are not guaranteed to be processed. + */ + public void quit() { + mQuit = true; + interrupt(); + } + + + /** + * check sd + * + * @return + */ + private static boolean checkSDCard() { + if (Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED)) { + return true; + } + + return false; + } + + /** + * generate the log file + * + * @param fileName + * @return + */ + private static File getLogFile(String fileName) { + + if (!checkSDCard()) { + return null; + } + + File storageRoot = Environment.getExternalStorageDirectory(); + + String path = LLog.getRootPath() + File.separator + "log"; //日志文件目录 + + File tmpFile = new File(storageRoot.getAbsolutePath().concat(File.separator).concat(path)); + + if (!tmpFile.exists()) { + + boolean result = tmpFile.mkdirs(); + + if (result) { + return new File(tmpFile, fileName); + } + + return null; + } + + return new File(tmpFile, fileName); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java new file mode 100644 index 0000000..435ed3c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LLog.java @@ -0,0 +1,133 @@ +package xyz.fycz.myreader.util.llog; + +/** + * @author luocan + * @version 1.0 + *

+ * Created on 15/12/25. + */ +public class LLog { + + private static boolean isDebuggable = true; + private static LQueue mLogQueue; + private static String mRootPath; + + + /** + * @param rootPath the log file path + */ + public static void init(String rootPath) { + mLogQueue = new LQueue(); + mLogQueue.start(); + mRootPath = rootPath; + } + + public static String getRootPath() { + return mRootPath; + } + + + /** + * open or close the function + * + * @param isDebuggable if set to false, not print the message. + */ + public static void setIsDebuggable(boolean isDebuggable) { + LLog.isDebuggable = isDebuggable; + } + + /** + * error log + * + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static void e(String tag, String msg) { + if (!isDebuggable) { + return; + } + + if (mLogQueue == null) { + throw new NullPointerException("Must call init method first!"); + } + + LMsg logMsg = new LMsg(); + logMsg.setTag(tag); + logMsg.setMsg(msg); + logMsg.setPriority(LMsg.ERROR); + mLogQueue.add(logMsg); + } + + + /** + * warning log + * + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static void w(String tag, String msg) { + if (!isDebuggable) { + return; + } + + if (mLogQueue == null) { + throw new NullPointerException("Must call init method first!"); + } + + LMsg logMsg = new LMsg(); + logMsg.setTag(tag); + logMsg.setMsg(msg); + logMsg.setPriority(LMsg.WARNING); + mLogQueue.add(logMsg); + } + + + /** + * info log + * + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static void i(String tag, String msg) { + if (!isDebuggable) { + return; + } + + if (mLogQueue == null) { + throw new NullPointerException("Must call init method first!"); + } + + LMsg logMsg = new LMsg(); + logMsg.setTag(tag); + logMsg.setMsg(msg); + logMsg.setPriority(LMsg.INFO); + mLogQueue.add(logMsg); + } + + + /** + * debug log + * + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static void d(String tag, String msg) { + if (!isDebuggable) { + return; + } + + if (mLogQueue == null) { + throw new NullPointerException("Must call init method first!"); + } + + LMsg logMsg = new LMsg(); + logMsg.setTag(tag); + logMsg.setMsg(msg); + logMsg.setPriority(LMsg.DEBUG); + mLogQueue.add(logMsg); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java new file mode 100644 index 0000000..583d1c5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LMsg.java @@ -0,0 +1,44 @@ +package xyz.fycz.myreader.util.llog; + +/** + * @author luocan + * @version 1.0 + *

+ * Created on 15/12/25. + */ +public class LMsg { + public static final int INFO = 0x01; + public static final int DEBUG = 0x02; + public static final int ERROR = 0x03; + public static final int WARNING = 0x04; + + protected String mTag; + protected String mMsg; + protected int mPriority; + + + public String getTag() { + return mTag; + } + + public void setTag(String tag) { + this.mTag = tag; + } + + public String getMsg() { + return mMsg; + } + + public void setMsg(String msg) { + this.mMsg = msg; + } + + public int getPriority() { + return mPriority; + } + + public void setPriority(int priority) { + this.mPriority = priority; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java b/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java new file mode 100644 index 0000000..5325b56 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/llog/LQueue.java @@ -0,0 +1,46 @@ +package xyz.fycz.myreader.util.llog; + +import java.util.concurrent.LinkedBlockingQueue; + + +/** + * @author luocan + * @version 1.0 + *

+ * Created on 15/12/25. + */ +public class LQueue { + + private final LinkedBlockingQueue mLogQueue = new LinkedBlockingQueue(); + private LExecutor logExecutor; + + + public void add(LMsg logMsg) { + try { + mLogQueue.add(logMsg); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public void start() { + stop(); + try { + logExecutor = new LExecutor(mLogQueue); + logExecutor.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stop() { + try { + if (logExecutor != null) { + logExecutor.quit(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java b/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java new file mode 100644 index 0000000..af55e8e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/BitmapUtil.java @@ -0,0 +1,394 @@ +package xyz.fycz.myreader.util.utils; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.net.Uri; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.util.Log; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import xyz.fycz.myreader.application.MyApplication; + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class BitmapUtil { + private static final String TAG = "BitmapUtil"; + + public static Bitmap getFitSampleBitmap(String file_path, int width, int height) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file_path, options); + options.inSampleSize = getFitInSampleSize(width, height, options); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(file_path, options); + } + + public static int getFitInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) { + int inSampleSize = 1; + if (options.outWidth > reqWidth || options.outHeight > reqHeight) { + int widthRatio = Math.round((float) options.outWidth / (float) reqWidth); + int heightRatio = Math.round((float) options.outHeight / (float) reqHeight); + inSampleSize = Math.min(widthRatio, heightRatio); + } + return inSampleSize; + } + + /** + * 通过资源id转化成Bitmap + */ + public static Bitmap ReadBitmapById(Context context, int resId) { + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inPreferredConfig = Config.ARGB_8888; + opt.inPurgeable = true; + opt.inInputShareable = true; + InputStream is = context.getResources().openRawResource(resId); + return BitmapFactory.decodeStream(is, null, opt); + } + + /** + * 缩放Bitmap满屏 + */ + public static Bitmap getBitmap(Bitmap bitmap, int screenWidth, + int screenHeight) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + Matrix matrix = new Matrix(); + float scale = (float) screenWidth / w; + float scale2 = (float) screenHeight / h; + // scale = scale < scale2 ? scale : scale2; + matrix.postScale(scale, scale); + Bitmap bmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true); + if (!bitmap.equals(bmp) && !bitmap.isRecycled()) { + bitmap.recycle(); + bitmap = null; + } + return bmp;// Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true); + } + + /** + * 按最大边按一定大小缩放图片 + */ + public static Bitmap scaleImage(byte[] buffer, float size) { + // 获取原图宽度 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inPurgeable = true; + options.inInputShareable = true; + Bitmap bm; + // 计算缩放比例 + float reSize = options.outWidth / size; + if (options.outWidth < options.outHeight) { + reSize = options.outHeight / size; + } + // 如果是小图则放大 + if (reSize <= 1) { + int newWidth = 0; + int newHeight = 0; + if (options.outWidth > options.outHeight) { + newWidth = (int) size; + newHeight = options.outHeight * (int) size / options.outWidth; + } else { + newHeight = (int) size; + newWidth = options.outWidth * (int) size / options.outHeight; + } + bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length); + bm = scaleImage(bm, newWidth, newHeight); + if (bm == null) { + Log.e(TAG, "convertToThumb, decode fail:" + null); + return null; + } + return bm; + } + // 缩放 + options.inJustDecodeBounds = false; + options.inSampleSize = (int) reSize; + bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + if (bm == null) { + Log.e(TAG, "convertToThumb, decode fail:" + null); + return null; + } + return bm; + } + + /** + * 检查图片是否超过一定值,是则缩小 + */ + public static Bitmap convertToThumb(byte[] buffer, float size) { + // 获取原图宽度 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inPurgeable = true; + options.inInputShareable = true; + Bitmap bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, + options); + // 计算缩放比例 + float reSize = options.outWidth / size; + if (options.outWidth > options.outHeight) { + reSize = options.outHeight / size; + } + if (reSize <= 0) { + reSize = 1; + } + Log.d(TAG, "convertToThumb, reSize:" + reSize); + // 缩放 + options.inJustDecodeBounds = false; + options.inSampleSize = (int) reSize; + if (bm != null && !bm.isRecycled()) { + bm.recycle(); + bm = null; + Log.e(TAG, "convertToThumb, recyle"); + } + bm = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + if (bm == null) { + Log.e(TAG, "convertToThumb, decode fail:" + null); + return null; + } + return bm; + } + + /** + * Bitmap --> byte[] + */ + private static byte[] readBitmap(Bitmap bmp) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bmp.compress(Bitmap.CompressFormat.JPEG, 60, stream); + try { + stream.flush(); + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return stream.toByteArray(); + } + + /** + * Bitmap --> byte[] + */ + public static byte[] readBitmapFromBuffer(byte[] buffer, float size) { + return readBitmap(convertToThumb(buffer, size)); + } + + /** + * 以屏幕宽度为基准,显示图片 + */ + public static Bitmap decodeStream(Context context, Intent data, float size) { + Bitmap image = null; + try { + Uri dataUri = data.getData(); + // 获取原图宽度 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inPurgeable = true; + options.inInputShareable = true; + BitmapFactory.decodeStream(context.getContentResolver() + .openInputStream(dataUri), null, options); + // 计算缩放比例 + float reSize = (int) (options.outWidth / size); + if (reSize <= 0) { + reSize = 1; + } + Log.d(TAG, "old-w:" + options.outWidth + ", llyt-w:" + size + + ", resize:" + reSize); + // 缩放 + options.inJustDecodeBounds = false; + options.inSampleSize = (int) reSize; + image = BitmapFactory.decodeStream(context.getContentResolver() + .openInputStream(dataUri), null, options); + } catch (Exception e) { + e.printStackTrace(); + } + return image; + } + + /** + * 按新的宽高缩放图片 + */ + public static Bitmap scaleImage(Bitmap bm, int newWidth, int newHeight) { + if (bm == null) { + return null; + } + int width = bm.getWidth(); + int height = bm.getHeight(); + float scaleWidth = ((float) newWidth) / width; + float scaleHeight = ((float) newHeight) / height; + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleHeight); + Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, + true); + if (!bm.isRecycled()) { + bm.recycle(); + } + return newbm; + } + + /** + * 设置固定的宽度,高度随之变化,使图片不会变形 + */ + public static Bitmap fitBitmap(Bitmap target, int newWidth) { + int width = target.getWidth(); + int height = target.getHeight(); + Matrix matrix = new Matrix(); + float scaleWidth = ((float) newWidth) / width; + // float scaleHeight = ((float)newHeight) / height; + matrix.postScale(scaleWidth, scaleWidth); + // Bitmap result = Bitmap.createBitmap(target,0,0,width,height, + // matrix,true); + Bitmap bmp = Bitmap.createBitmap(target, 0, 0, width, height, matrix, + true); + if (!target.equals(bmp) && !target.isRecycled()) { + target.recycle(); + } + return bmp;// Bitmap.createBitmap(target, 0, 0, width, height, matrix, + // true); + } + + /** + * 根据指定的宽高平铺图像 + */ + public static Bitmap createRepeater(int width, int heigth, Bitmap src) { + int countWidth = (width + src.getWidth() - 1) / src.getWidth(); + int countHeight = (heigth + src.getHeight() - 1) / src.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(width, heigth, Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + for (int i = 0; i < countHeight; ++i) { + for (int idx = 0; idx < countWidth; ++idx) { + canvas.drawBitmap(src, idx * src.getWidth(), i * src.getHeight(), null); + } + } + return bitmap; + + } + + /** + * 图片的质量压缩方法 + */ + public static Bitmap compressImage(Bitmap image) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.JPEG, 100, stream);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 + int options = 100; + while (stream.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩 + stream.reset();// 重置stream即清空stream + image.compress(Bitmap.CompressFormat.JPEG, options, stream);// 这里压缩options%,把压缩后的数据存放到baos中 + options -= 10;// 每次都减少10 + } + ByteArrayInputStream isBm = new ByteArrayInputStream(stream.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中 + Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片 + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + isBm.close(); + } catch (IOException e) { + e.printStackTrace(); + } + if (!image.isRecycled()) { + image.recycle(); + } + return bitmap; + } + + /** + * 图片按比例大小压缩方法(根据Bitmap图片压缩) + */ + public static Bitmap getImage(Bitmap image) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.JPEG, 100, stream); + if (stream.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出 + stream.reset();// 重置stream即清空stream + image.compress(Bitmap.CompressFormat.JPEG, 50, stream);// 这里压缩50%,把压缩后的数据存放到baos中 + } + ByteArrayInputStream isBm; + BitmapFactory.Options newOpts = new BitmapFactory.Options(); + // 开始读入图片,此时把options.inJustDecodeBounds 设回true了 + newOpts.inJustDecodeBounds = true; + Bitmap bitmap; + newOpts.inJustDecodeBounds = false; + int w = newOpts.outWidth; + int h = newOpts.outHeight; + // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为 + float hh = 800f;// 这里设置高度为800f + float ww = 480f;// 这里设置宽度为480f + // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 + int be = 1;// be=1表示不缩放 + if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放 + be = (int) (newOpts.outWidth / ww); + } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放 + be = (int) (newOpts.outHeight / hh); + } + if (be <= 0) + be = 1; + newOpts.inSampleSize = be;// 设置缩放比例 + // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 + isBm = new ByteArrayInputStream(stream.toByteArray()); + bitmap = BitmapFactory.decodeStream(isBm, null, newOpts); + try { + isBm.close(); + } catch (IOException e) { + e.printStackTrace(); + } + if (!image.isRecycled()) { + image.recycle(); + } + assert bitmap != null; + return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩 + } + + /** + * 通过资源id转化成Bitmap 全屏显示 + */ + public static Bitmap ReadBitmapById(Context context, int drawableId, + int screenWidth, int screenHight) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Config.ARGB_8888; + options.inInputShareable = true; + options.inPurgeable = true; + InputStream stream = context.getResources().openRawResource(drawableId); + Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options); + assert bitmap != null; + return getBitmap(bitmap, screenWidth, screenHight); + } + + /** + * 高斯模糊 + */ + public static Bitmap stackBlur(Bitmap srcBitmap) { + if (srcBitmap == null) return null; + RenderScript rs = RenderScript.create(MyApplication.getApplication()); + Bitmap blurredBitmap = srcBitmap.copy(Config.ARGB_8888, true); + + //分配用于渲染脚本的内存 + Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED); + Allocation output = Allocation.createTyped(rs, input.getType()); + + //加载我们想要使用的特定脚本的实例。 + ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + script.setInput(input); + + //设置模糊半径 + script.setRadius(8); + + //启动 ScriptIntrinsicBlur + script.forEach(output); + + //将输出复制到模糊的位图 + output.copyTo(blurredBitmap); + + return blurredBitmap; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java index 6f7653d..b20995e 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java @@ -16,6 +16,8 @@ import io.reactivex.Single; import io.reactivex.SingleEmitter; import io.reactivex.SingleOnSubscribe; import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.util.IOUtils; import xyz.fycz.myreader.util.StringHelper; import java.io.*; @@ -241,7 +243,27 @@ public class FileUtils { return charsetName; } - + public static byte[] getBytes(File file) { + byte[] buffer = null; + FileInputStream fis = null; + ByteArrayOutputStream bos = null; + try { + fis = new FileInputStream(file); + bos = new ByteArrayOutputStream(1000); + byte[] b = new byte[1000]; + int n; + while ((n = fis.read(b)) != -1) { + bos.write(b, 0, n); + } + bos.flush(); + buffer = bos.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + IOUtils.close(fis, bos); + } + return buffer; + } /** * 写文本文件 在Android系统中,文件保存在 /data/data/PACKAGE_NAME/files/ 目录下 @@ -281,20 +303,41 @@ public class FileUtils { return ""; } + public static String readText(String path){ + BufferedReader br = null; + StringBuilder sb = new StringBuilder(); + try { + br = new BufferedReader(new FileReader(path)); + String tem; + while ((tem = br.readLine()) != null){ + sb.append(tem); + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + }finally { + IOUtils.close(br); + } + } + private static String readInStream(FileInputStream inStream) { + ByteArrayOutputStream outStream = null; try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[512]; int length = -1; while ((length = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, length); } - + outStream.flush(); outStream.close(); inStream.close(); return outStream.toString(); } catch (IOException e) { Log.i("FileTest", e.getMessage()); + }finally { + IOUtils.close(inStream, outStream); } return null; } @@ -322,6 +365,7 @@ public class FileUtils { try { out = new FileOutputStream(file); out.write(buffer); + out.flush(); writeSucc = true; } catch (Exception e) { e.printStackTrace(); @@ -342,6 +386,7 @@ public class FileUtils { try { out = new FileOutputStream(file); out.write(buffer); + out.flush(); writeSucc = true; } catch (Exception e) { e.printStackTrace(); @@ -356,6 +401,12 @@ public class FileUtils { return writeSucc; } + public static boolean copy(String src, String dest){ + byte[] buffer = FileUtils.getBytes(new File(src)); + return buffer != null && FileUtils.writeFile(buffer, + FileUtils.getFile(dest)); + } + /** * 根据文件绝对路径获取文件名 * @@ -649,15 +700,15 @@ public class FileUtils { * 创建目录 * */ - public static xyz.fycz.myreader.util.FileUtils.PathStatus createPath(String newPath) { + public static PathStatus createPath(String newPath) { File path = new File(newPath); if (path.exists()) { - return xyz.fycz.myreader.util.FileUtils.PathStatus.EXITS; + return PathStatus.EXITS; } if (path.mkdir()) { - return xyz.fycz.myreader.util.FileUtils.PathStatus.SUCCESS; + return PathStatus.SUCCESS; } else { - return xyz.fycz.myreader.util.FileUtils.PathStatus.ERROR; + return PathStatus.ERROR; } } @@ -829,4 +880,18 @@ public class FileUtils { } } } + + public static String getFileSuffix(String filePath) { + File file = new File(filePath); + return getFileSuffix(file); + } + + public static String getFileSuffix(File file) { + if (file == null || !file.exists() || file.isDirectory()) { + return ""; + } + String fileName = file.getName(); + int dotIndex = fileName.lastIndexOf("."); + return dotIndex > 0 ? fileName.substring(dotIndex) : ""; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java new file mode 100644 index 0000000..f002775 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonUtils.java @@ -0,0 +1,103 @@ +package xyz.fycz.myreader.util.utils; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings({"unused"}) +public class GsonUtils { + /** + * 将Json数据解析成相应的映射对象 + */ + public static T parseJObject(String jsonData, Class type) { + T result = null; + if (!TextUtils.isEmpty(jsonData)) { + Gson gson = new GsonBuilder().create(); + try { + result = gson.fromJson(jsonData, type); + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * 将Json数组解析成相应的映射对象List + */ + public static List parseJArray(String jsonData, Class type) { + List result = null; + if (!TextUtils.isEmpty(jsonData)) { + Gson gson = new GsonBuilder().create(); + try { + JsonParser parser = new JsonParser(); + JsonArray JArray = parser.parse(jsonData).getAsJsonArray(); + if (JArray != null) { + result = new ArrayList<>(); + for (JsonElement obj : JArray) { + try { + T cse = gson.fromJson(obj, type); + result.add(cse); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * 将对象转换成Json + */ + public static String toJsonWithSerializeNulls(T entity) { + entity.getClass(); + Gson gson = new GsonBuilder().serializeNulls().create(); + String result = ""; + try { + result = gson.toJson(entity); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 将list排除值为null的字段转换成Json数组 + */ + public static String toJsonArrayWithSerializeNulls(List list) { + Gson gson = new GsonBuilder().serializeNulls().create(); + String result = ""; + try { + result = gson.toJson(list); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 将list中将Expose注解的字段转换成Json数组 + */ + public static String toJsonArrayWithExpose(List list) { + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + String result = ""; + try { + result = gson.toJson(list); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java new file mode 100644 index 0000000..532d946 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/MeUtils.java @@ -0,0 +1,93 @@ +package xyz.fycz.myreader.util.utils; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; + +/** + * by yangyxd + * data: 2019.08.29 + */ +public class MeUtils { + + /** 获取 assets 中指定目录中的文件名称列表 */ + public static CharSequence[] getAssetsFileList(AssetManager am, String path) throws IOException { + final String[] fs = am.list(path); + if (fs == null || fs.length == 0) + return null; + final CharSequence[] items = new CharSequence[fs.length]; + for (int i=0; i reqWidth || options.outHeight > reqHeight) { + int widthRatio = Math.round((float) options.outWidth / (float) reqWidth); + int heightRatio = Math.round((float) options.outHeight / (float) reqHeight); + inSampleSize = Math.min(widthRatio, heightRatio); + } + return inSampleSize; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java index 2b30e21..847fd2b 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/OkHttpUtils.java @@ -29,11 +29,11 @@ public class OkHttpUtils { public static String getHtml(String url, RequestBody requestBody, String encodeType) throws IOException { Request.Builder builder = new Request.Builder() - .addHeader("accept", "*/*") - .addHeader("connection", "Keep-Alive") + .addHeader("Accept", "*/*") + .addHeader("Connection", "Keep-Alive") //.addHeader("Charsert", "utf-8") .addHeader("Cache-Control", "no-cache") - .addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36"); + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36"); if (requestBody != null) { builder.post(requestBody); Log.d("HttpPost URl", url); diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java index 14a9a93..de527dc 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java @@ -1,8 +1,9 @@ package xyz.fycz.myreader.util.utils; -import android.content.Context; import android.text.Editable; +import android.text.TextUtils; + import androidx.annotation.StringRes; import xyz.fycz.myreader.application.MyApplication; @@ -141,50 +142,6 @@ public class StringUtils { return new String(c); } - //繁簡轉換 - /*public static String convertCC(String input, Context context) - { - ConversionType currentConversionType = ConversionType.S2TWP; - int convertType = SharedPreUtils.getInstance().getInt(SHARED_READ_CONVERT_TYPE, 0); - - if (input.length() == 0) - return ""; - - switch (convertType) { - case 1: - currentConversionType = ConversionType.TW2SP; - break; - case 2: - currentConversionType = ConversionType.S2HK; - break; - case 3: - currentConversionType = ConversionType.S2T; - break; - case 4: - currentConversionType = ConversionType.S2TW; - break; - case 5: - currentConversionType = ConversionType.S2TWP; - break; - case 6: - currentConversionType = ConversionType.T2HK; - break; - case 7: - currentConversionType = ConversionType.T2S; - break; - case 8: - currentConversionType = ConversionType.T2TW; - break; - case 9: - currentConversionType = ConversionType.TW2S; - break; - case 10: - currentConversionType = ConversionType.HK2S; - break; - } - - return (convertType != 0)?ChineseConverter.convert(input, currentConversionType, context):input; - }*/ public static void isNotChinese(Editable s){ //禁止输入中文 @@ -347,4 +304,39 @@ public class StringUtils { return -1; } + public static String repeat(String str, int n) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < n; i++) { + stringBuilder.append(str); + } + return stringBuilder.toString(); + } + + public static boolean isJsonType(String str) { + boolean result = false; + if (!TextUtils.isEmpty(str)) { + str = str.trim(); + if (str.startsWith("{") && str.endsWith("}")) { + result = true; + } else if (str.startsWith("[") && str.endsWith("]")) { + result = true; + } + } + return result; + } + + public static boolean isContainEachOther(String s1, String s2){ + if (s1 == null || s2 == null) return true; + s1 = s1.trim(); + s2 = s2.trim(); + return s1.contains(s2) || s2.contains(s1); + } + + public static boolean isEqual(String s1, String s2){ + if (s1 == null || s2 == null) return true; + s1 = s1.trim(); + s2 = s2.trim(); + return s1.equals(s2); + } + } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java index be14ff1..771da51 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java @@ -224,7 +224,6 @@ public class BaseApi { if (!jsonModel.isSuccess()) { if (jsonModel.getError() == ErrorCode.no_security) { ToastUtils.showWarring("登录过期,请重新登录"); - SysManager.logout(); } else { if (jsonModel.getError() == 0) { callback.onFinish(jsonModel.getResult(), -1); diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java index af5ed3f..cffecef 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java @@ -1,6 +1,7 @@ package xyz.fycz.myreader.webapi; +import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.webapi.callback.ResultCallback; import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; @@ -21,7 +22,7 @@ public class BookStoreApi extends BaseApi{ getCommonReturnHtmlStringApi(findCrawler.getFindUrl(), null, findCrawler.getCharset(), true, new ResultCallback() { @Override public void onFinish(Object o, int code) { - callback.onFinish(findCrawler.getBookTypeList((String) o),0); + callback.onFinish(findCrawler.getBookTypes((String) o),0); } @Override @@ -38,12 +39,12 @@ public class BookStoreApi extends BaseApi{ * @param findCrawler * @param callback */ - public static void getBookRankList(String url, FindCrawler findCrawler, final ResultCallback callback){ + public static void getBookRankList(BookType bookType, FindCrawler findCrawler, final ResultCallback callback){ - getCommonReturnHtmlStringApi(url, null, findCrawler.getCharset(),true, new ResultCallback() { + getCommonReturnHtmlStringApi(bookType.getUrl(), null, findCrawler.getCharset(),true, new ResultCallback() { @Override public void onFinish(Object o, int code) { - callback.onFinish(findCrawler.getRankBookList((String) o),0); + callback.onFinish(findCrawler.getFindBooks((String) o, bookType),0); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java index 1716f60..d3b1459 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java @@ -9,7 +9,11 @@ import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.ResourceBundle; /** @@ -48,7 +52,7 @@ public class ReadCrawlerUtil { public static HashMap getDisableSources() { SharedPreUtils spu = SharedPreUtils.getInstance(); String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null); - HashMap mSources = new HashMap<>(); + HashMap mSources = new LinkedHashMap<>(); if (searchSource == null) { for (BookSource bookSource : BookSource.values()) { if (bookSource.equals(BookSource.fynovel) || bookSource.equals(BookSource.local)) continue; @@ -69,10 +73,9 @@ public class ReadCrawlerUtil { } } return mSources; - } - public static void resetReaderCrawlers(){ + public static void resetReadCrawlers(){ StringBuilder sb = new StringBuilder(); for (BookSource bookSource : BookSource.values()) { if (bookSource.equals(BookSource.fynovel) || bookSource.equals(BookSource.local)) @@ -84,6 +87,43 @@ public class ReadCrawlerUtil { SharedPreUtils.getInstance().putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString()); } + public synchronized static void addReadCrawler(BookSource... bookSources){ + SharedPreUtils spu = SharedPreUtils.getInstance(); + String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null); + StringBuilder sb = new StringBuilder(searchSource); + for (BookSource bookSource : bookSources){ + sb.append(","); + sb.append(bookSource.toString()); + } + SharedPreUtils.getInstance().putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString()); + } + + public synchronized static void removeReadCrawler(String... bookSources){ + SharedPreUtils spu = SharedPreUtils.getInstance(); + String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null); + if (searchSource == null) { + return; + } + String[] ableSources = searchSource.split(","); + for (int i = 0; i < ableSources.length; i++) { + String ableSource = ableSources[i]; + for (String bookSource : bookSources) { + if (ableSource.equals(bookSource)) { + ableSources[i] = ""; + break; + } + } + } + StringBuilder sb = new StringBuilder(); + for (String ableSource : ableSources){ + if ("".equals(ableSource)) continue; + sb.append(ableSource); + sb.append(","); + } + sb.deleteCharAt(sb.lastIndexOf(",")); + spu.putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString()); + } + public static ReadCrawler getReadCrawler(String bookSource) { ResourceBundle rb = ResourceBundle.getBundle("crawler"); try { diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java index c2881c0..a8e3e1b 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java @@ -16,8 +16,16 @@ public abstract class FindCrawler implements Serializable { public abstract String getCharset(); public abstract String getFindName(); public abstract String getFindUrl(); - public abstract boolean getTypePage(BookType curType, int page); public abstract boolean hasImg(); - public abstract List getBookTypeList(String html); - public abstract List getRankBookList(String html); + public abstract boolean needSearch(); + //动态获取 + public List getBookTypes(String html) { + return null; + } + //静态获取 + public List getBookTypes(){ + return null; + } + public abstract List getFindBooks(String html, BookType bookType); + public abstract boolean getTypePage(BookType curType, int page); } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java index 9f0de47..7df7a12 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/ReadCrawler.java @@ -18,7 +18,7 @@ public interface ReadCrawler { String getSearchCharset(); // 书源搜索关键字的字符编码,和书源的字符编码就行 String getNameSpace(); // 书源主页地址 Boolean isPost(); // 是否以post请求搜索 - String getContentFormHtml(String html); // 获取书籍内容规则 - ArrayList getChaptersFromHtml(String html); // 获取书籍章节列表规则 ConcurrentMultiValueMap getBooksFromSearchHtml(String html); // 搜索书籍规则 + ArrayList getChaptersFromHtml(String html); // 获取书籍章节列表规则 + String getContentFormHtml(String html); // 获取书籍内容规则 } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java index 53263cd..12c64cc 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java @@ -1,6 +1,8 @@ package xyz.fycz.myreader.webapi.crawler.find; import android.content.Context; +import android.util.Log; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -40,6 +42,7 @@ public class QiDianMobileRank extends FindCrawler { private LinkedHashMap sortName = new LinkedHashMap<>(); private boolean isFemale; private boolean isSort; + private int onePageNum = 20; public QiDianMobileRank(boolean isFemale) { this.isFemale = isFemale; @@ -118,7 +121,7 @@ public class QiDianMobileRank extends FindCrawler { } - public List getRankTypes() { + public List getBookTypes() { if (!isSort) { initMaleRankName(); } else { @@ -159,7 +162,7 @@ public class QiDianMobileRank extends FindCrawler { new ResultCallback() { @Override public void onFinish(Object o, int code) { - rc.onFinish(getBooksFromJson((String) o), 1); + rc.onFinish(getBooksFromJson((String) o, bookType), 1); } @Override @@ -169,11 +172,13 @@ public class QiDianMobileRank extends FindCrawler { }); } - private List getBooksFromJson(String json) { + private List getBooksFromJson(String json, BookType bookType) { List books = new ArrayList<>(); try { JSONObject all = new JSONObject(json); JSONObject data = all.getJSONObject("data"); + int total = data.getInt("total"); + bookType.setPageSize(total % onePageNum == 0 ? total / onePageNum : total / onePageNum + 1); JSONArray jsonBooks = data.getJSONArray("records"); for (int i = 0; i < jsonBooks.length(); i++) { JSONObject jsonBook = jsonBooks.getJSONObject(i); @@ -218,7 +223,10 @@ public class QiDianMobileRank extends FindCrawler { @Override public boolean getTypePage(BookType curType, int page) { if (!isSort) { - if (page > 30) { + if (curType.getPageSize() <= 0){ + curType.setPageSize(30); + } + if (page > curType.getPageSize()) { return true; } }else { @@ -237,12 +245,12 @@ public class QiDianMobileRank extends FindCrawler { } @Override - public List getBookTypeList(String html) { - return null; + public boolean needSearch() { + return true; } @Override - public List getRankBookList(String html) { + public List getFindBooks(String html, BookType bookType) { return null; } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java new file mode 100644 index 0000000..42f77e8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java @@ -0,0 +1,78 @@ +package xyz.fycz.myreader.webapi.crawler.find; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.read.XS7ReadCrawler; + +/** + * @author fengyue + * @date 2020/11/28 22:43 + */ +public class XS7Rank extends FindCrawler { + private FindCrawler xs7 = new XS7ReadCrawler(); + public static final String CHARSET = "GBK"; + public static final String FIND_NAME = "排行榜[小说旗]"; + public static final String FIND_URL = "https://www.xs7.la/top/lastupdate/1.html"; + @Override + public String getCharset() { + return xs7.getCharset(); + } + + @Override + public String getFindName() { + return FIND_NAME; + } + + @Override + public String getFindUrl() { + return FIND_URL; + } + + @Override + public boolean hasImg() { + return true; + } + + @Override + public boolean needSearch() { + return false; + } + + @Override + public List getBookTypes(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementsByClass("toplist").first(); + Elements as = div.getElementsByTag("a"); + for (Element a : as){ + BookType bookType = new BookType(); + bookType.setUrl(a.attr("href")); + bookType.setTypeName(a.text()); + bookTypes.add(bookType); + } + return bookTypes; + } + + @Override + public List getFindBooks(String html, BookType bookType) { + return xs7.getFindBooks(html, bookType); + } + + @Override + public boolean getTypePage(BookType curType, int page) { + if (page != 1 && page > curType.getPageSize()){ + return true; + } + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page + ".html"); + return false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java index 1d1998d..daf6df6 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Ben100ReadCrawler.java @@ -1,26 +1,36 @@ package xyz.fycz.myreader.webapi.crawler.read; import android.text.Html; +import android.util.Log; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; + import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.enums.BookSource; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.Chapter; import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; -public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler { +public class Ben100ReadCrawler extends FindCrawler implements ReadCrawler, BookInfoCrawler { public static final String NAME_SPACE = "https://www.100ben.net"; public static final String NOVEL_SEARCH = "https://www.100ben.net/plus/search.php?keyword={key}"; public static final String CHARSET = "UTF-8"; public static final String SEARCH_CHARSET = "utf-8"; + public static final String FIND_NAME = "书城[100本书·实体]"; + private LinkedHashMap mBookTypes = new LinkedHashMap<>(); + @Override public String getSearchLink() { @@ -32,6 +42,26 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler { return CHARSET; } + @Override + public String getFindName() { + return FIND_NAME; + } + + @Override + public String getFindUrl() { + return NAME_SPACE; + } + + @Override + public boolean hasImg() { + return true; + } + + @Override + public boolean needSearch() { + return false; + } + @Override public String getNameSpace() { return NAME_SPACE; @@ -108,26 +138,27 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler { ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); Document doc = Jsoup.parse(html); // try { - Elements divs = doc.getElementsByClass("recommand"); - Element div = divs.get(0); - Elements elementsByTag = div.getElementsByTag("li"); - for (Element element : elementsByTag) { - Book book = new Book(); - String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text(); - book.setName(name); - String author = element.getElementsByClass("author").first().text().replace("作者:", ""); - book.setAuthor(author); - String imgUrl = element.getElementsByTag("img").first().attr("src"); - book.setImgUrl(imgUrl); - String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href"); - book.setChapterUrl(NAME_SPACE + chapterUrl); - String desc = element.getElementsByClass("intro").first().text(); - book.setDesc(desc); - book.setNewestChapterTitle(""); - book.setSource(BookSource.ben100.toString()); - SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); - books.add(sbb, book); - } + Elements divs = doc.getElementsByClass("recommand"); + Element div = divs.get(0); + Elements elementsByTag = div.getElementsByTag("li"); + for (Element element : elementsByTag) { + Book book = new Book(); + String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text(); + book.setName(name); + String author = element.getElementsByClass("author").first().text().replace("作者:", ""); + book.setAuthor(author); + String imgUrl = element.getElementsByTag("img").first().attr("src"); + book.setImgUrl(imgUrl); + String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href"); + book.setChapterUrl(NAME_SPACE + chapterUrl); + String desc = element.getElementsByClass("intro").first().text(); + book.setDesc(desc); + book.setNewestChapterTitle(""); + book.setIsCloseUpdate(true); + book.setSource(BookSource.ben100.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } // } catch (Exception e) { // e.printStackTrace(); // } @@ -136,9 +167,10 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler { /** * 获取书籍详细信息 + * * @param book */ - public Book getBookInfo(String html, Book book){ + public Book getBookInfo(String html, Book book) { Document doc = Jsoup.parse(html); Element img = doc.getElementById("fmimg"); book.setImgUrl(img.getElementsByTag("img").get(0).attr("src")); @@ -148,4 +180,110 @@ public class Ben100ReadCrawler implements ReadCrawler, BookInfoCrawler { book.setType(type.getElementsByTag("a").get(2).text()); return book; } + + + @Override + public List getBookTypes(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element menu = doc.getElementsByClass("menu").first(); + for (Element a : menu.children()) { + String name = a.text(); + String url = a.attr("href"); + if ("首页".equals(name)) { + name = "100本书"; + } else if (name.contains("全部小说")) { + name = "全部小说"; + } + BookType bookType = new BookType(); + bookType.setTypeName(name); + bookType.setUrl(url); + bookTypes.add(bookType); + System.out.println("bookTypes.put(\""+ bookType.getTypeName() + "\", \"" + bookType.getUrl() + "\");"); + } + return bookTypes; + } + + @Override + public List getBookTypes() { + initBookTypes(); + List bookTypes = new ArrayList<>(); + for (String name : mBookTypes.keySet()){ + BookType bookType = new BookType(); + bookType.setTypeName(name); + bookType.setUrl(mBookTypes.get(name)); + bookTypes.add(bookType); + } + return bookTypes; + } + + private void initBookTypes(){ + mBookTypes.put("世界名著", "https://www.100ben.net/shijiemingzhu/"); + mBookTypes.put("现代文学", "https://www.100ben.net/xiandaiwenxue/"); + mBookTypes.put("外国小说", "https://www.100ben.net/waiguoxiaoshuo/"); + mBookTypes.put("励志书籍", "https://www.100ben.net/lizhishuji/"); + mBookTypes.put("古典文学", "https://www.100ben.net/gudianwenxue/"); + mBookTypes.put("武侠小说", "https://www.100ben.net/wuxiaxiaoshuo/"); + mBookTypes.put("言情小说", "https://www.100ben.net/yanqingxiaoshuo/"); + mBookTypes.put("推理小说", "https://www.100ben.net/tuilixiaoshuo/"); + mBookTypes.put("科幻小说", "https://www.100ben.net/kehuanxiaoshuo/"); + mBookTypes.put("人物传记", "https://www.100ben.net/renwuzhuanji/"); + mBookTypes.put("盗墓悬疑", "https://www.100ben.net/daomuxuanyi/"); + mBookTypes.put("玄幻穿越", "https://www.100ben.net/xuanhuanchuanyue/"); + mBookTypes.put("科普书籍", "https://www.100ben.net/kepushuji/"); + mBookTypes.put("100本书", "https://www.100ben.net/"); + mBookTypes.put("全部小说", "https://www.100ben.net/all/"); + } + + @Override + public List getFindBooks(String html, BookType bookType) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + + try { + Element pageDiv = doc.getElementsByClass("page").first(); + String page = pageDiv.getElementsByTag("a").last().text(); + String pageStr = page.replace("末页(", "").replace(")", ""); + bookType.setPageSize(Integer.parseInt(pageStr)); + } catch (Exception ignored) {} + + Elements divs = doc.getElementsByClass("recommand"); + Element div = divs.get(0); + Elements elementsByTag = div.getElementsByTag("li"); + for (Element element : elementsByTag) { + Book book = new Book(); + String name = element.getElementsByClass("titles").first().getElementsByTag("a").first().text(); + book.setName(name); + String author = element.getElementsByClass("author").first().text().replace("作者:", ""); + book.setAuthor(author); + String imgUrl = element.getElementsByTag("img").first().attr("src"); + book.setImgUrl(imgUrl); + String chapterUrl = element.getElementsByClass("titles").first().getElementsByTag("a").first().attr("href"); + book.setChapterUrl(chapterUrl.contains("http") ? chapterUrl : NAME_SPACE + chapterUrl); + String desc = element.getElementsByClass("intro").first().text(); + book.setDesc(desc); + book.setNewestChapterTitle(""); + book.setType(bookType.getTypeName()); + book.setSource(BookSource.ben100.toString()); + books.add(book); + } + return books; + } + + @Override + public boolean getTypePage(BookType curType, int page) { + if (curType.getPageSize() <= 0) { + curType.setPageSize(1); + } + if (page > curType.getPageSize()) { + return true; + } + if (curType.getTypeName().equals("100本书")) return false; + if (curType.getUrl().contains("list")) { + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + ".html"); + } else { + curType.setUrl(curType.getUrl() + "list_" + page + ".html"); + } + return false; + } } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java new file mode 100644 index 0000000..92f2863 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/DSTQReadCrawler.java @@ -0,0 +1,129 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + + +public class DSTQReadCrawler implements ReadCrawler { + public static final String NAME_SPACE = "https://www.dstiejuan.com"; + public static final String NOVEL_SEARCH = "https://www.dstiejuan.com/search.html,searchkey={key}"; + public static final String CHARSET = "UTF-8"; + public static final String SEARCH_CHARSET = "UTF-8"; + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public Boolean isPost() { + return true; + } + + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("content"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " "); + return content; + } else { + return ""; + } + } + + /** + * 从html中获取章节列表 + * + * @param html + * @return + */ + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element divList = doc.getElementsByClass("read").first(); + Elements elementsByTag = divList.getElementsByTag("a"); + int i = 0; + for (int j = 12; j < elementsByTag.size(); j++) { + Element a = elementsByTag.get(j); + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(NAME_SPACE + url); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + try { + Element div = doc.getElementsByClass("library").first(); + Elements lis = div.getElementsByTag("li"); + for (Element li : lis){ + Elements as = li.getElementsByTag("a"); + Book book = new Book(); + book.setName(as.get(1).text()); + book.setAuthor(as.get(2).text()); + book.setType(as.get(3).text()); + book.setNewestChapterTitle(as.get(4).text().replace("最新章节:", "")); + book.setDesc(li.getElementsByClass("intro").first().text()); + book.setImgUrl(li.getElementsByTag("img").attr("src")); + book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace(".html", "/")); + book.setSource(BookSource.dstq.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + } catch (Exception e) { + e.printStackTrace(); + } + return books; + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java new file mode 100644 index 0000000..e7be199 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/Du1DuReadCrawler.java @@ -0,0 +1,146 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.Elements; + +import java.util.ArrayList; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + +/** + * 此小说源书籍详情页可添加推荐书籍,暂未开始做 + */ +public class Du1DuReadCrawler implements ReadCrawler, BookInfoCrawler { + public static final String NAME_SPACE = "http://du1du.org"; + public static final String NOVEL_SEARCH = "http://du1du.org/search.htm?keyword={key}"; + public static final String CHARSET = "GBK"; + public static final String SEARCH_CHARSET = "utf-8"; + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + @Override + public Boolean isPost() { + return false; + } + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + /** + * 从html中获取章节正文 + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("txtContent"); + if (divContent != null) { + StringBuilder sb = new StringBuilder(); + for (TextNode textNode : divContent.textNodes()){ + sb.append(textNode.text()); + sb.append("\n"); + } + String content = sb.toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " "); + return content; + } else { + return ""; + } + } + + /** + * 从html中获取章节列表 + * + * @param html + * @return + */ + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element divList = doc.getElementById("chapters-list"); + Elements elementsByTag = divList.getElementsByTag("a"); + int i = 0; + for (int j = 0; j < elementsByTag.size(); j++) { + Element a = elementsByTag.get(j); + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(NAME_SPACE + url); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByClass("panel-body"); + Element div = divs.get(0); + Elements lis = div.getElementsByClass("clearfix"); + for (int i = 1; i < lis.size(); i++) { + Element li = lis.get(i); + Book book = new Book(); + Elements info = li.getElementsByTag("div"); + book.setName(info.get(1).text()); + book.setInfoUrl(NAME_SPACE + info.get(1).getElementsByTag("a").attr("href")); + book.setChapterUrl(book.getInfoUrl() + "mulu.htm"); + book.setAuthor(info.get(3).text()); + book.setNewestChapterTitle(info.get(2).text()); + book.setType(info.get(0).text() + "小说"); + book.setUpdateDate(info.get(4).text()); + book.setSource(BookSource.du1du.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + return books; + } + + /** + * 获取书籍详细信息 + * @param book + */ + public Book getBookInfo(String html, Book book){ + Document doc = Jsoup.parse(html); + Element img = doc.selectFirst("meta[property=og:image]"); + book.setImgUrl(img.attr("content")); + Element desc = doc.selectFirst("meta[property=og:description]"); + book.setDesc(desc.attr("content")); + Element type = doc.selectFirst("meta[property=og:novel:category]"); + book.setType(type.attr("content")); + return book; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java new file mode 100644 index 0000000..5867b76 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/LiuLangCatReadCrawler.java @@ -0,0 +1,170 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + +/** + * @author fengyue + * @date 2020/11/27 14:09 + */ +public class LiuLangCatReadCrawler implements ReadCrawler { + public static final String NAME_SPACE = "http://m.liulangcat.com"; + public static final String NOVEL_SEARCH = "http://m.liulangcat.com/get/get_search_result.php?page=0&keyword={key}"; +// public static final String NOVEL_SEARCH = "http://www.liulangcat.com/search.php?k={key}&submit=搜索&wgxojg=wc0yz&uwzgzw=yi1p7&amlmvy=mcp50&rybwbm=1s0y7"; + public static final String CHARSET = "utf-8"; + public static final String SEARCH_CHARSET = "utf-8"; + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public Boolean isPost() { + return false; + } + + /* + * +
  • +
    + +
    +

    + 《白夜行》是日本作家东野圭吾的代表作。小说故事围绕着一对有着不同寻常情愫的小学生展开。1973年,大阪的一栋废弃建筑内发现了一具男尸,此后19年,嫌疑人之女雪穗与被害者之子桐原亮司走上截然不同的人生道路,一个跻身上流社会,一个却在底层游走,而他们身边的人,却接二连三地离奇死去,警察经过19年的艰苦追踪,终于使真相大白。

    +
    +
    +
  • + * */ + /*@Override + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementById("booklist"); + Elements lis = div.getElementsByTag("li"); + for (Element li : lis){ + Book book = new Book(); + Element bookName = li.getElementsByClass("bookname").first(); + Element author = li.getElementsByClass("author").first(); + Element desc = li.getElementsByClass("item_bottom").first(); + book.setName(bookName.text()); + book.setChapterUrl(NAME_SPACE + bookName.attr("href")); + book.setAuthor(author.text().replace("[", "").replace("]", "")); + book.setDesc(desc.text()); + book.setNewestChapterTitle(""); + book.setIsCloseUpdate(true); + book.setSource(BookSource.liulangcat.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + return books; + }*/ + + /* + author: "海明威" + authorid: "9102" + bookname: "老人与海" + descrption: "反映了社会经济发展某一阶段的普遍模式,这种模式甚至在现在的发展中国家还可以看到。在二十世纪三四十年代的农业国古巴,传统的渔业文化(和工业化世界隔绝,贴近自然,脱离现代技术,受庞大的家族和紧密联系的村落的约束)开始受到捕鱼产业(依赖工业化的世界,不顾或忽视环境,依靠机械设备获取利润,受庞大的家族和地方村落的约束较少)的冲击。在《老人与海》里,一方面,海明威把圣地亚哥描绘成一个一心一意将捕鱼手艺与自身身份、行为准则和自然法则完美结合在一起的渔民;另一方面,海明威刻画了一些奉行实用主义的年轻渔民,他们把鲨鱼肝卖给美国的肝油产业,用这些利润购买摩托艇及其他机械设备,把捕鱼全然当做改善物质生活的一种手段。" + no: "31072" + pycode: "wgwx" + * */ + + @Override + public ConcurrentMultiValueMap getBooksFromSearchHtml(String json) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + try { + JSONArray array = new JSONArray(json); + for (int i = 0; i < array.length(); i++) { + Book book = new Book(); + JSONObject jsonBook = array.getJSONObject(i); + book.setName(jsonBook.getString("bookname")); + book.setAuthor(jsonBook.getString("author")); + book.setDesc(jsonBook.getString("descrption")); + //http://m.liulangcat.com/wgwx/31072.html + book.setChapterUrl(NAME_SPACE + "/" + jsonBook.getString("pycode") + "/" + + jsonBook.getString("no") + ".html"); + book.setNewestChapterTitle(""); + book.setIsCloseUpdate(true); + //book.setSource(BookSource.liulangcat.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return books; + } + + @Override + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element ul = doc.getElementsByClass("booklist").first(); + Elements as = ul.getElementsByTag("a"); + int i = 0; + for (Element a : as) { + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(NAME_SPACE + url); + chapters.add(chapter); + } + return chapters; + } + + @Override + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementsByClass("item_content").first(); + Element nameSpan = doc.getElementsByClass("top_categoryname").first(); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " "). + replace(nameSpan.text(), ""). + replace("主页", ""). + replace("目录", ""); + return content; + } else { + return ""; + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java new file mode 100644 index 0000000..9e86eb3 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/MiaoBiReadCrawler.java @@ -0,0 +1,217 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + + +public class MiaoBiReadCrawler extends FindCrawler implements ReadCrawler { + public static final String NAME_SPACE = "https://www.imiaobige.com"; + public static final String NOVEL_SEARCH = "https://www.imiaobige.com/search.html,searchkey={key}"; + public static final String CHARSET = "UTF-8"; + public static final String SEARCH_CHARSET = "UTF-8"; + public static final String FIND_NAME = "书城[妙笔阁]"; + private final LinkedHashMap mBookTypes = new LinkedHashMap<>(); + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getFindName() { + return FIND_NAME; + } + + @Override + public String getFindUrl() { + return NAME_SPACE; + } + + @Override + public boolean hasImg() { + return true; + } + + @Override + public boolean needSearch() { + return false; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public Boolean isPost() { + return true; + } + + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("content"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " ").replaceAll("您可以在.*最新章节!|\\\\", ""); + return content; + } else { + return ""; + } + } + + /** + * 从html中获取章节列表 + * + * @param html + * @return + */ + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element divList = doc.getElementById("readerlists"); + Elements elementsByTag = divList.getElementsByTag("a"); + int i = 0; + for (int j = 12; j < elementsByTag.size(); j++) { + Element a = elementsByTag.get(j); + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(NAME_SPACE + url); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); +// try { + Element div = doc.getElementById("sitembox"); + Elements dls = div.getElementsByTag("dl"); + for (Element dl : dls){ + Elements as = dl.getElementsByTag("a"); + Book book = new Book(); + book.setName(as.get(1).text()); + book.setAuthor(as.get(3).text()); + book.setType(as.get(2).text()); + book.setNewestChapterTitle(as.get(4).text()); + book.setDesc(dl.getElementsByClass("book_des").first().text()); + book.setImgUrl(as.first().getElementsByTag("img").attr("src")); + book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace("novel", "read").replace(".html", "/")); + book.setSource(BookSource.miaobi.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } +// } catch (Exception e) { +// e.printStackTrace(); +// } + return books; + } + + @Override + public List getBookTypes() { + initBookTypes(); + List bookTypes = new ArrayList<>(); + for (String name : mBookTypes.keySet()){ + BookType bookType = new BookType(); + bookType.setTypeName(name); + bookType.setUrl(mBookTypes.get(name)); + bookType.setPageSize(100); + bookTypes.add(bookType); + } + return bookTypes; + } + + private void initBookTypes(){ + mBookTypes.put("玄幻奇幻", "https://www.imiaobige.com/xuanhuan/1.html"); + mBookTypes.put("武侠仙侠", "https://www.imiaobige.com/wuxia/1.html"); + mBookTypes.put("都市生活", "https://www.imiaobige.com/dushi/1.html"); + mBookTypes.put("历史军事", "https://www.imiaobige.com/lishi/1.html"); + mBookTypes.put("游戏竞技", "https://www.imiaobige.com/youxi/1.html"); + mBookTypes.put("科幻未来", "https://www.imiaobige.com/kehuan/1.html"); + } + + /* +
    +
    重生之我变成了火星
    +
    20-11-26 16:57

    重生之我变成了火星

    +
    作者:仰望黑夜状态:连载中
    +
    重生成了火星?第一阶段科技进化,第二阶段高魔进化,第三阶段超魔进化!宇宙之间除了人类还有无数可怕存在,让我带领人类征服宇宙,我们的目标是星辰大海~
    +
    最新章节:第三百一十六章 时空道兵
    +
    + */ + @Override + public List getFindBooks(String html, BookType bookType) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementById("sitebox"); + Elements dls = div.getElementsByTag("dl"); + for (Element dl : dls){ + Book book = new Book(); + Elements as = dl.getElementsByTag("a"); + book.setName(as.get(1).text()); + book.setAuthor(as.get(2).text()); + book.setType(bookType.getTypeName()); + book.setNewestChapterTitle(as.get(3).text()); + book.setDesc(dl.getElementsByClass("book_des").first().text()); + book.setImgUrl(as.first().getElementsByTag("img").attr("src")); + book.setChapterUrl(NAME_SPACE + as.get(1).attr("href").replace("novel", "read").replace(".html", "/")); + book.setUpdateDate(dl.getElementsByClass("uptime").first().text()); + book.setSource(BookSource.miaobi.toString()); + books.add(book); + } + return books; + } + + @Override + public boolean getTypePage(BookType curType, int page) { + if (page > curType.getPageSize()){ + return true; + } + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page + ".html"); + return false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java new file mode 100644 index 0000000..ca2f1bb --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/PiaoTianReadCrawler.java @@ -0,0 +1,154 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.Elements; + +import java.util.ArrayList; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + +public class PiaoTianReadCrawler implements ReadCrawler, BookInfoCrawler { + public static final String NAME_SPACE = "https://www.piaotian.org"; + public static final String NOVEL_SEARCH = "https://www.piaotian.org/modules/article/search.php?searchkey={key}&submit=%CB%D1%CB%F7"; + public static final String CHARSET = "GBK"; + public static final String SEARCH_CHARSET = "GBK"; + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + @Override + public Boolean isPost() { + return false; + } + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + /** + * 从html中获取章节正文 + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("htmlContent"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " ").replaceAll("飘天文学.*最新章节!", ""); + return content; + } else { + return ""; + } + } + + /** + * 从html中获取章节列表 + * + * @param html + * @return + */ + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content"); + Element ul = doc.getElementsByClass("chapterlist").get(1); + Elements elementsByTag = ul.getElementsByTag("a"); + int i = 0; + for (int j = 0; j < elementsByTag.size(); j++) { + Element a = elementsByTag.get(j); + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(readUrl + url); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + String urlType = doc.select("meta[property=og:type]").attr("content"); + if ("novel".equals(urlType)) { + String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content"); + Book book = new Book(); + book.setChapterUrl(readUrl); + getBookInfo(html, book); + book.setSource(BookSource.paiotian.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } else { + Elements divs = doc.getElementsByClass("grid"); + Element div = divs.get(0); + Elements trs = div.getElementsByTag("tr"); + for (int i = 1; i < trs.size(); i++) { + Element tr = trs.get(i); + Book book = new Book(); + Elements info = tr.getElementsByTag("td"); + book.setName(info.get(0).text()); + book.setChapterUrl(info.get(0).getElementsByTag("a").attr("href")); + book.setAuthor(info.get(2).text()); + book.setNewestChapterTitle(info.get(1).text()); + book.setUpdateDate(info.get(4).text()); + book.setDesc(""); + book.setSource(BookSource.paiotian.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + } + return books; + } + + /** + * 获取书籍详细信息 + * @param book + */ + public Book getBookInfo(String html, Book book){ + Document doc = Jsoup.parse(html); + Element name = doc.selectFirst("meta[property=og:novel:book_name]"); + book.setName(name.attr("content")); + Element author = doc.selectFirst("meta[property=og:novel:author]"); + book.setAuthor(author.attr("content")); + Element newestChapter = doc.selectFirst("meta[property=og:novel:latest_chapter_name]"); + book.setNewestChapterTitle(newestChapter.attr("content")); + Element img = doc.selectFirst("meta[property=og:image]"); + book.setImgUrl(img.attr("content")); + Element desc = doc.getElementsByClass("bookinfo_intro").first(); + book.setDesc(desc.text()); + Element type = doc.selectFirst("meta[property=og:novel:category]"); + book.setType(type.attr("content")); + return book; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java index 144db3c..cec3359 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/QB5ReadCrawler.java @@ -65,13 +65,19 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo public boolean hasImg() { return false; } + + @Override + public boolean needSearch() { + return false; + } + /** * 获取书城小说分类列表 * * @param html * @return */ - public List getBookTypeList(String html) { + public List getBookTypes(String html) { List bookTypes = new ArrayList<>(); Document doc = Jsoup.parse(html); Elements divs = doc.getElementsByClass("nav_cont"); @@ -126,9 +132,13 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo return books; } - public List getRankBookList(String html) { + public List getFindBooks(String html, BookType bookType) { List books = new ArrayList<>(); Document doc = Jsoup.parse(html); + try { + int pageSize = Integer.parseInt(doc.getElementsByClass("last").first().text()); + bookType.setPageSize(pageSize); + }catch (Exception ignored){} String type = doc.select("meta[name=keywords]").attr("content").replace(",全本小说网", ""); Element div = doc.getElementById("tlist"); Elements uls = div.getElementsByTag("ul"); @@ -267,19 +277,18 @@ public class QB5ReadCrawler extends FindCrawler implements ReadCrawler, BookInfo public boolean getTypePage(BookType curType, int page){ - if (page > 10){ + if (curType.getPageSize() <= 0){ + curType.setPageSize(10); + } + if (page > curType.getPageSize()){ return true; } if (!curType.getTypeName().equals("完本小说")) { curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + "/"); }else { - if (page > 1){ - return true; - } + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page); } return false; } - - } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java index 3f91783..33206c6 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/SoNovelReadCrawler.java @@ -19,8 +19,8 @@ import java.util.List; public class SoNovelReadCrawler implements ReadCrawler, BookInfoCrawler { - public static final String NAME_SPACE = "https://www.soxs.cc"; - public static final String NOVEL_SEARCH = "https://www.soxs.cc/search.html,searchtype=all&searchkey={key}"; + public static final String NAME_SPACE = "https://www.soxscc.com"; + public static final String NOVEL_SEARCH = "https://www.soxscc.com/search.html,searchtype=all&searchkey={key}&action=search&submit= 搜 索 "; public static final String CHARSET = "UTF-8"; public static final String SEARCH_CHARSET = "UTF-8"; @@ -119,7 +119,7 @@ public class SoNovelReadCrawler implements ReadCrawler, BookInfoCrawler { book.setName(info.text()); book.setChapterUrl(NAME_SPACE + info.getElementsByTag("a").attr("href")); book.setAuthor(element.getElementsByClass("s4").first().text()); - book.setType(element.getElementsByTag("span").first().text()); + book.setType(element.getElementsByTag("span").first().text().replace("[", "").replace("]", "")); book.setNewestChapterTitle(element.getElementsByClass("s3").first().text()); book.setSource(BookSource.sonovel.toString()); SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java new file mode 100644 index 0000000..715c869 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/XS7ReadCrawler.java @@ -0,0 +1,221 @@ +package xyz.fycz.myreader.webapi.crawler.read; + +import android.text.Html; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; + + +public class XS7ReadCrawler extends FindCrawler implements ReadCrawler { + public static final String NAME_SPACE = "https://www.xs7.la"; + public static final String NOVEL_SEARCH = "https://www.xs7.la/modules/article/search.php,searchkey={key}"; + public static final String CHARSET = "GBK"; + public static final String SEARCH_CHARSET = "GBK"; + public static final String FIND_NAME = "书城[小说旗]"; + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getFindName() { + return FIND_NAME; + } + + @Override + public String getFindUrl() { + return NAME_SPACE; + } + + @Override + public boolean hasImg() { + return true; + } + + @Override + public boolean needSearch() { + return false; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public Boolean isPost() { + return true; + } + + @Override + public String getSearchCharset() { + return SEARCH_CHARSET; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("content"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " ").replaceAll("一秒记住.*免费阅读!", ""); + return content; + } else { + return ""; + } + } + + /** + * 从html中获取章节列表 + * + * @param html + * @return + */ + public ArrayList getChaptersFromHtml(String html) { + ArrayList chapters = new ArrayList<>(); + Document doc = Jsoup.parse(html); + String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content"); + Element table = doc.getElementsByTag("table").first(); + Elements elementsByTag = table.getElementsByTag("a"); + int i = 0; + for (int j = 0; j < elementsByTag.size(); j++) { + Element a = elementsByTag.get(j); + String title = a.text(); + String url = a.attr("href"); + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + chapter.setUrl(readUrl + url); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); +// try { + Element alist = doc.getElementById("alist"); + Elements divs = alist.select("#alistbox"); + for (Element div : divs){ + Elements as = div.getElementsByTag("a"); + Book book = new Book(); + book.setName(as.get(1).text()); + book.setAuthor(div.getElementsByTag("span").first().text().replace("作者:", "")); + book.setNewestChapterTitle(as.get(2).text()); + book.setDesc(div.getElementsByClass("intro").first().text()); + book.setImgUrl(div.getElementsByTag("img").attr("src")); + book.setChapterUrl(as.get(1).attr("href")); + book.setSource(BookSource.xs7.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } +// } catch (Exception e) { +// e.printStackTrace(); +// } + return books; + } + + @Override + public List getBookTypes(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementsByClass("subnav-hot").first(); + Elements as = div.getElementsByTag("a"); + for (Element a : as){ + BookType bookType = new BookType(); + bookType.setUrl(a.attr("href")); + bookType.setTypeName(a.text()); + bookTypes.add(bookType); + } + return bookTypes; + } + + /* +
    +
    带着农场混异界
    +
    +
    +

    带着农场混异界

    + 作者:明宇 +
    + +
    他横任他横,我自种我田,若要来惹我,过不了明年。 + 宅男赵海带着QQ农场穿越到了异界,附身到了一个落迫的小贵族身上,他的封地是一片种不出东西的黑土地,而最主要的是,...
    + +
    +
    + */ + @Override + public List getFindBooks(String html, BookType bookType) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element last = doc.getElementsByClass("last").first(); + bookType.setPageSize(Integer.parseInt(last.text())); + Element div = doc.getElementById("alist"); + Elements bDivs = div.select("div[id=alistbox]"); + for (Element bDiv : bDivs){ + Book book = new Book(); + Element title = bDiv.getElementsByClass("title").first(); + Element sys = bDiv.getElementsByClass("sys").first(); + Element intro = bDiv.getElementsByClass("intro").first(); + book.setName(title.getElementsByTag("a").first().text()); + book.setAuthor(title.getElementsByTag("span").first().text().replace("作者:", "")); + book.setNewestChapterTitle(sys.getElementsByTag("a").first().text()); + book.setImgUrl(bDiv.getElementsByTag("img").attr("src")); + book.setDesc(intro.text()); + book.setType(bookType.getTypeName()); + book.setChapterUrl(title.getElementsByTag("a").first().attr("href")); + book.setSource(BookSource.xs7.toString()); + books.add(book); + } + return books; + } + + @Override + public boolean getTypePage(BookType curType, int page) { + if (page != 1 && page > curType.getPageSize()){ + return true; + } + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + ".html"); + return false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java b/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java new file mode 100644 index 0000000..ff8dabd --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/CircleCheckBox.java @@ -0,0 +1,480 @@ +package xyz.fycz.myreader.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.View; + +import java.util.Timer; + +import xyz.fycz.myreader.R; + +import static android.graphics.Paint.ANTI_ALIAS_FLAG; + +/** + * Copyright (c) 2016 Arlind Hajredinaj + *

    + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + *

    + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + *

    + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +public class CircleCheckBox extends View { + private float innerCircleRadius = 30.f; + private float outerCircleRadius = innerCircleRadius / 2f; + private float textSize = 35f; + private float borderThickness = 5f; + private float tickThickness = 2f; + private float textLeftPadding = 2f; + + private float increment = 20; + private float total_time = 200; + private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG); + private final Paint mPaintPageStroke = new Paint(ANTI_ALIAS_FLAG); + private final Paint mPaintTick = new Paint(ANTI_ALIAS_FLAG); + private final Paint mPaintOuter = new Paint(ANTI_ALIAS_FLAG); + private final Paint mPaintText = new Paint(ANTI_ALIAS_FLAG); + private boolean firstRun = true; + private boolean timer_running = false; + private float tick_third_ = innerCircleRadius / 3; + private boolean draw_tick_part_one = false; + private String text = ""; + + private int tick_color = Color.argb(255, 255, 255, 255); + private int text_color = Color.argb(255, 0, 0, 0); + private int outerCircleColor = Color.argb(100, 0, 207, 173); + private int innerCircleColor = Color.argb(255, 0, 207, 173); + private int circleBorderColor = Color.argb(255, 0, 207, 173); + + private OnCheckedChangeListener listener; + + private boolean showOuterCircle = true; + + float centerX = 0; + float centerY = 0; + private boolean isChecked = false; + + private boolean draw_tick = false; + + Timer timer = new Timer(); + + public CircleCheckBox(Context context) { + super(context); + init(context, null); + } + + public CircleCheckBox(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public CircleCheckBox(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + public void init(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.CircleCheckbox, + 0, 0); + + try { + setTickColorHex(a.getString(R.styleable.CircleCheckbox_tickColor)); + setTextColorHex(a.getString(R.styleable.CircleCheckbox_textColor)); + setShowOuterCircle(a.getBoolean(R.styleable.CircleCheckbox_showOuterCircle, true)); + setInnerCircleColorHex(a.getString(R.styleable.CircleCheckbox_innerCircleColor)); + setOuterCircleColorHex(a.getString(R.styleable.CircleCheckbox_outerCircleColor)); + setCircleBorderColorHex(a.getString(R.styleable.CircleCheckbox_circleBorderColor)); + setTickThickness(a.getDimension(R.styleable.CircleCheckbox_tickThickness, tickThickness)); + setBorderThickness(a.getDimension(R.styleable.CircleCheckbox_borderThickness, borderThickness)); + setTextLeftPadding(a.getDimension(R.styleable.CircleCheckbox_textLeftPadding, textLeftPadding)); + setTextSize(a.getDimension(R.styleable.CircleCheckbox_textSize, textSize)); + setInnerCircleRadius(a.getDimension(R.styleable.CircleCheckbox_innerCircleRadius, innerCircleRadius)); + setOuterCircleRadius(a.getDimension(R.styleable.CircleCheckbox_outerCircleRadius, outerCircleRadius)); + setText(a.getString(R.styleable.CircleCheckbox_text)); + } finally { + a.recycle(); + } + } + + mPaintOuter.setColor(outerCircleColor); + mPaintPageFill.setColor(innerCircleColor); + mPaintTick.setColor(tick_color); + mPaintTick.setStrokeWidth(tickThickness * 2); + + mPaintPageStroke.setColor(circleBorderColor); + mPaintPageStroke.setStrokeWidth(borderThickness); + mPaintPageStroke.setStyle(Paint.Style.STROKE); + + mPaintText.setTextSize(textSize); + mPaintText.setColor(text_color); + + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setChecked(!isChecked); + } + }); + } + + private float current_radius = 0.0f; + float time = 0; + + float tick_x = 0; + float tick_y = 0; + float tick_x_two = 0; + float tick_y_two = 0; + // Interpolator interpolator = new BounceInterpolator(); + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + centerX = innerCircleRadius + outerCircleRadius + getPaddingLeft(); + + centerY = getHeight() / 2; + + //float interpolation = Math.abs(interpolator.getInterpolation(time)); + + canvas.drawCircle(centerX, centerY, innerCircleRadius, mPaintPageStroke); + + if (isChecked) { + if (draw_tick) { + float tick_offset = tickThickness * 2; + if (showOuterCircle) { + canvas.drawCircle(centerX, centerY, current_radius + outerCircleRadius, mPaintOuter); + } + canvas.drawCircle(centerX, centerY, innerCircleRadius, mPaintPageFill); + if (draw_tick_part_one) { + + canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick); + canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick); + canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick); + } else { + canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick); + canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick); + canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick); + + canvas.drawLine(centerX - tick_offset, tick_y, tick_x_two - tick_offset, tick_y_two, mPaintTick); + canvas.drawCircle(tick_x_two - tick_offset, tick_y_two, tickThickness, mPaintTick); + } + + } else { + if (showOuterCircle && current_radius >= innerCircleRadius - outerCircleRadius) { + canvas.drawCircle(centerX, centerY, (current_radius + outerCircleRadius), mPaintOuter); + } + canvas.drawCircle(centerX, centerY, current_radius, mPaintPageFill); + } + } else { + if (!firstRun) { + canvas.drawCircle(centerX, centerY, current_radius, mPaintPageFill); + } + } + + if (isChecked) { + if (!timer_running) { + tick_x = centerX;// tick_third_; + tick_y = centerY + tick_third_; + float tick_offset = tickThickness * 2; + canvas.drawCircle(centerX - tick_offset - tick_third_, centerY, tickThickness, mPaintTick); + canvas.drawLine(centerX - tick_offset - tick_third_, centerY, tick_x - tick_offset, tick_y, mPaintTick); + canvas.drawCircle(tick_x - tick_offset, tick_y, tickThickness, mPaintTick); + + tick_x_two = tick_x + (tick_third_ * 1.7f); + tick_y_two = tick_y - (tick_third_ * 1.7f); + canvas.drawLine(centerX - tick_offset, tick_y, tick_x_two - tick_offset, tick_y_two, mPaintTick); + canvas.drawCircle(tick_x_two - tick_offset, tick_y_two, tickThickness, mPaintTick); + tick_x = 0; + tick_y = 0; + tick_x_two = 0; + tick_x_two = 0; + } + } + + canvas.drawText(text, centerX + textLeftPadding + innerCircleRadius + outerCircleRadius, centerY + textSize / 2, mPaintText); + firstRun = false; + } + + Handler handler = new Handler(); + + private void startAnimationTimer() { + this.post(new Runnable() { + @Override + public void run() { + runAnimation(); + } + }); + } + + private void runAnimation() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + timer_running = true; + time += increment; + if (time < total_time) { + + float inc = innerCircleRadius / (total_time / increment); + if (isChecked) { + current_radius = current_radius + inc; + } else { + current_radius = current_radius - inc; + } + postInvalidate(); + runAnimation(); + } else { + if (isChecked) { + time = 0; + startTickAnimation(); + } else { + timer_running = false; + } + } + } + }, (long) increment); + } + + private void startTickAnimation() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + draw_tick_part_one = true; + timer_running = true; + draw_tick = true; + + if (time == 0) { + tick_x = centerX - tick_third_; + tick_y = centerY; + } + float inc_tick = tick_third_ / (total_time / increment); + + tick_x += inc_tick; + tick_y += inc_tick; + + time += increment; + if (time <= total_time) { + postInvalidate(); + startTickAnimation(); + } else { + draw_tick_part_one = false; + time = 0; + startTickPartTwoAnimation(); + } + } + }, (int) increment); + } + + private void startTickPartTwoAnimation() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + timer_running = true; + draw_tick = true; + if (time == 0) { + tick_x_two = tick_x; + tick_y_two = tick_y; + } + + float inc_tick = tick_third_ * 1.7f / (total_time / increment); + + tick_x_two += inc_tick; + tick_y_two -= inc_tick; + + time += increment; + if (time <= total_time) { + postInvalidate(); + startTickPartTwoAnimation(); + } else { + timer_running = false; + draw_tick = false; + time = 0; + } + } + }, (long) increment); + } + + public int getTickColor() { + return tick_color; + } + + public void setTickColor(int tick_color) { + this.tick_color = tick_color; + } + + public void setTickColorHex(String tick_color) { + if (tick_color != null) + this.tick_color = Color.parseColor(tick_color); + } + + public int getTextColor() { + return text_color; + } + + public void setTextColor(int text_color) { + this.text_color = text_color; + } + + public void setTextColorHex(String color) { + if (color != null) + this.text_color = Color.parseColor(color); + } + + public boolean isShowOuterCircle() { + return showOuterCircle; + } + + public void setShowOuterCircle(boolean showOuterCircle) { + this.showOuterCircle = showOuterCircle; + } + + public int getInnerCircleColor() { + return innerCircleColor; + } + + public void setInnerCircleColor(int innerCircleColor) { + this.innerCircleColor = innerCircleColor; + } + + public void setInnerCircleColorHex(String innerCircleColor) { + if (innerCircleColor != null) + this.innerCircleColor = Color.parseColor(innerCircleColor); + } + + public int getOuterCircleColor() { + return outerCircleColor; + } + + public void setOuterCircleColor(int outerCircleColor) { + this.outerCircleColor = outerCircleColor; + } + + public void setOuterCircleColorHex(String outerCircleColor) { + if (outerCircleColor != null) + this.outerCircleColor = Color.parseColor(outerCircleColor); + } + + public int getCircleBorderColor() { + return circleBorderColor; + } + + public void setCircleBorderColor(int circleBorderColor) { + this.circleBorderColor = circleBorderColor; + } + + public void setCircleBorderColorHex(String color) { + if (color != null) + this.circleBorderColor = Color.parseColor(color); + } + + public float getTickThickness() { + return tickThickness; + } + + public void setTickThickness(float tickThickness) { + this.tickThickness = tickThickness; + } + + public float getBorderThickness() { + return borderThickness; + } + + public void setBorderThickness(float borderThickness) { + this.borderThickness = borderThickness; + } + + public float getTextLeftPadding() { + return textLeftPadding; + } + + public void setTextLeftPadding(float textLeftPadding) { + this.textLeftPadding = textLeftPadding; + } + + public float getTextSize() { + return textSize; + } + + public void setTextSize(float textSize) { + this.textSize = textSize; + } + + public float getInnerCircleRadius() { + return innerCircleRadius; + } + + public void setInnerCircleRadius(float innerCircleRadius) { + this.innerCircleRadius = innerCircleRadius; + } + + public float getOuterCircleRadius() { + return outerCircleRadius; + } + + public void setOuterCircleRadius(float outerCircleRadius) { + this.outerCircleRadius = outerCircleRadius; + } + + public void setChecked(boolean isChecked) { + if (!timer_running) { + this.isChecked = isChecked; + if (listener != null) + listener.onCheckedChanged(this, isChecked); + if (isChecked) { + tick_x = 0; + tick_y = 0; + tick_x_two = 0; + tick_y_two = 0; + current_radius = 0; + } + time = 0; + startAnimationTimer(); + } + } + + public boolean isChecked(){ + return isChecked; + } + + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + this.listener = listener; + } + + int position; + + public void setPosition(int position) { + this.position = position; + } + + public interface OnCheckedChangeListener { + void onCheckedChanged(CircleCheckBox view, boolean isChecked); + } + + public String getText() { + return text; + } + + public void setText(String text) { + if (text != null) { + this.text = text; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java b/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java new file mode 100644 index 0000000..d458989 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/HorizontalListView.java @@ -0,0 +1,362 @@ +package xyz.fycz.myreader.widget; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.Scroller; + +import java.util.LinkedList; +import java.util.Queue; + +public class HorizontalListView extends AdapterView { + + public boolean mAlwaysOverrideTouch = true; + protected ListAdapter mAdapter; + private int mLeftViewIndex = -1; + private int mRightViewIndex = 0; + protected int mCurrentX; + protected int mNextX; + private int mMaxX = Integer.MAX_VALUE; + private int mDisplayOffset = 0; + protected Scroller mScroller; + private GestureDetector mGesture; + private Queue mRemovedViewQueue = new LinkedList(); + private OnItemSelectedListener mOnItemSelected; + private OnItemClickListener mOnItemClicked; + private OnItemLongClickListener mOnItemLongClicked; + private boolean mDataChanged = false; + + + public HorizontalListView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + private synchronized void initView() { + mLeftViewIndex = -1; + mRightViewIndex = 0; + mDisplayOffset = 0; + mCurrentX = 0; + mNextX = 0; + mMaxX = Integer.MAX_VALUE; + mScroller = new Scroller(getContext()); + mGesture = new GestureDetector(getContext(), mOnGesture); + } + + @Override + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mOnItemSelected = listener; + } + + @Override + public void setOnItemClickListener(OnItemClickListener listener){ + mOnItemClicked = listener; + } + + @Override + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + mOnItemLongClicked = listener; + } + + private DataSetObserver mDataObserver = new DataSetObserver() { + + @Override + public void onChanged() { + synchronized(HorizontalListView.this){ + mDataChanged = true; + } + invalidate(); + requestLayout(); + } + + @Override + public void onInvalidated() { + reset(); + invalidate(); + requestLayout(); + } + + }; + public boolean onInterceptTouchEvent(MotionEvent ev) { + getParent().requestDisallowInterceptTouchEvent(true); + return mGesture.onTouchEvent(ev); + }; + @Override + public ListAdapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + return null; + } + + @Override + public void setAdapter(ListAdapter adapter) { + if(mAdapter != null) { + mAdapter.unregisterDataSetObserver(mDataObserver); + } + mAdapter = adapter; + mAdapter.registerDataSetObserver(mDataObserver); + reset(); + } + + private synchronized void reset(){ + initView(); + removeAllViewsInLayout(); + requestLayout(); + } + + @Override + public void setSelection(int position) { + } + + private void addAndMeasureChild(final View child, int viewPos) { + LayoutParams params = child.getLayoutParams(); + if(params == null) { + params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + } + + addViewInLayout(child, viewPos, params, true); + child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + } + + + + @Override + protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if(mAdapter == null){ + return; + } + + if(mDataChanged){ + int oldCurrentX = mCurrentX; + initView(); + removeAllViewsInLayout(); + mNextX = oldCurrentX; + mDataChanged = false; + } + + if(mScroller.computeScrollOffset()){ + mNextX = mScroller.getCurrX(); + } + + if(mNextX <= 0){ + mNextX = 0; + mScroller.forceFinished(true); + } + if(mNextX >= mMaxX) { + mNextX = mMaxX; + mScroller.forceFinished(true); + } + + int dx = mCurrentX - mNextX; + + removeNonVisibleItems(dx); + fillList(dx); + positionItems(dx); + + mCurrentX = mNextX; + + if(!mScroller.isFinished()){ + post(new Runnable(){ + @Override + public void run() { + requestLayout(); + } + }); + + } + } + + private void fillList(final int dx) { + int edge = 0; + View child = getChildAt(getChildCount()-1); + if(child != null) { + edge = child.getRight(); + } + fillListRight(edge, dx); + + edge = 0; + child = getChildAt(0); + if(child != null) { + edge = child.getLeft(); + } + fillListLeft(edge, dx); + + + } + + private void fillListRight(int rightEdge, final int dx) { + while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { + + View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, -1); + rightEdge += child.getMeasuredWidth(); + + if(mRightViewIndex == mAdapter.getCount()-1) { + mMaxX = mCurrentX + rightEdge - getWidth(); + } + + if (mMaxX < 0) { + mMaxX = 0; + } + mRightViewIndex++; + } + + } + + private void fillListLeft(int leftEdge, final int dx) { + while(leftEdge + dx > 0 && mLeftViewIndex >= 0) { + View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, 0); + leftEdge -= child.getMeasuredWidth(); + mLeftViewIndex--; + mDisplayOffset -= child.getMeasuredWidth(); + } + } + + private void removeNonVisibleItems(final int dx) { + View child = getChildAt(0); + while(child != null && child.getRight() + dx <= 0) { + mDisplayOffset += child.getMeasuredWidth(); + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mLeftViewIndex++; + child = getChildAt(0); + + } + + child = getChildAt(getChildCount()-1); + while(child != null && child.getLeft() + dx >= getWidth()) { + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mRightViewIndex--; + child = getChildAt(getChildCount()-1); + } + } + + private void positionItems(final int dx) { + if(getChildCount() > 0){ + mDisplayOffset += dx; + int left = mDisplayOffset; + for(int i=0;i mViewHeight){ + dis = mViewHeight; + } + //计算bitmap截取的区域 + mSrcRect.top = mViewHeight - dis; + //计算bitmap在canvas显示的区域 + mDestRect.bottom = dis; + canvas.drawBitmap(mNextBitmap,0,0,null); + canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null); + addShadow(dis,canvas); + break; + default: + mSrcRect.top = (int) (mViewHeight - mTouchY); + mDestRect.bottom = (int) mTouchY; + canvas.drawBitmap(mCurBitmap,0,0,null); + canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null); + addShadow((int) mTouchY,canvas); + break; + } + } + + //添加阴影 + public void addShadow(int top, Canvas canvas) { + mBackShadowDrawableLR.setBounds(0, top, mScreenWidth , top + 30); + mBackShadowDrawableLR.draw(canvas); + } + + @Override + public void startAnim() { + super.startAnim(); + int dy = 0; + switch (mDirection){ + case NEXT: + if (isCancel){ + int dis = (int) ((mViewHeight - mStartY) + mTouchY); + if (dis > mViewHeight){ + dis = mViewHeight; + } + dy = mViewHeight - dis; + }else{ + dy = (int) -(mTouchY + (mViewHeight - mStartY)); + } + break; + default: + if (isCancel){ + dy = (int) -mTouchY; + }else{ + dy = (int) (mViewHeight - mTouchY); + } + break; + } + + //滑动速度保持一致 + int duration = (animationSpeed * Math.abs(dy)) / mViewHeight; + mScroller.startScroll(0, (int) mTouchY, 0, dy, duration); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java index 83b9c8c..31687e3 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java @@ -6,6 +6,8 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import xyz.fycz.myreader.application.SysManager; + /** * Created by newbiechen on 17-7-24. * 横向动画的模板 @@ -31,12 +33,12 @@ public abstract class HorizonPageAnim extends PageAnimation{ private boolean noNext = false; public HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) { - this(w, h, 0, 0, view, listener); + this(w, h, 0, 0,0, view, listener); } - public HorizonPageAnim(int w, int h, int marginWidth, int marginHeight, + public HorizonPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) { - super(w, h, marginWidth, marginHeight, view,listener); + super(w, h, marginWidth, marginTop, marginBottom, view,listener); //创建图片 mCurBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); mNextBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); @@ -143,11 +145,7 @@ public abstract class HorizonPageAnim extends PageAnimation{ break; case MotionEvent.ACTION_UP: if (!isMove){ - if (x < mScreenWidth / 2){ - isNext = false; - }else{ - isNext = true; - } + isNext = x > mScreenWidth / 2 || SysManager.getSetting().isAlwaysNext(); if (isNext) { //判断是否下一页存在 diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java index 742a410..760c622 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java @@ -13,6 +13,8 @@ import android.widget.Scroller; */ public abstract class PageAnimation { + //动画速度 + static final int animationSpeed = 300; //正在使用的View protected View mView; //滑动装置 @@ -44,18 +46,18 @@ public abstract class PageAnimation { protected float mLastY; public PageAnimation(int w, int h, View view, OnPageChangeListener listener){ - this(w, h, 0, 0, view,listener); + this(w, h, 0, 0, 0, view,listener); } - public PageAnimation(int w, int h, int marginWidth, int marginHeight, View view, OnPageChangeListener listener){ + public PageAnimation(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener){ mScreenWidth = w; mScreenHeight = h; mMarginWidth = marginWidth; - mMarginHeight = marginHeight; + mMarginHeight = marginTop; - mViewWidth = mScreenWidth - mMarginWidth * 2; - mViewHeight = mScreenHeight - mMarginHeight * 2; + mViewWidth = mScreenWidth - marginWidth * 2; + mViewHeight = mScreenHeight - marginTop - marginBottom; mView = view; mListener = listener; @@ -138,6 +140,14 @@ public abstract class PageAnimation { */ public abstract Bitmap getNextBitmap(); + public float getmTouchX() { + return mTouchX; + } + + public float getmTouchY() { + return mTouchY; + } + public enum Direction { NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false); diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java index 3914acb..19439da 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java @@ -3,14 +3,18 @@ package xyz.fycz.myreader.widget.animation; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; +import android.view.ViewConfiguration; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; +import xyz.fycz.myreader.application.SysManager; + /** * Created by newbiechen on 17-7-23. * 原理:仿照ListView源码实现的上下滑动效果 @@ -28,7 +32,8 @@ public class ScrollPageAnim extends PageAnimation { // 滑动追踪的时间 private static final int VELOCITY_DURATION = 1000; private VelocityTracker mVelocity; - + //是否移动了 + private boolean isMove = false; // 整个Bitmap的背景显示 private Bitmap mBgBitmap; @@ -43,9 +48,9 @@ public class ScrollPageAnim extends PageAnimation { // 是否处于刷新阶段 private boolean isRefresh = true; - public ScrollPageAnim(int w, int h, int marginWidth, int marginHeight, + public ScrollPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) { - super(w, h, marginWidth, marginHeight, view, listener); + super(w, h, marginWidth, marginTop, marginBottom, view, listener); // 创建两个BitmapView initWidget(); } @@ -304,12 +309,17 @@ public class ScrollPageAnim extends PageAnimation { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isRunning = false; + isMove = false; // 设置起始点 setStartPoint(x, y); // 停止动画 abortAnim(); break; case MotionEvent.ACTION_MOVE: + final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + if (!isMove) { + isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop; + } mVelocity.computeCurrentVelocity(VELOCITY_DURATION); isRunning = true; // 进行刷新 @@ -317,11 +327,23 @@ public class ScrollPageAnim extends PageAnimation { break; case MotionEvent.ACTION_UP: isRunning = false; - // 开启动画 - startAnim(); + if (!isMove) { + //是否翻阅下一页。true表示翻到下一页,false表示上一页。 + boolean isNext = x > mScreenWidth / 2 || SysManager.getSetting().isAlwaysNext(); + if (isNext) { + startAnim(Direction.NEXT); + } else { + startAnim(Direction.PRE); + } + } else { + // 开启动画 + startAnim(); + } // 删除检测器 - mVelocity.recycle(); - mVelocity = null; + if (mVelocity != null) { + mVelocity.recycle(); + mVelocity = null; + } break; case MotionEvent.ACTION_CANCEL: @@ -369,6 +391,23 @@ public class ScrollPageAnim extends PageAnimation { , 0, 0, Integer.MAX_VALUE * -1, Integer.MAX_VALUE); } + /** + * 翻页动画 + */ + public void startAnim(Direction direction) { + setStartPoint(0, 0); + setTouchPoint(0, 0); + switch (direction) { + case NEXT: + isRunning = true; + mScroller.startScroll(0, 0, 0, -mViewHeight + mMarginHeight * 2, animationSpeed); + break; + case PRE: + isRunning = true; + mScroller.startScroll(0, 0, 0, mViewHeight - mMarginHeight * 2, animationSpeed); + break; + } + } @Override public void scrollAnim() { diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java index 2b85402..86a5f27 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java @@ -142,7 +142,7 @@ public class SimulationPageAnim extends HorizonPageAnim { dy = (int) (1 - mTouchY); // 防止mTouchY最终变为0 } } - mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, 400); + mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, animationSpeed); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java index c8ceba9..cfb30d7 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java @@ -95,7 +95,7 @@ public class SlidePageAnim extends HorizonPageAnim { break; } //滑动速度保持一致 - int duration = (400 * Math.abs(dx)) / mScreenWidth; + int duration = (animationSpeed * Math.abs(dx)) / mScreenWidth; mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); } } diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java new file mode 100644 index 0000000..97cb2c7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/VerticalPageAnim.java @@ -0,0 +1,228 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import xyz.fycz.myreader.application.SysManager; + +/** + * Created by fengyue on 20-11-22. + * 竖向移动的模板 + */ + +public abstract class VerticalPageAnim extends PageAnimation{ + private static final String TAG = "HorizonPageAnim"; + + protected Bitmap mCurBitmap; + protected Bitmap mNextBitmap; + //是否取消翻页 + protected boolean isCancel = false; + + //可以使用 mLast代替 + private int mMoveX = 0; + private int mMoveY = 0; + //是否移动了 + private boolean isMove = false; + //是否翻阅下一页。true表示翻到下一页,false表示上一页。 + private boolean isNext = false; + + //是否没下一页或者上一页 + private boolean noNext = false; + + public VerticalPageAnim(int w, int h, View view, OnPageChangeListener listener) { + this(w, h, 0, 0,0, view, listener); + } + + public VerticalPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom, + View view, OnPageChangeListener listener) { + super(w, h, marginWidth, marginTop, marginBottom, view,listener); + //创建图片 + mCurBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); + mNextBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); + } + + /** + * 转换页面,在显示下一章的时候,必须首先调用此方法 + */ + public void changePage(){ + Bitmap bitmap = mCurBitmap; + mCurBitmap = mNextBitmap; + mNextBitmap = bitmap; + } + + public abstract void drawStatic(Canvas canvas); + + public abstract void drawMove(Canvas canvas); + + @Override + public boolean onTouchEvent(MotionEvent event) { + //获取点击位置 + int x = (int)event.getX(); + int y = (int)event.getY(); + //设置触摸点 + setTouchPoint(x,y); + + switch (event.getAction()){ + case MotionEvent.ACTION_DOWN: + //移动的点击位置 + mMoveX = 0; + mMoveY = 0; + //是否移动 + isMove = false; + //是否存在下一章 + noNext = false; + //是下一章还是前一章 + isNext = false; + //是否正在执行动画 + isRunning = false; + //取消 + isCancel = false; + //设置起始位置的触摸点 + setStartPoint(x,y); + //如果存在动画则取消动画 + abortAnim(); + break; + case MotionEvent.ACTION_MOVE: + final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + //判断是否移动了 + if (!isMove) { + isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop; + } + + if (isMove){ + //判断是否是准备移动的状态(将要移动但是还没有移动) + if (mMoveX == 0 && mMoveY ==0) { + //判断翻得是上一页还是下一页 + if (y - mStartY > 0){ + //上一页的参数配置 + isNext = false; + boolean hasPrev = mListener.hasPrev(); + setDirection(Direction.PRE); + //如果上一页不存在 + if (!hasPrev) { + noNext = true; + return true; + } + }else{ + //进行下一页的配置 + isNext = true; + //判断是否下一页存在 + boolean hasNext = mListener.hasNext(); + //如果存在设置动画方向 + setDirection(Direction.NEXT); + + //如果不存在表示没有下一页了 + if (!hasNext) { + noNext = true; + return true; + } + } + }else{ + //判断是否取消翻页 + if (isNext){ + if (y - mMoveY > 0){ + isCancel = true; + }else { + isCancel = false; + } + }else{ + if (y - mMoveY < 0){ + isCancel = true; + }else { + isCancel = false; + } + } + } + + mMoveX = x; + mMoveY = y; + isRunning = true; + mView.invalidate(); + } + break; + case MotionEvent.ACTION_UP: + if (!isMove){ + isNext = y > mScreenHeight / 2 || SysManager.getSetting().isAlwaysNext(); + + if (isNext) { + //判断是否下一页存在 + boolean hasNext = mListener.hasNext(); + //设置动画方向 + setDirection(Direction.NEXT); + if (!hasNext) { + return true; + } + } else { + boolean hasPrev = mListener.hasPrev(); + setDirection(Direction.PRE); + if (!hasPrev) { + return true; + } + } + } + + // 是否取消翻页 + if (isCancel){ + mListener.pageCancel(); + } + + // 开启翻页效果 + if (!noNext) { + startAnim(); + mView.invalidate(); + } + break; + } + return true; + } + + @Override + public void draw(Canvas canvas) { + if (isRunning) { + drawMove(canvas); + } else { + if (isCancel){ + mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true); + } + drawStatic(canvas); + } + } + + @Override + public void scrollAnim() { + if (mScroller.computeScrollOffset()) { + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + + setTouchPoint(x, y); + + if (mScroller.getFinalX() == x && mScroller.getFinalY() == y){ + isRunning = false; + } + mView.postInvalidate(); + } + } + + @Override + public void abortAnim() { + if (!mScroller.isFinished()){ + mScroller.abortAnimation(); + isRunning = false; + setTouchPoint(mScroller.getFinalX(),mScroller.getFinalY()); + mView.postInvalidate(); + } + } + + @Override + public Bitmap getBgBitmap() { + return mNextBitmap; + } + + @Override + public Bitmap getNextBitmap() { + return mNextBitmap; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java b/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java index 239dd5c..16d5281 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java @@ -4,14 +4,14 @@ package xyz.fycz.myreader.widget.page; public class BookChapterBean { //章节所属的小说(网络) - String bookId; + public String bookId; //章节名 - String title; + public String title; //章节内容在文章中的起始位置(本地) - long start; + public long start; //章节内容在文章中的终止位置(本地) - long end; + public long end; public String getBookId() { return bookId; diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java index 3efe950..6b700c7 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java @@ -1,21 +1,30 @@ package xyz.fycz.myreader.widget.page; +import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.graphics.*; +import android.os.Build; import android.text.Layout; import android.text.StaticLayout; import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; + import android.text.TextPaint; +import android.util.DisplayMetrics; import android.util.Log; +import com.gyf.immersionbar.ImmersionBar; +import com.luhuiguo.chinese.ChineseUtils; + import io.reactivex.Single; import io.reactivex.SingleObserver; import io.reactivex.SingleOnSubscribe; import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.enums.Font; @@ -24,10 +33,16 @@ import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.greendao.entity.Chapter; import xyz.fycz.myreader.model.audio.ReadAloudService; +import xyz.fycz.myreader.util.IOUtils; +import xyz.fycz.myreader.util.StatusBarUtil; import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.BitmapUtil; +import xyz.fycz.myreader.util.utils.MeUtils; import xyz.fycz.myreader.util.utils.RxUtils; import xyz.fycz.myreader.util.utils.ScreenUtils; import xyz.fycz.myreader.util.utils.StringUtils; +import xyz.fycz.myreader.widget.page2.TxtChar; +import xyz.fycz.myreader.widget.page2.TxtLine; import java.io.BufferedReader; import java.io.File; @@ -39,7 +54,7 @@ import java.util.List; import static xyz.fycz.myreader.common.APPCONST.*; /** - * Created by newbiechen on 17-7-1. + * Created by fengyue on 20-11-21 */ public abstract class PageLoader { @@ -54,8 +69,8 @@ public abstract class PageLoader { public static final int STATUS_PARSE_ERROR = 6; // 本地文件解析错误(暂未被使用) public static final int STATUS_CATEGORY_EMPTY = 7; // 获取到的目录为空 // 默认的显示参数配置 - private static final int DEFAULT_MARGIN_HEIGHT = 28; - private static final int DEFAULT_MARGIN_WIDTH = 15; + public static final int DEFAULT_MARGIN_HEIGHT = 28; + public static final int DEFAULT_MARGIN_WIDTH = 15; private static final int DEFAULT_TIP_SIZE = 12; private static final int EXTRA_TITLE_SIZE = 4; private static final int TIP_ALPHA = 180; @@ -84,7 +99,7 @@ public abstract class PageLoader { // 绘制提示的画笔 private TextPaint mTipPaint; // 绘制标题的画笔 - private Paint mTitlePaint; + private TextPaint mTitlePaint; // 绘制背景颜色的画笔(用来擦除需要重绘的部分) private Paint mBgPaint; // 绘制小说内容的画笔 @@ -95,7 +110,8 @@ public abstract class PageLoader { private TxtPage mCancelPage; // 存储阅读记录类 // private BookRecordBean mBookRecord; - + //缩进 + String indent; private Disposable mPreLoadDisp; /*****************params**************************/ @@ -112,8 +128,6 @@ public abstract class PageLoader { private PageMode mPageMode; // 加载器的颜色主题 // private PageStyle mPageStyle; - //当前是否是日间模式 - private boolean isDayMode; //书籍绘制区域的宽高 private int mVisibleWidth; private int mVisibleHeight; @@ -121,8 +135,10 @@ public abstract class PageLoader { private int mDisplayWidth; private int mDisplayHeight; //间距 - private int mMarginWidth; - private int mMarginHeight; + private int mMarginTop; + private int mMarginBottom; + private int mMarginLeft; + private int mMarginRight; //字体的颜色 private int mTextColor; //字体类型 @@ -142,7 +158,6 @@ public abstract class PageLoader { private int mBatteryLevel; //当前页面的背景 private int mBgColor; - //繁简体 private Language language; // 当前章 @@ -153,6 +168,12 @@ public abstract class PageLoader { private boolean resetReadAloud; //是否重新朗读 private int readAloudParagraph = -1; //正在朗读章节 + private Bitmap bgBitmap; + + public void resetReadAloudParagraph() { + readAloudParagraph = -1; + } + /*****************************init params*******************************/ public PageLoader(PageView pageView, Book collBook, Setting setting) { mPageView = pageView; @@ -182,40 +203,72 @@ public abstract class PageLoader { prepareBook(); } + /** + * 刷新界面 + */ + public void refreshUi() { + initData(); + initPaint(); + upMargin(); + } + private void initData() { /*// 获取配置管理器 mSettingManager = SysManager.getSetting();*/ // 获取配置参数 mPageMode = mSettingManager.getPageMode(); - isDayMode = mSettingManager.isDayStyle(); //获取字体 getFont(mSettingManager.getFont()); //获取繁简体 language = mSettingManager.getLanguage(); // mPageStyle = mSettingManager.getPageStyle(); - // 初始化参数 - mMarginWidth = ScreenUtils.dpToPx(DEFAULT_MARGIN_WIDTH); - mMarginHeight = ScreenUtils.dpToPx(DEFAULT_MARGIN_HEIGHT); + indent = StringUtils.repeat(StringUtils.halfToFull(" "), mSettingManager.getIntent()); + + initBgBitmap(); // 配置文字有关的参数 - setUpTextParams(mSettingManager.getReadWordSize()); + setUpTextParams(); } + private void initBgBitmap() { + if (!mSettingManager.bgIsColor()) { + String bgPath = mSettingManager.getBgPath(); + if (bgPath == null) { + return; + } + Resources resources = MyApplication.getApplication().getResources(); + DisplayMetrics dm = resources.getDisplayMetrics(); + int width = dm.widthPixels; + int height = dm.heightPixels; + try { + if (mSettingManager.bgIsAssert()) { + bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), bgPath, width, height); + } else { + bgBitmap = BitmapUtil.getFitSampleBitmap(bgPath, width, height); + } + if (bgBitmap == null) { + ToastUtils.showError("背景加载失败,已加载默认背景"); + bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), "bg/p01.jpg", width, height); + } + } catch (Exception e) { + ToastUtils.showError("背景加载失败,已加载默认背景"); + bgBitmap = MeUtils.getFitAssetsSampleBitmap(mContext.getAssets(), "bg/p01.jpg", width, height); + } + } + } /** * 作用:设置与文字相关的参数 - * - * @param textSize */ - private void setUpTextParams(float textSize) { + private void setUpTextParams() { // 文字大小 - mTextSize = textSize * 2; + mTextSize = ScreenUtils.spToPx(mSettingManager.getReadWordSize()); mTitleSize = mTextSize + ScreenUtils.spToPx(EXTRA_TITLE_SIZE); // 行间距(大小为字体的一半) - mTextInterval = mTextSize / 2; - mTitleInterval = mTitleSize / 2; + mTextInterval = (int) (mTextSize * mSettingManager.getLineMultiplier() / 2); + mTitleInterval = (int) (mTitleSize * mSettingManager.getLineMultiplier() / 2); // 段落间距(大小为字体的高度) - mTextPara = mTextSize; - mTitlePara = mTitleSize; + mTextPara = (int) (mTextSize * mSettingManager.getParagraphSize()); + mTitlePara = (int) (mTitleSize * mSettingManager.getParagraphSize()); } private void initPaint() { @@ -232,6 +285,9 @@ public abstract class PageLoader { mTextPaint = new TextPaint(); mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(mTextSize); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mTextPaint.setLetterSpacing(mSettingManager.getTextLetterSpacing()); + } mTextPaint.setTypeface(mTypeFace); mTextPaint.setAntiAlias(true); @@ -239,14 +295,13 @@ public abstract class PageLoader { mTitlePaint = new TextPaint(); mTitlePaint.setColor(mTextColor); mTitlePaint.setTextSize(mTitleSize); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mTitlePaint.setLetterSpacing(mSettingManager.getTextLetterSpacing()); + } mTitlePaint.setStyle(Paint.Style.FILL_AND_STROKE); - mTitlePaint.setTypeface(mTypeFace); + mTitlePaint.setTypeface(Typeface.create(mTypeFace, Typeface.BOLD)); mTitlePaint.setAntiAlias(true); - // 绘制背景的画笔 - mBgPaint = new Paint(); - mBgPaint.setColor(mBgColor); - // 绘制电池的画笔 mBatteryPaint = new TextPaint(); mBatteryPaint.setAntiAlias(true); @@ -255,7 +310,7 @@ public abstract class PageLoader { mBatteryPaint.setTypeface(Typeface.createFromAsset(mContext.getAssets(), "fonts/number.ttf")); // 初始化页面样式 - setPageStyle(isDayMode); + setPageStyle(); } private void initPageView() { @@ -358,6 +413,7 @@ public abstract class PageLoader { public boolean skipToNextPage() { return mPageView.autoNextPage(); } + /** * 翻到下一页,无动画 */ @@ -369,12 +425,24 @@ public abstract class PageLoader { skipNextChapter(); } + /** + * 翻到上一页,无动画 + */ + void noAnimationToPrePage() { + if (getPagePos() > 0) { + skipToPage(getPagePos() - 1); + return; + } + skipPreChapter(); + skipToPage(mCurChapter.getPageSize() - 1); + } + /** * 更新时间 */ public void updateTime() { - if (!mPageView.isRunning()) { - mPageView.drawCurPage(true); + if (!mPageView.isRunning() && !mSettingManager.isShowStatusBar()) { + mPageView.drawCurPage(mPageMode == PageMode.SCROLL); } } @@ -384,9 +452,9 @@ public abstract class PageLoader { * @param level */ public void updateBattery(int level) { - mBatteryLevel = level; - if (!mPageView.isRunning()) { - mPageView.drawCurPage(true); + if (!mPageView.isRunning() && !mSettingManager.isShowStatusBar()) { + mBatteryLevel = level; + mPageView.drawCurPage(mPageMode == PageMode.SCROLL); } } @@ -404,38 +472,12 @@ public abstract class PageLoader { /** * 设置文字相关参数 - * - * @param textSize */ - public void setTextSize(int textSize) { + public void setTextSize() { // 设置文字相关参数 - setUpTextParams(textSize); - - // 设置画笔的字体大小 - mTextPaint.setTextSize(mTextSize); - // 设置标题的字体大小 - mTitlePaint.setTextSize(mTitleSize); - /*// 存储文字大小 - mSettingManager.setReadWordSize(mTextSize);*/ - // 取消缓存 - mPreChapter = null; - mNextChapter = null; - - // 如果当前已经显示数据 - if (isChapterListPrepare && mStatus == STATUS_FINISH) { - // 重新计算当前页面 - dealLoadPageList(mCurChapterPos); - - // 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题 - if (mCurPage.position >= mCurChapter.getPageSize()) { - mCurPage.position = mCurChapter.getPageSize() - 1; - } - - // 重新获取指定页面 - mCurPage = mCurChapter.getPage(mCurPage.position); - } - - mPageView.drawCurPage(false); + setUpTextParams(); + initPaint(); + refreshPagePara(); } /** @@ -444,7 +486,6 @@ public abstract class PageLoader { * @param dayMode */ public void setNightMode(boolean dayMode) { - isDayMode = dayMode; if (!dayMode) { mBatteryPaint.setColor(Color.WHITE); } else { @@ -455,57 +496,21 @@ public abstract class PageLoader { /** * 设置页面样式 */ - public void setPageStyle(boolean dayMode) { - /*if (pageStyle != PageStyle.NIGHT) { - mPageStyle = pageStyle; - mSettingManager.setPageStyle(pageStyle); - } - - if (isNightMode && pageStyle != PageStyle.NIGHT) { - return; - } -*/ + public void setPageStyle() { int textColorId; int bgColorId; - switch (mSettingManager.getReadStyle()) { - case common: - textColorId = READ_STYLE_COMMON[0]; - bgColorId = READ_STYLE_COMMON[1]; - break; - case leather: - default: - textColorId = READ_STYLE_LEATHER[0]; - bgColorId = READ_STYLE_LEATHER[1]; - break; - case protectedEye: - textColorId = READ_STYLE_PROTECTED_EYE[0]; - bgColorId = READ_STYLE_PROTECTED_EYE[1]; - break; - case breen: - textColorId = READ_STYLE_BREEN_EYE[0]; - bgColorId = READ_STYLE_BREEN_EYE[1]; - break; - case blueDeep: - textColorId = READ_STYLE_BLUE_DEEP[0]; - bgColorId = READ_STYLE_BLUE_DEEP[1]; - break; - } - if (!dayMode) { - mTextColor = ContextCompat.getColor(mContext, READ_STYLE_NIGHT[0]); - mBgColor = ContextCompat.getColor(mContext, READ_STYLE_NIGHT[1]); - mBatteryPaint.setColor(mTextColor); - } else { - mTextColor = ContextCompat.getColor(mContext, textColorId); - mBgColor = ContextCompat.getColor(mContext, bgColorId); - mBatteryPaint.setColor(Color.BLACK); - } - // 设置当前颜色样式 + textColorId = mSettingManager.getTextColor(); + bgColorId = mSettingManager.getBgColor(); + + mTextColor = textColorId; + mBgColor = bgColorId; + mBatteryPaint.setColor(Color.BLACK); + // 设置当前颜色样式 mTipPaint.setColor(mTextColor); mTitlePaint.setColor(mTextColor); mTextPaint.setColor(mTextColor); mBatteryPaint.setColor(mTextColor); - mBgPaint.setColor(mBgColor); mBatteryPaint.setAlpha(TIP_ALPHA); @@ -522,7 +527,6 @@ public abstract class PageLoader { mPageMode = pageMode; mPageView.setPageMode(mPageMode); - mSettingManager.setPageMode(mPageMode); // 重新绘制当前页 mPageView.drawCurPage(false); @@ -540,6 +544,10 @@ public abstract class PageLoader { mTipPaint.setTypeface(mTypeFace); mTextPaint.setTypeface(mTypeFace); mTitlePaint.setTypeface(mTypeFace); + refreshPagePara(); + } + + private void refreshPagePara() { // 取消缓存 mPreChapter = null; mNextChapter = null; @@ -567,7 +575,7 @@ public abstract class PageLoader { * @param font */ public void getFont(Font font) { - String fontFileName = mSettingManager.getFont().fileName; + String fontFileName = mSettingManager.getFont().toString() + ".ttf"; if (font == Font.本地字体) { fontFileName = mSettingManager.getLocalFontName(); } @@ -601,21 +609,10 @@ public abstract class PageLoader { } /** - * 设置内容与屏幕的间距 - * - * @param marginWidth :单位为 px - * @param marginHeight :单位为 px + * 设置内容与屏幕的间距 单位为 px */ - public void setMargin(int marginWidth, int marginHeight) { - mMarginWidth = marginWidth; - mMarginHeight = marginHeight; - - // 如果是滑动动画,则需要重新创建了 - if (mPageMode == PageMode.SCROLL) { - mPageView.setPageMode(PageMode.SCROLL); - } - - mPageView.drawCurPage(false); + public void upMargin() { + prepareDisplay(mDisplayWidth, mDisplayHeight); } /** @@ -693,36 +690,22 @@ public abstract class PageLoader { } /** - * 获取距离屏幕的高度 + * 获取距离屏幕的上高度 * * @return */ - public int getMarginHeight() { - return mMarginHeight; + public int getMarginTop() { + return mMarginTop; } /** - * 保存阅读记录 + * 获取距离屏幕的上高度 + * + * @return */ - /*public void saveRecord() { - - if (mChapterList.isEmpty()) { - return; - } - - mBookRecord.setBookId(mCollBook.get_id()); - mBookRecord.setChapter(mCurChapterPos); - - if (mCurPage != null) { - mBookRecord.setPagePos(mCurPage.position); - } else { - mBookRecord.setPagePos(0); - } - - //存储到数据库 - BookRepository.getInstance() - .saveBookRecord(mBookRecord); - }*/ + public int getMarginBottom() { + return mMarginBottom; + } /** * 初始化书籍 @@ -872,7 +855,7 @@ public abstract class PageLoader { /***********************************default method***********************************************/ void drawPage(Bitmap bitmap, boolean isUpdate) { - drawBackground(mPageView.getBgBitmap(), isUpdate); + drawBackground(mPageView.getBgBitmap()); if (!isUpdate) { drawContent(bitmap); } @@ -880,15 +863,36 @@ public abstract class PageLoader { mPageView.invalidate(); } - private void drawBackground(Bitmap bitmap, boolean isUpdate) { + /** + * 横翻模式绘制背景 + */ + private synchronized void drawBackground(Bitmap bitmap) { + if (bitmap == null) return; Canvas canvas = new Canvas(bitmap); + if (!mSettingManager.bgIsColor()) { + if (bgBitmap == null || bgBitmap.isRecycled()) { + initBgBitmap(); + } + Rect mDestRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawBitmap(bgBitmap, null, mDestRect, null); + } else { + canvas.drawColor(mBgColor); + } + drawBackground(canvas); + } + + public Bitmap getBgBitmap() { + initBgBitmap(); + return bgBitmap; + } + + private void drawBackground(Canvas canvas) { + if (canvas == null) return; int tipMarginHeight = ScreenUtils.dpToPx(3); String progress = (mStatus != STATUS_FINISH) ? "" : getReadProgress(getChapterPos(), mChapterList.size(), getPagePos(), getAllPagePos()); - if (!isUpdate) { - /****绘制背景****/ - canvas.drawColor(mBgColor); - + /****绘制背景****/ + if (!mSettingManager.isShowStatusBar()) { if (!mChapterList.isEmpty()) { /*****初始化标题的参数********/ //需要注意的是:绘制text的y的起始点是text的基准线的位置,而不是从text的头部的位置 @@ -897,81 +901,95 @@ public abstract class PageLoader { if (mStatus != STATUS_FINISH) { if (isChapterListPrepare) { canvas.drawText(mChapterList.get(mCurChapterPos).getTitle() - , mMarginWidth, tipTop, mTipPaint); + , mMarginLeft, tipTop, mTipPaint); } } else { - String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, mDisplayWidth - 2 * mMarginWidth - mTipPaint.measureText(progress), TextUtils.TruncateAt.END).toString(); - canvas.drawText(title, mMarginWidth, tipTop, mTipPaint); + String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, mDisplayWidth - mMarginLeft - mMarginRight - mTipPaint.measureText(progress), TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, mMarginLeft, tipTop, mTipPaint); + /******绘制页码********/ + // 底部的字显示的位置Y + float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; + String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize(); + canvas.drawText(percent, mMarginLeft, y, mTipPaint); } - /*******绘制进度*******/ - float progressTipLeft = mDisplayWidth - mMarginWidth - mTipPaint.measureText(progress); + float progressTipLeft = mDisplayWidth - mMarginRight - mTipPaint.measureText(progress); canvas.drawText(progress, progressTipLeft, tipTop, mTipPaint); - - /******绘制页码********/ - // 底部的字显示的位置Y - float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; - // 只有finish的时候采用页码 - if (mStatus == STATUS_FINISH) { - String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize(); - canvas.drawText(percent, mMarginWidth, y, mTipPaint); + } + } else { + float tipBottom = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; + //根据状态不一样,数据不一样 + if (mStatus != STATUS_FINISH) { + if (isChapterListPrepare) { + canvas.drawText(mChapterList.get(mCurChapterPos).getTitle() + , mMarginLeft, tipBottom, mTipPaint); } + } else { + /******绘制页码********/ + String percent = (mCurPage.position + 1) + "/" + mCurChapter.getPageSize(); + //页码的x坐标 + float tipLeft = mDisplayWidth - 2 * mMarginRight - mTipPaint.measureText(percent + progress); + canvas.drawText(percent, tipLeft, tipBottom, mTipPaint); + String title = TextUtils.ellipsize(mCurPage.title, mTipPaint, tipLeft - 2 * mMarginRight, TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, mMarginLeft, tipBottom, mTipPaint); } - } else { - //擦除区域 - mBgPaint.setColor(mBgColor); - canvas.drawRect(mDisplayWidth / 2, mDisplayHeight - mMarginHeight + ScreenUtils.dpToPx(2), mDisplayWidth, mDisplayHeight, mBgPaint); + /*******绘制进度*******/ + float progressTipLeft = mDisplayWidth - mMarginRight - mTipPaint.measureText(progress); + canvas.drawText(progress, progressTipLeft, tipBottom, mTipPaint); } - /******绘制电池********/ - int visibleRight = mDisplayWidth - mMarginWidth; - int visibleBottom = mDisplayHeight - tipMarginHeight; + if (!mSettingManager.isShowStatusBar()) { + /******绘制电池********/ + + int visibleRight = mDisplayWidth - mMarginRight; + int visibleBottom = mDisplayHeight - tipMarginHeight; - int outFrameWidth = (int) mTipPaint.measureText("xxx"); - int outFrameHeight = (int) mTipPaint.getTextSize(); + int outFrameWidth = (int) mTipPaint.measureText("xxx"); + int outFrameHeight = (int) mTipPaint.getTextSize(); - int polarHeight = ScreenUtils.dpToPx(6); - int polarWidth = ScreenUtils.dpToPx(2); - int border = 1; - int innerMargin = 1; + int polarHeight = ScreenUtils.dpToPx(6); + int polarWidth = ScreenUtils.dpToPx(2); + int border = 1; + int innerMargin = 1; - //电极的制作 - int polarLeft = visibleRight - polarWidth; - int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2; - Rect polar = new Rect(polarLeft, polarTop, visibleRight, - polarTop + polarHeight - ScreenUtils.dpToPx(2)); + //电极的制作 + int polarLeft = visibleRight - polarWidth; + int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2; + Rect polar = new Rect(polarLeft, polarTop, visibleRight, + polarTop + polarHeight - ScreenUtils.dpToPx(2)); - mBatteryPaint.setStyle(Paint.Style.FILL); - canvas.drawRect(polar, mBatteryPaint); + mBatteryPaint.setStyle(Paint.Style.FILL); + canvas.drawRect(polar, mBatteryPaint); - //外框的制作 - int outFrameLeft = polarLeft - outFrameWidth; - int outFrameTop = visibleBottom - outFrameHeight; - int outFrameBottom = visibleBottom - ScreenUtils.dpToPx(2); - Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, outFrameBottom); + //外框的制作 + int outFrameLeft = polarLeft - outFrameWidth; + int outFrameTop = visibleBottom - outFrameHeight; + int outFrameBottom = visibleBottom - ScreenUtils.dpToPx(2); + Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, outFrameBottom); - mBatteryPaint.setStyle(Paint.Style.STROKE); - mBatteryPaint.setStrokeWidth(border); - canvas.drawRect(outFrame, mBatteryPaint); + mBatteryPaint.setStyle(Paint.Style.STROKE); + mBatteryPaint.setStrokeWidth(border); + canvas.drawRect(outFrame, mBatteryPaint); - //绘制电量 - mBatteryPaint.setStyle(Paint.Style.FILL); - Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics(); - String batteryLevel = String.valueOf(mBatteryLevel); - float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2 - ScreenUtils.dpToPx(1) / 2f; - float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2 - ScreenUtils.dpToPx(1); - canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint); + //绘制电量 + mBatteryPaint.setStyle(Paint.Style.FILL); + Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics(); + String batteryLevel = String.valueOf(mBatteryLevel); + float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2 - ScreenUtils.dpToPx(1) / 2f; + float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2 - ScreenUtils.dpToPx(1); + canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint); - /******绘制当前时间********/ - //底部的字显示的位置Y - float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; - String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm"); - float x = (mDisplayWidth - mTipPaint.measureText(time)) / 2; - canvas.drawText(time, x, y, mTipPaint); + /******绘制当前时间********/ + //底部的字显示的位置Y + float y = mDisplayHeight - mTipPaint.getFontMetrics().bottom - tipMarginHeight; + String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm"); + float x = (mDisplayWidth - mTipPaint.measureText(time)) / 2; + canvas.drawText(time, x, y, mTipPaint); + } } private void drawContent(Bitmap bitmap) { @@ -1015,11 +1033,13 @@ public abstract class PageLoader { canvas.drawText(tip, pivotX, pivotY, mTextPaint); } else { float top; - if (mPageMode == PageMode.SCROLL) { top = -mTextPaint.getFontMetrics().top; } else { - top = mMarginHeight - mTextPaint.getFontMetrics().top; + top = mMarginTop - mTextPaint.getFontMetrics().top; + if (mSettingManager.isShowStatusBar()) { + top += ImmersionBar.getStatusBarHeight((Activity) mContext); + } } //设置总距离 @@ -1065,7 +1085,17 @@ public abstract class PageLoader { int paragraphLength = mCurPage.position == 0 ? strLength + titleLen : mCurChapter.getPageLength(mCurPage.position - 1) + strLength; isLight = ReadAloudService.running && readAloudParagraph == mCurChapter.getParagraphIndex(paragraphLength); mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : mTextColor); - canvas.drawText(str, mMarginWidth, top, mTextPaint); + if (!mSettingManager.isTightCom()) { + Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint); + if (needScale(str)) { + drawScaledText(canvas, str, width, mTextPaint, top, i, mCurPage.txtLists); + } else { + canvas.drawText(str, mMarginLeft, top, mTextPaint); + } + } else { + canvas.drawText(str, mMarginLeft, top, mTextPaint); + } if (str.endsWith("\n")) { top += para; } else { @@ -1075,18 +1105,78 @@ public abstract class PageLoader { } } + private void drawScaledText(Canvas canvas, String line, float lineWidth, TextPaint paint, float top, int y, List txtLists) { + float x = mMarginLeft; + + if (isFirstLineOfParagraph(line)) { + canvas.drawText(indent, x, top, paint); + float bw = StaticLayout.getDesiredWidth(indent, paint); + x += bw; + line = line.substring(mSettingManager.getIntent()); + } + int gapCount = line.length() - 1; + int i = 0; + + TxtLine txtList = new TxtLine();//每一行pzl + txtList.setCharsData(new ArrayList<>());//pzl + + float d = ((mDisplayWidth - (mMarginLeft + mMarginRight)) - lineWidth) / gapCount; + for (; i < line.length(); i++) { + String c = String.valueOf(line.charAt(i)); + float cw = StaticLayout.getDesiredWidth(c, paint); + canvas.drawText(c, x, top, paint); + //pzl + TxtChar txtChar = new TxtChar(); + txtChar.setChardata(line.charAt(i)); + if (i == 0) txtChar.setCharWidth(cw + d / 2); + if (i == gapCount) txtChar.setCharWidth(cw + d / 2); + txtChar.setCharWidth(cw + d); + ;//字宽 + //txtChar.Index = y;//每页每个字的位置 + txtList.getCharsData().add(txtChar); + //pzl + x += cw + d; + } + if (txtLists != null) { + txtLists.set(y, txtList);//pzl + } + } + + //判断是不是d'hou + private boolean isFirstLineOfParagraph(String line) { + return line.length() > 3 && line.charAt(0) == (char) 12288 && line.charAt(1) == (char) 12288; + } + + private boolean needScale(String line) {//判断不是空行 + return line != null && line.length() != 0 && line.charAt(line.length() - 1) != '\n'; + } + void prepareDisplay(int w, int h) { // 获取PageView的宽高 mDisplayWidth = w; mDisplayHeight = h; + // 设置边距 + mMarginTop = mSettingManager.isShowStatusBar() ? + ScreenUtils.dpToPx(mSettingManager.getPaddingTop() + DEFAULT_MARGIN_HEIGHT - 8) : + ScreenUtils.dpToPx(mSettingManager.getPaddingTop() + DEFAULT_MARGIN_HEIGHT); + mMarginBottom = ScreenUtils.dpToPx(mSettingManager.getPaddingBottom() + DEFAULT_MARGIN_HEIGHT); + mMarginLeft = ScreenUtils.dpToPx(mSettingManager.getPaddingLeft()); + mMarginRight = ScreenUtils.dpToPx(mSettingManager.getPaddingRight()); + // 获取内容显示位置的大小 - mVisibleWidth = mDisplayWidth - mMarginWidth * 2; - mVisibleHeight = mDisplayHeight - mMarginHeight * 2; + mVisibleWidth = mDisplayWidth - (mMarginLeft + mMarginRight); + mVisibleHeight = !mSettingManager.isShowStatusBar() ? mDisplayHeight - (mMarginTop + mMarginBottom) + : mDisplayHeight - (mMarginTop + mMarginBottom) - mPageView.getStatusBarHeight(); // 重置 PageMode mPageView.setPageMode(mPageMode); + + // 取消缓存 + mPreChapter = null; + mNextChapter = null; + if (!isChapterOpen) { // 展示加载界面 mPageView.drawCurPage(false); @@ -1113,6 +1203,7 @@ public abstract class PageLoader { * @return */ boolean prev() { + // 以下情况禁止翻页 if (!canTurnPage()) { return false; @@ -1407,6 +1498,7 @@ public abstract class PageLoader { TxtChapter txtChapter = new TxtChapter(chapter.getNumber()); //使用流的方式加载 List lines = new ArrayList<>(); + List txtLists = new ArrayList<>();//记录每个字的位置 //pzl float rHeight = mVisibleHeight; int titleLinesCount = 0; boolean showTitle = true; // 是否展示标题 @@ -1414,12 +1506,17 @@ public abstract class PageLoader { paragraph = paragraph.trim() + "\n"; try { while (showTitle || (paragraph = br.readLine()) != null) { + if (mSettingManager.getLanguage() == Language.traditional) { + paragraph = ChineseUtils.toTraditional(paragraph); + } else if (mSettingManager.getLanguage() == Language.simplified) { + paragraph = ChineseUtils.toSimplified(paragraph); + } // 重置段落 if (!showTitle) { paragraph = paragraph.replaceAll("\\s", "").trim(); // 如果只有换行符,那么就不执行 if (paragraph.equals("")) continue; - paragraph = StringUtils.halfToFull(" " + paragraph + "\n"); + paragraph = indent + paragraph + "\n"; } else { //设置 title 的顶部间距 rHeight -= mTitlePara; @@ -1441,33 +1538,65 @@ public abstract class PageLoader { page.position = txtChapter.getTxtPageList().size(); page.title = chapter.getTitle(); page.lines = new ArrayList<>(lines); + page.txtLists = new ArrayList<>(txtLists); page.titleLines = titleLinesCount; txtChapter.addPage(page); addTxtPageLength(txtChapter, page.getContent().length()); // 重置Lines lines.clear(); + txtLists.clear();//pzl rHeight = mVisibleHeight; titleLinesCount = 0; continue; } //测量一行占用的字节数 - if (showTitle) { - wordCount = mTitlePaint.breakText(paragraph, - true, mVisibleWidth, null); + if (mSettingManager.isTightCom()) { + if (showTitle) { + wordCount = mTitlePaint.breakText(paragraph, + true, mVisibleWidth, null); + } else { + wordCount = mTextPaint.breakText(paragraph, + true, mVisibleWidth, null); + } + + subStr = paragraph.substring(0, wordCount); + if (paragraph.substring(wordCount).equals("\n")) { + subStr += "\n"; + } } else { - wordCount = mTextPaint.breakText(paragraph, - true, mVisibleWidth, null); + Layout tempLayout; + if (showTitle) { + tempLayout = new StaticLayout(paragraph, mTitlePaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + } else { + tempLayout = new StaticLayout(paragraph, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + } + wordCount = tempLayout.getLineEnd(0); + subStr = paragraph.substring(0, wordCount); } - subStr = paragraph.substring(0, wordCount); - if (paragraph.substring(wordCount).equals("\n")) { - subStr += "\n"; - } if (!subStr.equals("\n")) { //将一行字节,存储到lines中 lines.add(subStr); - + //begin pzl + //记录每个字的位置 + char[] cs = subStr.toCharArray(); + TxtLine txtList = new TxtLine();//每一行 + txtList.setCharsData(new ArrayList()); + for (char c : cs) { + String mesasrustr = String.valueOf(c); + float charwidth = mTextPaint.measureText(mesasrustr); + if (showTitle) { + charwidth = mTitlePaint.measureText(mesasrustr); + } + TxtChar txtChar = new TxtChar(); + txtChar.setChardata(c); + txtChar.setCharWidth(charwidth);//字宽 + txtChar.setIndex(66);//每页每个字的位置 + txtList.getCharsData().add(txtChar); + } + txtLists.add(txtList); + //end pzl //设置段落间距 if (showTitle) { titleLinesCount += 1; @@ -1497,11 +1626,13 @@ public abstract class PageLoader { page.position = txtChapter.getTxtPageList().size(); page.title = chapter.getTitle(); page.lines = new ArrayList<>(lines); + page.txtLists = new ArrayList<>(txtLists); page.titleLines = titleLinesCount; txtChapter.addPage(page); addTxtPageLength(txtChapter, page.getContent().length()); //重置Lines lines.clear(); + txtLists.clear(); } } catch (IOException e) { e.printStackTrace(); @@ -1517,8 +1648,10 @@ public abstract class PageLoader { return txtChapter; } + /** * 添加TxtChapter的长度 + * * @param txtChapter * @param length */ @@ -1532,6 +1665,7 @@ public abstract class PageLoader { /** * 添加TxtChapter段落长度 + * * @param txtChapter * @param length */ @@ -1563,7 +1697,7 @@ public abstract class PageLoader { return null; } if (mPageChangeListener != null) { - mPageChangeListener.onPageChange(pos,resetReadAloud); + mPageChangeListener.onPageChange(pos, resetReadAloud); resetReadAloud = true; } return mCurChapter.getPage(pos); @@ -1658,6 +1792,7 @@ public abstract class PageLoader { } return strLength; } + /** * @return 本页内容 */ @@ -1748,6 +1883,7 @@ public abstract class PageLoader { mPageView.invalidate(); } } + /*****************************************interface*****************************************/ public interface OnPageChangeListener { diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java index b5ca9ef..5d55685 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java @@ -1,10 +1,16 @@ package xyz.fycz.myreader.widget.page; +import xyz.fycz.myreader.enums.BookSource; + /** * Created by newbiechen on 2018/2/5. * 作用:翻页动画的模式 */ public enum PageMode { - SIMULATION, COVER, SLIDE, NONE, SCROLL + SIMULATION, COVER, SLIDE, NONE, SCROLL, VERTICAL_COVER, AUTO; + + public static PageMode fromString(String string) { + return valueOf(string); + } } diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java index f9142c8..d467c73 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java @@ -1,16 +1,23 @@ package xyz.fycz.myreader.widget.page; +import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RectF; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; + +import com.gyf.immersionbar.ImmersionBar; + +import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.ui.popmenu.AutoPageMenu; import xyz.fycz.myreader.util.utils.SnackbarUtils; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.widget.animation.*; @@ -26,13 +33,14 @@ public class PageView extends View { private int mViewWidth = 0; // 当前View的宽 private int mViewHeight = 0; // 当前View的高 + private int statusBarHeight = 0; //状态栏高度 private int mStartX = 0; private int mStartY = 0; private boolean isMove = false; // 初始化参数 private int mBgColor = 0xFFCEC29C; - private PageMode mPageMode = PageMode.SIMULATION; + private PageMode mPageMode = PageMode.COVER; // 是否允许点击 private boolean canTouch = true; // 唤醒菜单的区域 @@ -73,6 +81,7 @@ public class PageView extends View { public PageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + statusBarHeight = ImmersionBar.getStatusBarHeight((Activity) getContext()); } @Override @@ -104,12 +113,18 @@ public class PageView extends View { case SLIDE: mPageAnim = new SlidePageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); break; + case VERTICAL_COVER: + mPageAnim = new CoverVerticalPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); + break; case NONE: mPageAnim = new NonePageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); break; case SCROLL: mPageAnim = new ScrollPageAnim(mViewWidth, mViewHeight, 0, - mPageLoader.getMarginHeight(), this, mPageAnimListener); + mPageLoader.getMarginTop(), mPageLoader.getMarginBottom(),this, mPageAnimListener); + break; + case AUTO: + mPageAnim = new AutoPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); break; default: mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); @@ -121,6 +136,10 @@ public class PageView extends View { return mPageAnim.getNextBitmap(); } + public int getStatusBarHeight() { + return statusBarHeight; + } + public Bitmap getBgBitmap() { if (mPageAnim == null) return null; return mPageAnim.getBgBitmap(); @@ -145,6 +164,12 @@ public class PageView extends View { } } + public void autoPageOnSpeedChange() { + //是否正在执行动画 + abortAnimation(); + mPageLoader.noAnimationToPrePage(); + } + private void startPageAnim(PageAnimation.Direction direction) { if (mTouchListener == null) return; //是否正在执行动画 @@ -166,6 +191,10 @@ public class PageView extends View { } else { int x = 0; int y = mViewHeight; + if (mPageAnim instanceof VerticalPageAnim) { + x = mViewWidth; + y = 0; + } //初始化动画 mPageAnim.setStartPoint(x, y); //设置点击点 @@ -187,10 +216,6 @@ public class PageView extends View { @Override protected void onDraw(Canvas canvas) { - - //绘制背景 - canvas.drawColor(mBgColor); - //绘制动画 mPageAnim.draw(canvas); } @@ -253,7 +278,7 @@ public class PageView extends View { private boolean hasPrevPage() { mTouchListener.prePage(); boolean hasPrevPage = mPageLoader.prev(); - if (!hasPrevPage){ + if (!hasPrevPage) { showSnackBar("已经是第一页了"); } return hasPrevPage; @@ -267,7 +292,7 @@ public class PageView extends View { private boolean hasNextPage() { boolean hasNextPage = mPageLoader.next(); mTouchListener.nextPage(hasNextPage); - if (!hasNextPage){ + if (!hasNextPage) { showSnackBar("已经是最后一页了"); } return hasNextPage; @@ -320,6 +345,10 @@ public class PageView extends View { if (mPageAnim instanceof HorizonPageAnim) { ((HorizonPageAnim) mPageAnim).changePage(); + }else if (mPageAnim instanceof VerticalPageAnim){ + ((VerticalPageAnim) mPageAnim).changePage(); + } else if (mPageAnim instanceof AutoPageAnim) { + ((AutoPageAnim) mPageAnim).changePage(); } mPageLoader.drawPage(getNextBitmap(), false); } @@ -332,7 +361,7 @@ public class PageView extends View { public void drawCurPage(boolean isUpdate) { if (!isPrepare) return; - if (!isUpdate){ + if (!isUpdate) { if (mPageAnim instanceof ScrollPageAnim) { ((ScrollPageAnim) mPageAnim).resetBitmap(); } @@ -363,9 +392,9 @@ public class PageView extends View { return mPageLoader; } // 获取具体的加载器 - if ("本地书籍".equals(collBook.getType())){ + if ("本地书籍".equals(collBook.getType())) { mPageLoader = new LocalPageLoader(this, collBook, ChapterService.getInstance(), setting); - }else { + } else { mPageLoader = new NetPageLoader(this, collBook, ChapterService.getInstance(), mReadCrawler, setting); } // 判断是否 PageView 已经初始化完成 diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java index 85b7e7f..630a418 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java @@ -2,12 +2,21 @@ package xyz.fycz.myreader.widget.page; import java.util.List; +import xyz.fycz.myreader.widget.page2.TxtLine; + public class TxtPage { int position; String title; int titleLines; //当前 lines 中为 title 的行数。 List lines; + List txtLists; + public TxtPage(int position) { + this.position = position; + } + + public TxtPage() { + } public int getTitleLines() { return titleLines; @@ -30,4 +39,5 @@ public class TxtPage { } return s.toString(); } + } diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java new file mode 100644 index 0000000..b4b6528 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/ChapterProvider.java @@ -0,0 +1,213 @@ +package xyz.fycz.myreader.widget.page2; + +import android.text.Layout; +import android.text.StaticLayout; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.util.help.ChapterContentHelp; +import xyz.fycz.myreader.util.utils.NetworkUtils; + +class ChapterProvider { + private PageLoader pageLoader; + private ChapterContentHelp contentHelper = new ChapterContentHelp(); + + ChapterProvider(PageLoader pageLoader) { + this.pageLoader = pageLoader; + } + + TxtChapter dealLoadPageList(Chapter chapter, boolean isPrepare) { + TxtChapter txtChapter = new TxtChapter(chapter.getNumber()); + // 判断章节是否存在 + if (!isPrepare || pageLoader.noChapterData(chapter)) { + if (pageLoader instanceof PageLoaderNet && !NetworkUtils.isNetWorkAvailable()) { + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("网络连接不可用"); + } + return txtChapter; + } + String content; + try { + content = pageLoader.getChapterContent(chapter); + } catch (Exception e) { + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("读取内容出错\n" + e.getLocalizedMessage()); + return txtChapter; + } + if (content == null) { + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("缓存文件不存在"); + return txtChapter; + } + return loadPageList(chapter, content); + } + + /** + * 将章节数据,解析成页面列表 + * + * @param chapter:章节信息 + * @param content:章节的文本 + */ + private TxtChapter loadPageList(Chapter chapter, @NonNull String content) { + //生成的页面 + TxtChapter txtChapter = new TxtChapter(chapter.getNumber()); + /*if (pageLoader.book.isAudio()) { + txtChapter.setStatus(TxtChapter.Status.FINISH); + txtChapter.setMsg(content); + TxtPage page = new TxtPage(txtChapter.getTxtPageList().size()); + page.setTitle(chapter.getDurChapterName()); + page.addLine(chapter.getDurChapterName()); + page.addLine(content); + page.setTitleLines(1); + txtChapter.addPage(page); + addTxtPageLength(txtChapter, page.getContent().length()); + txtChapter.addPage(page); + return txtChapter; + }*/ + content = contentHelper.replaceContent(pageLoader.book.getName(), pageLoader.book.getTag(), content, pageLoader.book.getReplaceEnable()); + String[] allLine = content.split("\n"); + List lines = new ArrayList<>(); + List txtLists = new ArrayList<>();//记录每个字的位置 //pzl + int rHeight = pageLoader.mVisibleHeight - pageLoader.contentMarginHeight * 2; + int titleLinesCount = 0; + boolean showTitle = pageLoader.readBookControl.getShowTitle(); // 是否展示标题 + String paragraph = null; + if (showTitle) { + paragraph = contentHelper.replaceContent(pageLoader.book.getName(), pageLoader.book.getTag(), chapter.getTitle(), pageLoader.book.getReplaceEnable()); + paragraph = paragraph.trim() + "\n"; + } + int i = 1; + while (showTitle || i < allLine.length) { + // 重置段落 + if (!showTitle) { + paragraph = allLine[i].replaceAll("\\s", " ").trim(); + i++; + if (paragraph.equals("")) continue; + paragraph = pageLoader.indent + paragraph + "\n"; + } + addParagraphLength(txtChapter, paragraph.length()); + int wordCount; + String subStr; + while (paragraph.length() > 0) { + //当前空间,是否容得下一行文字 + if (showTitle) { + rHeight -= pageLoader.mTitlePaint.getTextSize(); + } else { + rHeight -= pageLoader.mTextPaint.getTextSize(); + } + // 一页已经填充满了,创建 TextPage + if (rHeight <= 0) { + // 创建Page + TxtPage page = new TxtPage(txtChapter.getTxtPageList().size()); + page.setTitle(chapter.getTitle()); + page.addLines(lines); + page.setTxtLists(new ArrayList<>(txtLists)); + page.setTitleLines(titleLinesCount); + txtChapter.addPage(page); + addTxtPageLength(txtChapter, page.getContent().length()); + // 重置Lines + lines.clear(); + txtLists.clear();//pzl + rHeight = pageLoader.mVisibleHeight - pageLoader.contentMarginHeight * 2; + titleLinesCount = 0; + + continue; + } + + //测量一行占用的字节数 + if (showTitle) { + Layout tempLayout = new StaticLayout(paragraph, pageLoader.mTitlePaint, pageLoader.mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + wordCount = tempLayout.getLineEnd(0); + } else { + Layout tempLayout = new StaticLayout(paragraph, pageLoader.mTextPaint, pageLoader.mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + wordCount = tempLayout.getLineEnd(0); + } + + subStr = paragraph.substring(0, wordCount); + if (!subStr.equals("\n")) { + //将一行字节,存储到lines中 + lines.add(subStr); + //begin pzl + //记录每个字的位置 + char[] cs = subStr.toCharArray(); + TxtLine txtList = new TxtLine();//每一行 + txtList.setCharsData(new ArrayList()); + for (char c : cs) { + String mesasrustr = String.valueOf(c); + float charwidth = pageLoader.mTextPaint.measureText(mesasrustr); + if (showTitle) { + charwidth = pageLoader.mTitlePaint.measureText(mesasrustr); + } + TxtChar txtChar = new TxtChar(); + txtChar.setChardata(c); + txtChar.setCharWidth(charwidth);//字宽 + txtChar.setIndex(66);//每页每个字的位置 + txtList.getCharsData().add(txtChar); + } + txtLists.add(txtList); + //end pzl + //设置段落间距 + if (showTitle) { + titleLinesCount += 1; + rHeight -= pageLoader.mTitleInterval; + } else { + rHeight -= pageLoader.mTextInterval; + } + } + //裁剪 + paragraph = paragraph.substring(wordCount); + } + + //增加段落的间距 + if (!showTitle && lines.size() != 0) { + rHeight = rHeight - pageLoader.mTextPara + pageLoader.mTextInterval; + } + + if (showTitle) { + rHeight = rHeight - pageLoader.mTitlePara + pageLoader.mTitleInterval; + showTitle = false; + } + } + + if (lines.size() != 0) { + //创建Page + TxtPage page = new TxtPage(txtChapter.getTxtPageList().size()); + page.setTitle(chapter.getTitle()); + page.addLines(lines); + page.setTxtLists(new ArrayList<>(txtLists)); + page.setTitleLines(titleLinesCount); + txtChapter.addPage(page); + addTxtPageLength(txtChapter, page.getContent().length()); + //重置Lines + lines.clear(); + txtLists.clear(); + } + if (txtChapter.getPageSize() > 0) { + txtChapter.setStatus(TxtChapter.Status.FINISH); + } else { + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("未加载到内容"); + } + return txtChapter; + } + + private void addTxtPageLength(TxtChapter txtChapter, int length) { + if (txtChapter.getTxtPageLengthList().isEmpty()) { + txtChapter.addTxtPageLength(length); + } else { + txtChapter.addTxtPageLength(txtChapter.getTxtPageLengthList().get(txtChapter.getTxtPageLengthList().size() - 1) + length); + } + } + + private void addParagraphLength(TxtChapter txtChapter, int length) { + if (txtChapter.getParagraphLengthList().isEmpty()) { + txtChapter.addParagraphLength(length); + } else { + txtChapter.addParagraphLength(txtChapter.getParagraphLengthList().get(txtChapter.getParagraphLengthList().size() - 1) + length); + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java new file mode 100644 index 0000000..c6d98f7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/LocalPageLoader.java @@ -0,0 +1,401 @@ +package xyz.fycz.myreader.widget.page2; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.util.IOUtils; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.utils.Charset; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.webapi.callback.ResultCallback; +import xyz.fycz.myreader.widget.page.BookChapterBean; + +import static xyz.fycz.myreader.widget.page.PageLoader.STATUS_PARING; + +/** + * Created by newbiechen on 17-7-1. + * 问题: + * 1. 异常处理没有做好 + */ + +public class LocalPageLoader extends PageLoader { + private static final String TAG = "LocalPageLoader"; + //默认从文件中获取数据的长度 + private final static int BUFFER_SIZE = 512 * 1024; + //没有标题的时候,每个章节的最大长度 + private final static int MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024; + + // "序(章)|前言" + private final static Pattern mPreChapterPattern = Pattern.compile("^(\\s{0,10})((\u5e8f[\u7ae0\u8a00]?)|(\u524d\u8a00)|(\u6954\u5b50))(\\s{0,10})$", Pattern.MULTILINE); + + //正则表达式章节匹配模式 + // "(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节回集卷])(.*)" + private static final String[] CHAPTER_PATTERNS = new String[]{"^(.{0,8})(\u7b2c)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\u7ae0\u8282\u56de\u96c6\u5377])(.{0,30})$", + "^(\\s{0,4})([\\(\u3010\u300a]?(\u5377)?)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\\.:\uff1a\u0020\f\t])(.{0,30})$", + "^(\\s{0,4})([\\(\uff08\u3010\u300a])(.{0,30})([\\)\uff09\u3011\u300b])(\\s{0,2})$", + "^(\\s{0,4})(\u6b63\u6587)(.{0,20})$", + "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$"}; + + //章节解析模式 + private Pattern mChapterPattern = null; + //获取书本的文件 + private File mBookFile; + //编码类型 + private String mCharset; + + private Disposable mChapterDisp = null; + + private ChapterService mChapterService; + + public LocalPageLoader(PageView pageView, Book book, Callback callback) { + super(pageView, book, callback); + this.mChapterService = ChapterService.getInstance(); + } + + + /** + * 未完成的部分: + * 1. 序章的添加 + * 2. 章节存在的书本的虚拟分章效果 + */ + public List loadChapters() throws IOException { + mBookFile = new File(book.getChapterUrl()); + //获取文件编码 + mCharset = FileUtils.getFileEncode(mBookFile.getAbsolutePath()); + List chapters = new ArrayList<>(); + RandomAccessFile bookStream = null; + boolean hasChapter = false; + //获取文件流 + bookStream = new RandomAccessFile(mBookFile, "r"); + //寻找匹配文章标题的正则表达式,判断是否存在章节名 + hasChapter = checkChapterType(bookStream); + //加载章节 + byte[] buffer = new byte[BUFFER_SIZE]; + //获取到的块起始点,在文件中的位置 + long curOffset = 0; + //block的个数 + int blockPos = 0; + //读取的长度 + int length; + + //获取文件中的数据到buffer,直到没有数据为止 + while ((length = bookStream.read(buffer, 0, buffer.length)) > 0) { + ++blockPos; + //如果存在Chapter + if (hasChapter) { + //将数据转换成String + String blockContent = new String(buffer, 0, length, mCharset); + //当前Block下使过的String的指针 + int seekPos = 0; + //进行正则匹配 + Matcher matcher = mChapterPattern.matcher(blockContent); + //如果存在相应章节 + while (matcher.find()) { + //获取匹配到的字符在字符串中的起始位置 + int chapterStart = matcher.start(); + + //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 + //第一种情况一定是序章 第二种情况可能是上一个章节的内容 + if (seekPos == 0 && chapterStart != 0) { + //获取当前章节的内容 + String chapterContent = blockContent.substring(seekPos, chapterStart); + //设置指针偏移 + seekPos += chapterContent.length(); + + //如果当前对整个文件的偏移位置为0的话,那么就是序章 + if (curOffset == 0) { + //创建序章 + BookChapterBean preChapter = new BookChapterBean(); + preChapter.title = "序章"; + preChapter.start = 0; + preChapter.end = chapterContent.getBytes(mCharset).length; //获取String的byte值,作为最终值 + + //如果序章大小大于30才添加进去 + if (preChapter.end - preChapter.start > 30) { + chapters.add(preChapter); + } + + //创建当前章节 + BookChapterBean curChapter = new BookChapterBean(); + curChapter.title = matcher.group(); + curChapter.start = preChapter.end; + chapters.add(curChapter); + } + //否则就block分割之后,上一个章节的剩余内容 + else { + //获取上一章节 + BookChapterBean lastChapter = chapters.get(chapters.size() - 1); + //将当前段落添加上一章去 + lastChapter.end += chapterContent.getBytes(mCharset).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + BookChapterBean curChapter = new BookChapterBean(); + curChapter.title = matcher.group(); + curChapter.start = lastChapter.end; + chapters.add(curChapter); + } + } else { + //是否存在章节 + if (chapters.size() != 0) { + //获取章节内容 + String chapterContent = blockContent.substring(seekPos, matcher.start()); + seekPos += chapterContent.length(); + + //获取上一章节 + BookChapterBean lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + BookChapterBean curChapter = new BookChapterBean(); + curChapter.title = matcher.group(); + curChapter.start = lastChapter.end; + chapters.add(curChapter); + } + //如果章节不存在则创建章节 + else { + BookChapterBean curChapter = new BookChapterBean(); + curChapter.title = matcher.group(); + curChapter.start = 0; + chapters.add(curChapter); + } + } + } + } + //进行本地虚拟分章 + else { + //章节在buffer的偏移量 + int chapterOffset = 0; + //当前剩余可分配的长度 + int strLength = length; + //分章的位置 + int chapterPos = 0; + + while (strLength > 0) { + ++chapterPos; + //是否长度超过一章 + if (strLength > MAX_LENGTH_WITH_NO_CHAPTER) { + //在buffer中一章的终止点 + int end = length; + //寻找换行符作为终止点 + for (int i = chapterOffset + MAX_LENGTH_WITH_NO_CHAPTER; i < length; ++i) { + if (buffer[i] == Charset.BLANK) { + end = i; + break; + } + } + BookChapterBean chapter = new BookChapterBean(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + end; + chapters.add(chapter); + //减去已经被分配的长度 + strLength = strLength - (end - chapterOffset); + //设置偏移的位置 + chapterOffset = end; + } else { + BookChapterBean chapter = new BookChapterBean(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + length; + chapters.add(chapter); + strLength = 0; + } + } + } + + //block的偏移点 + curOffset += length; + + if (hasChapter) { + //设置上一章的结尾 + BookChapterBean lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = curOffset; + } + + //当添加的block太多的时候,执行GC + if (blockPos % 15 == 0) { + System.gc(); + System.runFinalization(); + } + } + + IOUtils.close(bookStream); + System.gc(); + System.runFinalization(); + return chapters; + } + + public void loadChapters(final ResultCallback resultCallback) { + // 通过RxJava异步处理分章事件 + Single.create((SingleOnSubscribe>) e -> { + e.onSuccess(loadChapters()); + }).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + mChapterDisp = d; + } + + @Override + public void onSuccess(List chapters) { + mChapterDisp = null; + isChapterListPrepare = true; + List mChapters = new ArrayList<>(); + int i = 0; + for (BookChapterBean bookChapterBean : chapters) { + Chapter chapter = new Chapter(); + chapter.setBookId(book.getId()); + chapter.setTitle(bookChapterBean.getTitle().replace("\n", "").replaceAll("^\\s+|\\s+$", "")); + chapter.setId(StringHelper.getStringRandom(25)); + String content = getChapterContent(bookChapterBean); + if (StringHelper.isEmpty(content)) { + continue; + } + chapter.setNumber(i++); + mChapterService.saveOrUpdateChapter(chapter, content); + mChapters.add(chapter); + } + if (resultCallback != null) { + resultCallback.onFinish(mChapters, 1); + } + } + + @Override + public void onError(Throwable e) { + resultCallback.onError((Exception) e); + durDhapterError(e.getMessage()); + Log.d(TAG, "file load error:" + e.toString()); + } + }); + + } + + /** + * 从文件中提取一章的内容 + * + * @param chapter + * @return + */ + private String getChapterContent(BookChapterBean chapter) { + RandomAccessFile bookStream = null; + try { + bookStream = new RandomAccessFile(mBookFile, "r"); + bookStream.seek(chapter.start); + int extent = (int) (chapter.end - chapter.start); + byte[] content = new byte[extent]; + bookStream.read(content, 0, extent); + return new String(content, mCharset); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.close(bookStream); + } + return null; + } + + /** + * 1. 检查文件中是否存在章节名 + * 2. 判断文件中使用的章节名类型的正则表达式 + * + * @return 是否存在章节名 + */ + private boolean checkChapterType(RandomAccessFile bookStream) throws IOException { + //首先获取128k的数据 + byte[] buffer = new byte[BUFFER_SIZE / 4]; + int length = bookStream.read(buffer, 0, buffer.length); + //进行章节匹配 + for (String str : CHAPTER_PATTERNS) { + Pattern pattern = Pattern.compile(str, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(new String(buffer, 0, length, mCharset)); + //如果匹配存在,那么就表示当前章节使用这种匹配方式 + if (matcher.find()) { + mChapterPattern = pattern; + //重置指针位置 + bookStream.seek(0); + return true; + } + } + + //重置指针位置 + bookStream.seek(0); + return false; + } + + + @Override + public void closeBook() { + super.closeBook(); + if (mChapterDisp != null) { + mChapterDisp.dispose(); + mChapterDisp = null; + } + } + + @Override + public void refreshChapterList() { + // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。 + mBookFile = new File(book.getChapterUrl()); + + // 判断文件是否已经加载过,并具有缓存 + if (book.getChapterTotalNum() != 0) { + List chapters = mChapterService.findBookAllChapterByBookId(book.getId()); + + isChapterListPrepare = true; + // 目录加载完成,执行回调操作。 + if (!chapters.isEmpty()) { + callback.onCategoryFinish(chapters); + } + // 打开章节 + skipToChapter(book.getHisttoryChapterNum(), book.getLastReadPosition()); + } + } + + + @Override + public String getChapterContent(Chapter chapter) { + File file = new File(APPCONST.BOOK_CACHE_PATH + book.getId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY); + if (!file.exists()) return null; + byte[] contentByte = FileUtils.getBytes(file); + return new String(contentByte, StandardCharsets.UTF_8); + } + + @Override + protected boolean noChapterData(Chapter chapter) { + return !ChapterService.isChapterCached(book.getId(), chapter.getTitle()); + } + + @Override + public void updateChapter() { + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java new file mode 100644 index 0000000..c0953e1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoader.java @@ -0,0 +1,1866 @@ +package xyz.fycz.myreader.widget.page2; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.widget.Toast; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.entity.ReadBookControl; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.model.audio.ReadAloudService; +import xyz.fycz.myreader.util.help.ChapterContentHelp; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.util.utils.ScreenUtils; +import xyz.fycz.myreader.util.utils.StringUtils; +import xyz.fycz.myreader.widget.page2.animation.PageAnimation; + +/** + * 页面加载器 + */ + +public abstract class PageLoader { + private static final String TAG = "PageLoader"; + + // 默认的显示参数配置 + private static final int DEFAULT_MARGIN_HEIGHT = 20; + public static final int DEFAULT_MARGIN_WIDTH = 15; + private static final int DEFAULT_TIP_SIZE = 12; + private static final float MAX_SCROLL_OFFSET = 100; + private static final int TIP_ALPHA = 180; + // 监听器 + Callback callback; + private Context mContext; + Book book; + // 页面显示类 + PageView mPageView; + private List chapterContainers = new ArrayList<>(); + + // 绘制电池的画笔 + private TextPaint mBatteryPaint; + // 绘制提示的画笔(章节名称和时间) + private TextPaint mTipPaint; + private float pageOffset = 0; + // 绘制标题的画笔 + TextPaint mTitlePaint; + // 绘制小说内容的画笔 + TextPaint mTextPaint; + // 绘制结束的画笔 + private TextPaint mTextEndPaint; + // 阅读器的配置选项 + ReadBookControl readBookControl = ReadBookControl.getInstance(); + //缩进 + String indent; + /*****************params**************************/ + // 判断章节列表是否加载完成 + boolean isChapterListPrepare; + private boolean isClose; + // 页面的翻页效果模式 + private PageAnimation.Mode mPageMode; + //书籍绘制区域的宽高 + int mVisibleWidth; + int mVisibleHeight; + //应用的宽高 + int mDisplayWidth; + private int mDisplayHeight; + //间距 + private int mMarginTop; + private int mMarginBottom; + private int mMarginLeft; + private int mMarginRight; + int contentMarginHeight; + private int tipMarginTop; + private int tipMarginBottom; + private int oneSpPx; + //标题的大小 + private int mTitleSize; + //字体的大小 + private int mTextSize; + private int mTextEndSize; + //行间距 + int mTextInterval; + //标题的行间距 + int mTitleInterval; + //段落距离(基于行间距的额外距离) + int mTextPara; + int mTitlePara; + private int textInterval; + private int textPara; + private int titleInterval; + private int titlePara; + private float tipBottomTop; + private float tipBottomBot; + private float tipDistance; + private float tipMarginLeft; + private float displayRightEnd; + private float tipVisibleWidth; + + private boolean hideStatusBar; + private boolean showTimeBattery; + + //电池的百分比 + private int mBatteryLevel; + + // 当前章 + int mCurChapterPos; + private int mCurPagePos; + private int readTextLength; //已读字符数 + private boolean resetReadAloud; //是否重新朗读 + private int readAloudParagraph; //正在朗读章节 + + Bitmap cover; + private int linePos = 0; + private boolean isLastPage = false; + + CompositeDisposable compositeDisposable; + //翻页时间 + private long skipPageTime = 0; + + /*****************************init params*******************************/ + PageLoader(PageView pageView, Book book, Callback callback) { + mPageView = pageView; + this.book = book; + this.callback = callback; + for (int i = 0; i < 3; i++) { + chapterContainers.add(new ChapterContainer()); + } + mContext = pageView.getContext(); + mCurChapterPos = book.getHisttoryChapterNum(); + mCurPagePos = book.getLastReadPosition(); + compositeDisposable = new CompositeDisposable(); + oneSpPx = ScreenUtils.spToPx(1); + // 初始化数据 + initData(); + // 初始化画笔 + initPaint(); + } + + private void initData() { + // 获取配置参数 + hideStatusBar = readBookControl.getHideStatusBar(); + showTimeBattery = hideStatusBar && readBookControl.getShowTimeBattery(); + mPageMode = PageAnimation.Mode.getPageMode(readBookControl.getPageMode()); + // 初始化参数 + indent = StringUtils.repeat(StringUtils.halfToFull(" "), readBookControl.getIndent()); + // 配置文字有关的参数 + setUpTextParams(); + } + + /** + * 屏幕大小变化处理 + */ + void prepareDisplay(int w, int h) { + // 获取PageView的宽高 + mDisplayWidth = w; + mDisplayHeight = h; + + // 设置边距 + mMarginTop = hideStatusBar ? + ScreenUtils.dpToPx(readBookControl.getTipPaddingTop() + readBookControl.getPaddingTop() + DEFAULT_MARGIN_HEIGHT) + : ScreenUtils.dpToPx(readBookControl.getPaddingTop()); + mMarginBottom = ScreenUtils.dpToPx(readBookControl.getTipPaddingBottom() + readBookControl.getPaddingBottom() + DEFAULT_MARGIN_HEIGHT); + mMarginLeft = ScreenUtils.dpToPx(readBookControl.getPaddingLeft()); + mMarginRight = ScreenUtils.dpToPx(readBookControl.getPaddingRight()); + contentMarginHeight = oneSpPx; + tipMarginTop = ScreenUtils.dpToPx(readBookControl.getTipPaddingTop() + DEFAULT_MARGIN_HEIGHT); + tipMarginBottom = ScreenUtils.dpToPx(readBookControl.getTipPaddingBottom() + DEFAULT_MARGIN_HEIGHT); + + Paint.FontMetrics fontMetrics = mTipPaint.getFontMetrics(); + float tipMarginTopHeight = (tipMarginTop + fontMetrics.top - fontMetrics.bottom) / 2; + float tipMarginBottomHeight = (tipMarginBottom + fontMetrics.top - fontMetrics.bottom) / 2; + tipBottomTop = tipMarginTopHeight - fontMetrics.top; + tipBottomBot = mDisplayHeight - fontMetrics.bottom - tipMarginBottomHeight; + tipDistance = ScreenUtils.dpToPx(DEFAULT_MARGIN_WIDTH); + tipMarginLeft = ScreenUtils.dpToPx(readBookControl.getTipPaddingLeft()); + float tipMarginRight = ScreenUtils.dpToPx(readBookControl.getTipPaddingRight()); + displayRightEnd = mDisplayWidth - tipMarginRight; + tipVisibleWidth = mDisplayWidth - tipMarginLeft - tipMarginRight; + + // 获取内容显示位置的大小 + mVisibleWidth = mDisplayWidth - mMarginLeft - mMarginRight; + mVisibleHeight = readBookControl.getHideStatusBar() + ? mDisplayHeight - mMarginTop - mMarginBottom + : mDisplayHeight - mMarginTop - mMarginBottom - mPageView.getStatusBarHeight(); + + // 设置翻页模式 + mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom); + skipToChapter(mCurChapterPos, mCurPagePos); + } + + /** + * 设置与文字相关的参数 + */ + private void setUpTextParams() { + // 文字大小 + mTextSize = ScreenUtils.spToPx(readBookControl.getTextSize()); + mTitleSize = mTextSize + oneSpPx; + mTextEndSize = mTextSize - oneSpPx; + // 行间距(大小为字体的一半) + mTextInterval = (int) (mTextSize * readBookControl.getLineMultiplier() / 2); + mTitleInterval = (int) (mTitleSize * readBookControl.getLineMultiplier() / 2); + // 段落间距(大小为字体的高度) + mTextPara = (int) (mTextSize * readBookControl.getLineMultiplier() * readBookControl.getParagraphSize() / 2); + mTitlePara = (int) (mTitleSize * readBookControl.getLineMultiplier() * readBookControl.getParagraphSize() / 2); + } + + /** + * 初始化画笔 + */ + private void initPaint() { + Typeface typeface; + try { + if (!TextUtils.isEmpty(readBookControl.getFontPath())) { + typeface = Typeface.createFromFile(readBookControl.getFontPath()); + } else { + typeface = Typeface.SANS_SERIF; + } + } catch (Exception e) { + Toast.makeText(mContext, "字体文件未找,到恢复默认字体", Toast.LENGTH_SHORT).show(); + readBookControl.setReadBookFont(null); + typeface = Typeface.SANS_SERIF; + } + // 绘制提示的画笔 + mTipPaint = new TextPaint(); + mTipPaint.setColor(readBookControl.getTextColor()); + mTipPaint.setTextAlign(Paint.Align.LEFT); // 绘制的起始点 + mTipPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE)); // Tip默认的字体大小 + mTipPaint.setTypeface(Typeface.create(typeface, Typeface.NORMAL)); + mTipPaint.setAntiAlias(true); + mTipPaint.setSubpixelText(true); + + // 绘制标题的画笔 + mTitlePaint = new TextPaint(); + mTitlePaint.setColor(readBookControl.getTextColor()); + mTitlePaint.setTextSize(mTitleSize); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mTitlePaint.setLetterSpacing(readBookControl.getTextLetterSpacing()); + } + mTitlePaint.setStyle(Paint.Style.FILL_AND_STROKE); + mTitlePaint.setTypeface(Typeface.create(typeface, Typeface.BOLD)); + mTitlePaint.setTextAlign(Paint.Align.CENTER); + mTitlePaint.setAntiAlias(true); + + // 绘制页面内容的画笔 + mTextPaint = new TextPaint(); + mTextPaint.setColor(readBookControl.getTextColor()); + mTextPaint.setTextSize(mTextSize); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mTextPaint.setLetterSpacing(readBookControl.getTextLetterSpacing()); + } + int bold = readBookControl.getTextBold() ? Typeface.BOLD : Typeface.NORMAL; + mTextPaint.setTypeface(Typeface.create(typeface, bold)); + mTextPaint.setAntiAlias(true); + + // 绘制结束的画笔 + mTextEndPaint = new TextPaint(); + mTextEndPaint.setColor(readBookControl.getTextColor()); + mTextEndPaint.setTextSize(mTextEndSize); + mTextEndPaint.setTypeface(Typeface.create(typeface, Typeface.NORMAL)); + mTextEndPaint.setAntiAlias(true); + mTextEndPaint.setSubpixelText(true); + mTextEndPaint.setTextAlign(Paint.Align.CENTER); + + // 绘制电池的画笔 + mBatteryPaint = new TextPaint(); + mBatteryPaint.setAntiAlias(true); + mBatteryPaint.setDither(true); + mBatteryPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE - 3)); + mBatteryPaint.setTypeface(Typeface.createFromAsset(mContext.getAssets(), "number.ttf")); + + setupTextInterval(); + // 初始化页面样式 + initPageStyle(); + } + + /** + * 设置文字相关参数 + */ + public void setTextSize() { + // 设置文字相关参数 + setUpTextParams(); + initPaint(); + skipToChapter(mCurChapterPos, mCurPagePos); + } + + private void setupTextInterval() { + textInterval = mTextInterval + (int) mTextPaint.getTextSize(); + textPara = mTextPara + (int) mTextPaint.getTextSize(); + titleInterval = mTitleInterval + (int) mTitlePaint.getTextSize(); + titlePara = mTitlePara + (int) mTextPaint.getTextSize(); + } + + /** + * 设置页面样式 + */ + private void initPageStyle() { + mTipPaint.setColor(readBookControl.getTextColor()); + mTitlePaint.setColor(readBookControl.getTextColor()); + mTextPaint.setColor(readBookControl.getTextColor()); + mBatteryPaint.setColor(readBookControl.getTextColor()); + mTextEndPaint.setColor(readBookControl.getTextColor()); + mTipPaint.setAlpha(TIP_ALPHA); + mBatteryPaint.setAlpha(TIP_ALPHA); + mTextEndPaint.setAlpha(TIP_ALPHA); + } + + /** + * 设置翻页动画 + */ + public void setPageMode(PageAnimation.Mode pageMode) { + mPageMode = pageMode; + mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom); + skipToChapter(mCurChapterPos, mCurPagePos); + } + + /** + * 设置内容与屏幕的间距 单位为 px + */ + public void upMargin() { + prepareDisplay(mDisplayWidth, mDisplayHeight); + } + + /** + * 刷新界面 + */ + public void refreshUi() { + initData(); + initPaint(); + mPageView.setPageMode(mPageMode, mMarginTop, mMarginBottom); + skipToChapter(mCurChapterPos, mCurPagePos); + } + + /** + * 跳转到上一章 + */ + public void skipPreChapter() { + if (mCurChapterPos <= 0) { + return; + } + + // 载入上一章。 + mCurChapterPos = mCurChapterPos - 1; + mCurPagePos = 0; + Collections.swap(chapterContainers, 2, 1); + Collections.swap(chapterContainers, 1, 0); + prevChapter().txtChapter = null; + parsePrevChapter(); + + chapterChangeCallback(); + openChapter(mCurPagePos); + pagingEnd(PageAnimation.Direction.NONE); + } + + /** + * 跳转到下一章 + */ + public boolean skipNextChapter() { + if (mCurChapterPos + 1 >= book.getChapterTotalNum()) { + return false; + } + + //载入下一章 + mCurChapterPos = mCurChapterPos + 1; + mCurPagePos = 0; + Collections.swap(chapterContainers, 0, 1); + Collections.swap(chapterContainers, 1, 2); + nextChapter().txtChapter = null; + parseNextChapter(); + + chapterChangeCallback(); + openChapter(mCurPagePos); + pagingEnd(PageAnimation.Direction.NONE); + return true; + } + + /** + * 跳转到指定章节页 + */ + public void skipToChapter(int chapterPos, int pagePos) { + // 设置参数 + mCurChapterPos = chapterPos; + mCurPagePos = pagePos; + + prevChapter().txtChapter = null; + curChapter().txtChapter = null; + nextChapter().txtChapter = null; + + openChapter(pagePos); + } + + /** + * 跳转到指定的页 + */ + public void skipToPage(int pos) { + if (!isChapterListPrepare) { + return; + } + openChapter(pos); + } + + /** + * 翻到上一页 + */ + public void skipToPrePage() { + if ((System.currentTimeMillis() - skipPageTime) > 300) { + mPageView.autoPrevPage(); + skipPageTime = System.currentTimeMillis(); + } + } + + /** + * 翻到下一页 + */ + public void skipToNextPage() { + if ((System.currentTimeMillis() - skipPageTime) > 300) { + mPageView.autoNextPage(); + skipPageTime = System.currentTimeMillis(); + } + } + + /** + * 翻到下一页,无动画 + */ + private void noAnimationToNextPage() { + if (getCurPagePos() < curChapter().txtChapter.getPageSize() - 1) { + skipToPage(getCurPagePos() + 1); + return; + } + skipNextChapter(); + } + + /** + * 更新时间 + */ + public void updateTime() { + if (readBookControl.getHideStatusBar() && readBookControl.getShowTimeBattery()) { + if (mPageMode == PageAnimation.Mode.SCROLL) { + mPageView.drawBackground(0); + } else { + upPage(); + } + mPageView.invalidate(); + } + } + + /** + * 更新电量 + */ + public boolean updateBattery(int level) { + if (mBatteryLevel == level) { + return false; + } + mBatteryLevel = level; + if (readBookControl.getHideStatusBar() && readBookControl.getShowTimeBattery()) { + if (mPageMode == PageAnimation.Mode.SCROLL) { + mPageView.drawBackground(0); + } else if (curChapter().txtChapter != null) { + upPage(); + } + mPageView.invalidate(); + return true; + } + return false; + } + + /** + * 获取当前页的状态 + */ + public TxtChapter.Status getPageStatus() { + return curChapter().txtChapter != null ? curChapter().txtChapter.getStatus() : TxtChapter.Status.LOADING; + } + + /** + * 获取当前页的页码 + */ + int getCurPagePos() { + return mCurPagePos; + } + + /** + * 更新状态 + */ + public void setStatus(TxtChapter.Status status) { + curChapter().txtChapter.setStatus(status); + reSetPage(); + mPageView.invalidate(); + } + + /** + * 加载错误 + */ + void durDhapterError(String msg) { + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + } + if (curChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) return; + curChapter().txtChapter.setStatus(TxtChapter.Status.ERROR); + curChapter().txtChapter.setMsg(msg); + if (mPageMode != PageAnimation.Mode.SCROLL) { + upPage(); + } else { + mPageView.drawPage(0); + } + mPageView.invalidate(); + } + + /** + * @return 当前章节所有内容 + */ + public String getAllContent() { + return getContentStartPage(0); + } + + /** + * @return 本页未读内容 + */ + public String getContent() { + if (curChapter().txtChapter == null) return null; + if (curChapter().txtChapter.getPageSize() == 0) return null; + TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos); + StringBuilder s = new StringBuilder(); + int size = txtPage.size(); + int start = mPageMode == PageAnimation.Mode.SCROLL ? Math.min(Math.max(0, linePos), size - 1) : 0; + for (int i = start; i < size; i++) { + s.append(txtPage.getLine(i)); + } + return s.toString(); + } + + /** + * @return 本章未读内容 + */ + public String getUnReadContent() { + if (curChapter().txtChapter == null) return null; + //if (book.isAudio()) return curChapter().txtChapter.getMsg(); + if (curChapter().txtChapter.getTxtPageList().isEmpty()) return null; + StringBuilder s = new StringBuilder(); + String content = getContent(); + if (content != null) { + s.append(content); + } + content = getContentStartPage(mCurPagePos + 1); + if (content != null) { + s.append(content); + } + readTextLength = mCurPagePos > 0 ? curChapter().txtChapter.getPageLength(mCurPagePos - 1) : 0; + if (mPageMode == PageAnimation.Mode.SCROLL) { + for (int i = 0; i < Math.min(Math.max(0, linePos), curChapter().txtChapter.getPage(mCurPagePos).size() - 1); i++) { + readTextLength += curChapter().txtChapter.getPage(mCurPagePos).getLine(i).length(); + } + } + return s.toString(); + } + + /** + * * @return curPageLength 当前页字数 + */ + public int curPageLength() { + if (curChapter().txtChapter == null) return 0; + if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) return 0; + String str; + int strLength = 0; + TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos); + if (txtPage != null) { + for (int i = txtPage.getTitleLines(); i < txtPage.size(); ++i) { + str = txtPage.getLine(i); + strLength = strLength + str.length(); + } + } + return strLength; + } + + /** + * @param page 开始页数 + * @return 从page页开始的的当前章节所有内容 + */ + private String getContentStartPage(int page) { + if (curChapter().txtChapter == null) return null; + if (curChapter().txtChapter.getTxtPageList().isEmpty()) return null; + StringBuilder s = new StringBuilder(); + if (curChapter().txtChapter.getPageSize() > page) { + for (int i = page; i < curChapter().txtChapter.getPageSize(); i++) { + s.append(curChapter().txtChapter.getPage(i).getContent()); + } + } + return s.toString(); + } + + /** + * @param start 开始朗读字数 + */ + public void readAloudStart(int start) { + start = readTextLength + start; + int x = curChapter().txtChapter.getParagraphIndex(start); + if (readAloudParagraph != x) { + readAloudParagraph = x; + mPageView.drawPage(0); + mPageView.invalidate(); + mPageView.drawPage(-1); + mPageView.drawPage(1); + mPageView.invalidate(); + } + } + + /** + * @param readAloudLength 已朗读字数 + */ + public void readAloudLength(int readAloudLength) { + if (curChapter().txtChapter == null) return; + if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) return; + if (curChapter().txtChapter.getPageLength(mCurPagePos) < 0) return; + if (mPageView.isRunning()) return; + readAloudLength = readTextLength + readAloudLength; + if (readAloudLength >= curChapter().txtChapter.getPageLength(mCurPagePos)) { + resetReadAloud = false; + noAnimationToNextPage(); + mPageView.invalidate(); + } + } + + /** + * 刷新章节列表 + */ + public abstract void refreshChapterList(); + + /** + * 获取章节的文本 + */ + protected abstract String getChapterContent(Chapter chapter) throws Exception; + + /** + * 章节数据是否存在 + */ + protected abstract boolean noChapterData(Chapter chapter); + + /** + * 打开当前章节指定页 + */ + void openChapter(int pagePos) { + mCurPagePos = pagePos; + if (!mPageView.isPrepare()) { + return; + } + + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + reSetPage(); + } else if (curChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) { + reSetPage(); + mPageView.invalidate(); + pagingEnd(PageAnimation.Direction.NONE); + return; + } + + // 如果章节目录没有准备好 + if (!isChapterListPrepare) { + curChapter().txtChapter.setStatus(TxtChapter.Status.LOADING); + reSetPage(); + mPageView.invalidate(); + return; + } + + // 如果获取到的章节目录为空 + if (callback.getChapterList().isEmpty()) { + curChapter().txtChapter.setStatus(TxtChapter.Status.CATEGORY_EMPTY); + reSetPage(); + mPageView.invalidate(); + return; + } + + parseCurChapter(); + resetPageOffset(); + } + + /** + * 重置页面 + */ + private void reSetPage() { + if (mPageMode == PageAnimation.Mode.SCROLL) { + resetPageOffset(); + mPageView.invalidate(); + } else { + upPage(); + } + } + + /** + * 更新页面 + */ + private void upPage() { + if (mPageMode != PageAnimation.Mode.SCROLL) { + mPageView.drawPage(0); + if (mCurPagePos > 0 || curChapter().txtChapter.getPosition() > 0) { + mPageView.drawPage(-1); + } + if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1 || curChapter().txtChapter.getPosition() < callback.getChapterList().size() - 1) { + mPageView.drawPage(1); + } + } + } + + /** + * 翻页完成 + */ + void pagingEnd(PageAnimation.Direction direction) { + if (!isChapterListPrepare) { + return; + } + switch (direction) { + case NEXT: + if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1) { + mCurPagePos = mCurPagePos + 1; + } else if (mCurChapterPos < book.getChapterTotalNum() - 1) { + mCurChapterPos = mCurChapterPos + 1; + mCurPagePos = 0; + Collections.swap(chapterContainers, 0, 1); + Collections.swap(chapterContainers, 1, 2); + nextChapter().txtChapter = null; + parseNextChapter(); + chapterChangeCallback(); + } + if (mPageMode != PageAnimation.Mode.SCROLL) { + mPageView.drawPage(1); + } + break; + case PREV: + if (mCurPagePos > 0) { + mCurPagePos = mCurPagePos - 1; + } else if (mCurChapterPos > 0) { + mCurChapterPos = mCurChapterPos - 1; + mCurPagePos = prevChapter().txtChapter.getPageSize() - 1; + Collections.swap(chapterContainers, 2, 1); + Collections.swap(chapterContainers, 1, 0); + prevChapter().txtChapter = null; + parsePrevChapter(); + chapterChangeCallback(); + } + if (mPageMode != PageAnimation.Mode.SCROLL) { + mPageView.drawPage(-1); + } + break; + } + mPageView.setContentDescription(getContent()); + book.setHisttoryChapterNum(mCurChapterPos); + book.setLastReadPosition(mCurPagePos); + callback.onPageChange(mCurChapterPos, getCurPagePos(), resetReadAloud); + resetReadAloud = true; + } + + /** + * 绘制页面 + * pageOnCur: 位于当前页的位置, 小于0上一页, 0 当前页, 大于0下一页 + */ + synchronized void drawPage(Bitmap bitmap, int pageOnCur) { + TxtChapter txtChapter; + TxtPage txtPage = null; + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + } + if (pageOnCur == 0) { //当前页 + txtChapter = curChapter().txtChapter; + txtPage = txtChapter.getPage(mCurPagePos); + } else if (pageOnCur < 0) { //上一页 + if (mCurPagePos > 0) { + txtChapter = curChapter().txtChapter; + txtPage = txtChapter.getPage(mCurPagePos - 1); + } else { + if (prevChapter().txtChapter == null) { + txtChapter = new TxtChapter(mCurChapterPos + 1); + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("未加载完成"); + } else { + txtChapter = prevChapter().txtChapter; + txtPage = txtChapter.getPage(txtChapter.getPageSize() - 1); + } + } + } else { //下一页 + if (mCurPagePos + 1 < curChapter().txtChapter.getPageSize()) { + txtChapter = curChapter().txtChapter; + txtPage = txtChapter.getPage(mCurPagePos + 1); + } else { + if (mCurChapterPos + 1 >= callback.getChapterList().size()) { + txtChapter = new TxtChapter(mCurChapterPos + 1); + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("没有下一页"); + } else if (nextChapter().txtChapter == null) { + txtChapter = new TxtChapter(mCurChapterPos + 1); + txtChapter.setStatus(TxtChapter.Status.ERROR); + txtChapter.setMsg("未加载完成"); + } else { + txtChapter = nextChapter().txtChapter; + txtPage = txtChapter.getPage(0); + } + } + } + if (bitmap != null) + drawBackground(bitmap, txtChapter, txtPage); + drawContent(bitmap, txtChapter, txtPage); + } + + /** + * 滚动模式绘制背景 + */ + void drawBackground(Canvas canvas) { + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + } + drawBackground(canvas, curChapter().txtChapter, curChapter().txtChapter.getPage(mCurPagePos)); + } + + /** + * 横翻模式绘制背景 + */ + private synchronized void drawBackground(Bitmap bitmap, TxtChapter txtChapter, TxtPage txtPage) { + if (bitmap == null) return; + Canvas canvas = new Canvas(bitmap); + if (!readBookControl.bgIsColor() && !readBookControl.bgBitmapIsNull()) { + Rect mDestRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + canvas.drawBitmap(readBookControl.getBgBitmap(), null, mDestRect, null); + } else { + canvas.drawColor(readBookControl.getBgColor()); + } + drawBackground(canvas, txtChapter, txtPage); + } + + /** + * 绘制背景 + */ + @SuppressLint("DefaultLocale") + private synchronized void drawBackground(final Canvas canvas, TxtChapter txtChapter, TxtPage txtPage) { + if (canvas == null) return; + if (!callback.getChapterList().isEmpty()) { + String title = callback.getChapterList().size() > txtChapter.getPosition() ? callback.getChapterList().get(txtChapter.getPosition()).getTitle() : ""; + title = ChapterContentHelp.getInstance().replaceContent(book.getName(), book.getTag(), title, book.getReplaceEnable()); + String page = (txtChapter.getStatus() != TxtChapter.Status.FINISH || txtPage == null) ? "" + : String.format("%d/%d", txtPage.getPosition() + 1, txtChapter.getPageSize()); + String progress = (txtChapter.getStatus() != TxtChapter.Status.FINISH) ? "" + : getReadProgress(mCurChapterPos, book.getChapterTotalNum(), mCurPagePos, curChapter().txtChapter.getPageSize()); + + float tipBottom; + float tipLeft; + //初始化标题的参数 + //需要注意的是:绘制text的y的起始点是text的基准线的位置,而不是从text的头部的位置 + if (!hideStatusBar) { //显示状态栏 + if (txtChapter.getStatus() != TxtChapter.Status.FINISH) { + if (isChapterListPrepare) { + //绘制标题 + title = TextUtils.ellipsize(title, mTipPaint, tipVisibleWidth, TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, tipMarginLeft, tipBottomBot, mTipPaint); + } + } else { + //绘制总进度 + tipLeft = displayRightEnd - mTipPaint.measureText(progress); + canvas.drawText(progress, tipLeft, tipBottomBot, mTipPaint); + //绘制页码 + tipLeft = tipLeft - tipDistance - mTipPaint.measureText(page); + canvas.drawText(page, tipLeft, tipBottomBot, mTipPaint); + //绘制标题 + title = TextUtils.ellipsize(title, mTipPaint, tipLeft - tipDistance, TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, tipMarginLeft, tipBottomBot, mTipPaint); + } + if (readBookControl.getShowLine()) { + //绘制分隔线 + tipBottom = mDisplayHeight - tipMarginBottom; + canvas.drawRect(tipMarginLeft, tipBottom, displayRightEnd, tipBottom + 2, mTipPaint); + } + } else { //隐藏状态栏 + if (getPageStatus() != TxtChapter.Status.FINISH) { + if (isChapterListPrepare) { + //绘制标题 + title = TextUtils.ellipsize(title, mTipPaint, tipVisibleWidth, TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, tipMarginLeft, tipBottomTop, mTipPaint); + } + } else { + //绘制标题 + float titleTipLength = showTimeBattery ? tipVisibleWidth - mTipPaint.measureText(progress) - tipDistance : tipVisibleWidth; + title = TextUtils.ellipsize(title, mTipPaint, titleTipLength, TextUtils.TruncateAt.END).toString(); + canvas.drawText(title, tipMarginLeft, tipBottomTop, mTipPaint); + // 绘制页码 + canvas.drawText(page, tipMarginLeft, tipBottomBot, mTipPaint); + //绘制总进度 + float progressTipLeft = displayRightEnd - mTipPaint.measureText(progress); + float progressTipBottom = showTimeBattery ? tipBottomTop : tipBottomBot; + canvas.drawText(progress, progressTipLeft, progressTipBottom, mTipPaint); + } + if (readBookControl.getShowLine()) { + //绘制分隔线 + tipBottom = tipMarginTop - 2; + canvas.drawRect(tipMarginLeft, tipBottom, displayRightEnd, tipBottom + 2, mTipPaint); + } + } + } + + int visibleRight = (int) displayRightEnd; + if (hideStatusBar && showTimeBattery) { + //绘制当前时间 + String time = StringUtils.dateConvert(System.currentTimeMillis(), "HH:mm"); + float timeTipLeft = (mDisplayWidth - mTipPaint.measureText(time)) / 2; + canvas.drawText(time, timeTipLeft, tipBottomBot, mTipPaint); + + //绘制电池 + int polarHeight = ScreenUtils.dpToPx(4); + int polarWidth = ScreenUtils.dpToPx(2); + int border = 2; + int outFrameWidth = (int) mBatteryPaint.measureText("0000") + polarWidth; + int outFrameHeight = (int) mBatteryPaint.getTextSize() + oneSpPx; + int visibleBottom = mDisplayHeight - (tipMarginBottom - outFrameHeight) / 2; + + //电极的制作 + int polarLeft = visibleRight - polarWidth; + int polarTop = visibleBottom - (outFrameHeight + polarHeight) / 2; + Rect polar = new Rect(polarLeft, polarTop, visibleRight, polarTop + polarHeight); + + mBatteryPaint.setStyle(Paint.Style.FILL); + canvas.drawRect(polar, mBatteryPaint); + + //外框的制作 + int outFrameLeft = polarLeft - outFrameWidth; + int outFrameTop = visibleBottom - outFrameHeight; + Rect outFrame = new Rect(outFrameLeft, outFrameTop, polarLeft, visibleBottom); + + mBatteryPaint.setStyle(Paint.Style.STROKE); + mBatteryPaint.setStrokeWidth(border); + canvas.drawRect(outFrame, mBatteryPaint); + + //绘制电量 + mBatteryPaint.setStyle(Paint.Style.FILL); + Paint.FontMetrics fontMetrics = mBatteryPaint.getFontMetrics(); + String batteryLevel = String.valueOf(mBatteryLevel); + float batTextLeft = outFrameLeft + (outFrameWidth - mBatteryPaint.measureText(batteryLevel)) / 2; + float batTextBaseLine = visibleBottom - outFrameHeight / 2f - fontMetrics.top / 2 - fontMetrics.bottom / 2; + canvas.drawText(batteryLevel, batTextLeft, batTextBaseLine, mBatteryPaint); + } + } + + + /** + * 绘制内容 + */ + private synchronized void drawContent(Bitmap bitmap, TxtChapter txtChapter, TxtPage txtPage) { + if (bitmap == null) return; + Canvas canvas = new Canvas(bitmap); + if (mPageMode == PageAnimation.Mode.SCROLL) { + bitmap.eraseColor(Color.TRANSPARENT); + } + + Paint.FontMetrics fontMetricsForTitle = mTitlePaint.getFontMetrics(); + Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); + + if (txtChapter.getStatus() != TxtChapter.Status.FINISH) { + //绘制字体 + String tip = getStatusText(txtChapter); + drawErrorMsg(canvas, tip, 0); + } else { + float top = contentMarginHeight - fontMetrics.ascent; + if (mPageMode != PageAnimation.Mode.SCROLL) { + top += readBookControl.getHideStatusBar() ? mMarginTop : mPageView.getStatusBarHeight() + mMarginTop; + } + int ppp = 0;//pzl,文字位置 + //对标题进行绘制 + String str; + int strLength = 0; + boolean isLight; + for (int i = 0; i < txtPage.getTitleLines(); ++i) { + str = txtPage.getLine(i); + strLength = strLength + str.length(); + isLight = ReadAloudService.running && readAloudParagraph == 0; + mTitlePaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor()); + + //进行绘制 + canvas.drawText(str, mDisplayWidth / 2f, top, mTitlePaint); + + //pzl + float leftposition = mDisplayWidth / 2; + float rightposition = 0; + float bottomposition = top + mTitlePaint.getFontMetrics().descent; + float TextHeight = Math.abs(fontMetricsForTitle.ascent) + Math.abs(fontMetricsForTitle.descent); + + if (txtPage.getTxtLists() != null) { + for (TxtChar c : txtPage.getTxtLists().get(i).getCharsData()) { + rightposition = leftposition + c.getCharWidth(); + Point tlp = new Point(); + c.setTopLeftPosition(tlp); + tlp.x = (int) leftposition; + tlp.y = (int) (bottomposition - TextHeight); + + Point blp = new Point(); + c.setBottomLeftPosition(blp); + blp.x = (int) leftposition; + blp.y = (int) bottomposition; + + Point trp = new Point(); + c.setTopRightPosition(trp); + trp.x = (int) rightposition; + trp.y = (int) (bottomposition - TextHeight); + + Point brp = new Point(); + c.setBottomRightPosition(brp); + brp.x = (int) rightposition; + brp.y = (int) bottomposition; + ppp++; + c.setIndex(ppp); + + leftposition = rightposition; + } + } + + //设置尾部间距 + if (i == txtPage.getTitleLines() - 1) { + top += titlePara; + } else { + //行间距 + top += titleInterval; + } + } + + if (txtPage.getLines().isEmpty()) { + return; + } + //对内容进行绘制 + for (int i = txtPage.getTitleLines(); i < txtPage.size(); ++i) { + str = txtPage.getLine(i); + strLength = strLength + str.length(); + int paragraphLength = txtPage.getPosition() == 0 ? strLength : txtChapter.getPageLength(txtPage.getPosition() - 1) + strLength; + isLight = ReadAloudService.running && readAloudParagraph == txtChapter.getParagraphIndex(paragraphLength); + mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor()); + Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint); + if (needScale(str)) { + drawScaledText(canvas, str, width, mTextPaint, top, i, txtPage.getTxtLists()); + } else { + canvas.drawText(str, mMarginLeft, top, mTextPaint); + } + + //记录文字位置 --开始 pzl + float leftposition = mMarginLeft; + if (isFirstLineOfParagraph(str)) { + String blanks = StringUtils.halfToFull(" "); + //canvas.drawText(blanks, x, top, mTextPaint); + float bw = StaticLayout.getDesiredWidth(blanks, mTextPaint); + leftposition += bw; + } + float rightposition = 0; + float bottomposition = top + mTextPaint.getFontMetrics().descent; + float textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent); + + if (txtPage.getTxtLists() != null) { + for (TxtChar c : txtPage.getTxtLists().get(i).getCharsData()) { + rightposition = leftposition + c.getCharWidth(); + Point tlp = new Point(); + c.setTopLeftPosition(tlp); + tlp.x = (int) leftposition; + tlp.y = (int) (bottomposition - textHeight); + + Point blp = new Point(); + c.setBottomLeftPosition(blp); + blp.x = (int) leftposition; + blp.y = (int) bottomposition; + + Point trp = new Point(); + c.setTopRightPosition(trp); + trp.x = (int) rightposition; + trp.y = (int) (bottomposition - textHeight); + + Point brp = new Point(); + c.setBottomRightPosition(brp); + brp.x = (int) rightposition; + brp.y = (int) bottomposition; + + leftposition = rightposition; + + ppp++; + c.setIndex(ppp); + } + } + //记录文字位置 --结束 pzl + + //设置尾部间距 + if (str.endsWith("\n")) { + top += textPara; + } else { + top += textInterval; + } + } + } + } + + public void drawCover(Canvas canvas, float top) { + } + + private int getCoverHeight() { + return cover == null ? 0 : cover.getHeight() + 20; + } + + /** + * 绘制内容-滚动 + */ + void drawContent(final Canvas canvas, float offset) { + + if (offset > MAX_SCROLL_OFFSET) { + offset = MAX_SCROLL_OFFSET; + } else if (offset < 0 - MAX_SCROLL_OFFSET) { + offset = -MAX_SCROLL_OFFSET; + } + + boolean pageChanged = false; + Paint.FontMetrics fontMetricsForTitle = mTitlePaint.getFontMetrics(); + Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); + + final float totalHeight = mVisibleHeight + titleInterval; + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + } + + if (!isLastPage || offset < 0) { + pageOffset += offset; + isLastPage = false; + } + // 首页 + if (pageOffset < 0 && mCurChapterPos == 0 && mCurPagePos == 0) { + pageOffset = 0; + } + + float cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos); + cHeight = cHeight > 0 ? cHeight : mVisibleHeight; + if (offset > 0 && pageOffset > cHeight) { + while (pageOffset > cHeight) { + switchToPageOffset(1); + pageOffset -= cHeight; + cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos); + cHeight = cHeight > 0 ? cHeight : mVisibleHeight; + pageChanged = true; + } + } else if (offset < 0 && pageOffset < 0) { + while (pageOffset < 0) { + switchToPageOffset(-1); + cHeight = getFixedPageHeight(curChapter().txtChapter, mCurPagePos); + cHeight = cHeight > 0 ? cHeight : mVisibleHeight; + pageOffset += cHeight; + pageChanged = true; + } + } + + if (pageChanged) { + chapterChangeCallback(); + pagingEnd(PageAnimation.Direction.NONE); + } + + float top = contentMarginHeight - mTextPaint.ascent() - pageOffset; + + int chapterPos = mCurChapterPos; + int pagePos = mCurPagePos; + boolean isLight; + int ppp = 0;//pzl,文字位置 + + if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) { + String tip = getStatusText(curChapter().txtChapter); + drawErrorMsg(canvas, tip, pageOffset); + top += mVisibleHeight; + chapterPos += 1; + pagePos = 0; + } + String str; + linePos = 0; + boolean linePosSet = false; + boolean bookEnd = false; + float startHeight = -2 * titleInterval; + if (pageOffset < mTextPaint.getTextSize()) { + linePos = 0; + linePosSet = true; + } + while (top < totalHeight) { + TxtChapter chapter = chapterPos == mCurChapterPos ? curChapter().txtChapter : nextChapter().txtChapter; + if (chapter == null || chapterPos - mCurChapterPos > 1) break; + if (chapter.getStatus() != TxtChapter.Status.FINISH) { + String tip = getStatusText(chapter); + drawErrorMsg(canvas, tip, 0 - top); + top += mVisibleHeight; + chapterPos += 1; + pagePos = 0; + continue; + } + if (chapter.getPageSize() == 0) break; + TxtPage page = chapter.getPage(pagePos); + if (page.getLines().isEmpty()) break; + if (top > totalHeight) break; + float topi = top; + int strLength = 0; + isLight = ReadAloudService.running && readAloudParagraph == 0; + mTitlePaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor()); + for (int i = 0; i < page.getTitleLines(); i++) { + if (top > totalHeight) { + break; + } else if (top > startHeight) { + str = page.getLine(i); + strLength = strLength + str.length(); + //进行绘制 + canvas.drawText(str, mDisplayWidth / 2f, top, mTitlePaint); + //pzl + float leftposition = mDisplayWidth / 2f; + float rightPosition = 0; + float bottomPosition = top + mTitlePaint.getFontMetrics().descent; + float TextHeight = Math.abs(fontMetricsForTitle.ascent) + Math.abs(fontMetricsForTitle.descent); + + if (page.getTxtLists() != null) { + for (TxtChar c : page.getTxtLists().get(i).getCharsData()) { + rightPosition = leftposition + c.getCharWidth(); + Point tlp = new Point(); + c.setTopLeftPosition(tlp); + tlp.x = (int) leftposition; + tlp.y = (int) (bottomPosition - TextHeight); + + Point blp = new Point(); + c.setBottomLeftPosition(blp); + blp.x = (int) leftposition; + blp.y = (int) bottomPosition; + + Point trp = new Point(); + c.setTopRightPosition(trp); + trp.x = (int) rightPosition; + trp.y = (int) (bottomPosition - TextHeight); + + Point brp = new Point(); + c.setBottomRightPosition(brp); + brp.x = (int) rightPosition; + brp.y = (int) bottomPosition; + ppp++; + c.setIndex(ppp); + + leftposition = rightPosition; + } + } + //pzl + } + top += (i == page.getTitleLines() - 1) ? titlePara : titleInterval; + if (!linePosSet && chapterPos == mCurChapterPos && top > titlePara) { + linePos = i; + linePosSet = true; + } + } + if (top > totalHeight) break; + // 首页画封面 + if (pagePos == 0 && chapterPos == 0) { + drawCover(canvas, top); + top += getCoverHeight(); + } + if (top > totalHeight) break; + for (int i = page.getTitleLines(); i < page.size(); i++) { + str = page.getLine(i); + strLength = strLength + str.length(); + int paragraphLength = page.getPosition() == 0 ? strLength : chapter.getPageLength(page.getPosition() - 1) + strLength; + isLight = ReadAloudService.running && readAloudParagraph == chapter.getParagraphIndex(paragraphLength); + mTextPaint.setColor(isLight ? mContext.getResources().getColor(R.color.sys_color) : readBookControl.getTextColor()); + if (top > totalHeight) { + break; + } else if (top > startHeight) { + Layout tempLayout = new StaticLayout(str, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + float width = StaticLayout.getDesiredWidth(str, tempLayout.getLineStart(0), tempLayout.getLineEnd(0), mTextPaint); + if (needScale(str)) { + drawScaledText(canvas, str, width, mTextPaint, top, i, page.getTxtLists()); + } else { + canvas.drawText(str, mMarginLeft, top, mTextPaint); + } + //记录文字位置 --开始 pzl + + float leftposition = mMarginLeft; + + if (isFirstLineOfParagraph(str)) { + String blanks = StringUtils.halfToFull(" "); + //canvas.drawText(blanks, x, top, mTextPaint); + float bw = StaticLayout.getDesiredWidth(blanks, mTextPaint); + leftposition += bw; + } + + float rightposition = 0; + float bottomposition = top + mTextPaint.getFontMetrics().descent; + float textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent); + + if (page.getTxtLists() != null) { + for (TxtChar c : page.getTxtLists().get(i).getCharsData()) { + + rightposition = leftposition + c.getCharWidth(); + Point tlp = new Point(); + c.setTopLeftPosition(tlp); + tlp.x = (int) leftposition; + tlp.y = (int) (bottomposition - textHeight); + + Point blp = new Point(); + c.setBottomLeftPosition(blp); + blp.x = (int) leftposition; + blp.y = (int) bottomposition; + + Point trp = new Point(); + c.setTopRightPosition(trp); + trp.x = (int) rightposition; + trp.y = (int) (bottomposition - textHeight); + + Point brp = new Point(); + c.setBottomRightPosition(brp); + brp.x = (int) rightposition; + brp.y = (int) bottomposition; + + leftposition = rightposition; + + ppp++; + c.setIndex(ppp); + } + } + //记录文字位置 --结束 pzl + } + top += str.endsWith("\n") ? textPara : textInterval; + if (!linePosSet && chapterPos == mCurChapterPos && top >= textPara) { + linePos = i; + linePosSet = true; + } + } + if (top > totalHeight) break; + if (pagePos == chapter.getPageSize() - 1) { + String sign = "\u23af \u23af"; + if (chapterPos == book.getChapterTotalNum() - 1) { + bookEnd = pagePos == mCurPagePos; + str = sign + " 所有章节已读完 " + sign; + } else { + str = sign + " 本章完 " + sign; + } + top += textPara; + canvas.drawText(str, mDisplayWidth / 2f, top, mTextEndPaint); + top += textPara * 2; + } + if (top > totalHeight) break; + if (chapter.getPageSize() == 1) { + float pHeight = getFixedPageHeight(chapter, pagePos); + if (top - topi < pHeight) { + top = topi + pHeight; + } + if (top > totalHeight) break; + } + if (pagePos >= chapter.getPageSize() - 1) { + chapterPos += 1; + pagePos = 0; + top += 60; + } else { + pagePos += 1; + } + if (bookEnd && top < mVisibleHeight) { + isLastPage = true; + break; + } + } + } + + void resetPageOffset() { + pageOffset = 0; + linePos = 0; + isLastPage = false; + } + + private void switchToPageOffset(int offset) { + switch (offset) { + case 1: + if (mCurPagePos < curChapter().txtChapter.getPageSize() - 1) { + mCurPagePos = mCurPagePos + 1; + } else if (mCurChapterPos < book.getChapterTotalNum() - 1) { + mCurChapterPos = mCurChapterPos + 1; + Collections.swap(chapterContainers, 0, 1); + Collections.swap(chapterContainers, 1, 2); + nextChapter().txtChapter = null; + mCurPagePos = 0; + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + parseCurChapter(); + } else { + parseNextChapter(); + } + } + break; + case -1: + if (mCurPagePos > 0) { + mCurPagePos = mCurPagePos - 1; + } else if (mCurChapterPos > 0) { + mCurChapterPos = mCurChapterPos - 1; + Collections.swap(chapterContainers, 2, 1); + Collections.swap(chapterContainers, 1, 0); + prevChapter().txtChapter = null; + if (curChapter().txtChapter == null) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + mCurPagePos = 0; + parseCurChapter(); + } else { + mCurPagePos = curChapter().txtChapter.getPageSize() - 1; + parsePrevChapter(); + } + } + break; + default: + break; + } + } + + private float getFixedPageHeight(TxtChapter chapter, int pagePos) { + float height = getPageHeight(chapter, pagePos); + if (height == 0) { + return height; + } + int lastPageIndex = chapter.getPageSize() - 1; + if (pagePos == lastPageIndex) { + height += 60 + textPara * 3; + } + if (lastPageIndex <= 0 && height < mVisibleHeight / 2.0f) { + height = mVisibleHeight / 2.0f; + } + return height; + } + + private float getPageHeight(TxtChapter chapter, int pagePos) { + float height = 0; + if (chapter == null || chapter.getStatus() != TxtChapter.Status.FINISH) { + return height; + } + if (pagePos >= 0 && pagePos < chapter.getPageSize()) { + height = getPageHeight(chapter.getPage(pagePos)); + } + if (chapter.getPosition() == 0 && pagePos == 0) { + height += getCoverHeight(); + } + return height; + } + + private float getPageHeight(TxtPage page) { + if (page.getLines().isEmpty()) + return 0; + float height = 0; + if (page.getTitleLines() > 0) + height += titleInterval * (page.getTitleLines() - 1) + titlePara; + for (int i = page.getTitleLines(); i < page.size(); i++) { + height += page.getLine(i).endsWith("\n") ? textPara : textInterval; + } + return height; + } + + private void drawErrorMsg(Canvas canvas, String msg, float offset) { + Layout tempLayout = new StaticLayout(msg, mTextPaint, mVisibleWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false); + List linesData = new ArrayList<>(); + for (int i = 0; i < tempLayout.getLineCount(); i++) { + linesData.add(msg.substring(tempLayout.getLineStart(i), tempLayout.getLineEnd(i))); + } + float pivotY = (mDisplayHeight - textInterval * linesData.size()) / 3f - offset; + for (String str : linesData) { + float textWidth = mTextPaint.measureText(str); + float pivotX = (mDisplayWidth - textWidth) / 2; + canvas.drawText(str, pivotX, pivotY, mTextPaint); + pivotY += textInterval; + } + } + + /** + * 获取状态文本 + */ + private String getStatusText(TxtChapter chapter) { + String tip = ""; + switch (chapter.getStatus()) { + case LOADING: + tip = mContext.getString(R.string.loading); + break; + case ERROR: + tip = mContext.getString(R.string.load_error_msg, curChapter().txtChapter.getMsg()); + break; + case EMPTY: + tip = mContext.getString(R.string.content_empty); + break; + case CATEGORY_EMPTY: + tip = mContext.getString(R.string.chapter_list_empty); + break; + case CHANGE_SOURCE: + tip = mContext.getString(R.string.on_change_source); + } + return tip; + } + + /** + * 判断是否存在上一页 + */ + boolean hasPrev() { + // 以下情况禁止翻页 + if (canNotTurnPage()) { + return false; + } + if (getPageStatus() == TxtChapter.Status.FINISH) { + // 先查看是否存在上一页 + if (mCurPagePos > 0) { + return true; + } + } + return mCurChapterPos > 0; + } + + /** + * 判断是否下一页存在 + */ + boolean hasNext(int pageOnCur) { + // 以下情况禁止翻页 + if (canNotTurnPage()) { + return false; + } + if (getPageStatus() == TxtChapter.Status.FINISH) { + // 先查看是否存在下一页 + if (mCurPagePos + pageOnCur < curChapter().txtChapter.getPageSize() - 1) { + return true; + } + } + return mCurChapterPos + 1 < book.getChapterTotalNum(); + } + + /** + * 解析当前页数据 + */ + void parseCurChapter() { + if (curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) { + Single.create((SingleOnSubscribe) e -> { + ChapterProvider chapterProvider = new ChapterProvider(this); + TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(mCurChapterPos), mPageView.isPrepare()); + e.onSuccess(txtChapter); + }) + .compose(RxUtils::toSimpleSingle) + .subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onSuccess(TxtChapter txtChapter) { + upTextChapter(txtChapter); + } + + @Override + public void onError(Throwable e) { + if (curChapter().txtChapter == null || curChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) { + curChapter().txtChapter = new TxtChapter(mCurChapterPos); + curChapter().txtChapter.setStatus(TxtChapter.Status.ERROR); + curChapter().txtChapter.setMsg(e.getMessage()); + } + } + }); + } + parsePrevChapter(); + parseNextChapter(); + } + + /** + * 解析上一章数据 + */ + void parsePrevChapter() { + final int prevChapterPos = mCurChapterPos - 1; + if (prevChapterPos < 0) { + prevChapter().txtChapter = null; + return; + } + if (prevChapter().txtChapter == null) + prevChapter().txtChapter = new TxtChapter(prevChapterPos); + if (prevChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) { + return; + } + Single.create((SingleOnSubscribe) e -> { + ChapterProvider chapterProvider = new ChapterProvider(this); + TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(prevChapterPos), mPageView.isPrepare()); + e.onSuccess(txtChapter); + }) + .compose(RxUtils::toSimpleSingle) + .subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onSuccess(TxtChapter txtChapter) { + upTextChapter(txtChapter); + } + + @Override + public void onError(Throwable e) { + if (prevChapter().txtChapter == null || prevChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) { + prevChapter().txtChapter = new TxtChapter(prevChapterPos); + prevChapter().txtChapter.setStatus(TxtChapter.Status.ERROR); + prevChapter().txtChapter.setMsg(e.getMessage()); + } + } + }); + } + + /** + * 解析下一章数据 + */ + void parseNextChapter() { + final int nextChapterPos = mCurChapterPos + 1; + if (nextChapterPos >= callback.getChapterList().size()) { + nextChapter().txtChapter = null; + return; + } + if (nextChapter().txtChapter == null) + nextChapter().txtChapter = new TxtChapter(nextChapterPos); + if (nextChapter().txtChapter.getStatus() == TxtChapter.Status.FINISH) { + return; + } + Single.create((SingleOnSubscribe) e -> { + ChapterProvider chapterProvider = new ChapterProvider(this); + TxtChapter txtChapter = chapterProvider.dealLoadPageList(callback.getChapterList().get(nextChapterPos), mPageView.isPrepare()); + e.onSuccess(txtChapter); + }) + .compose(RxUtils::toSimpleSingle) + .subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onSuccess(TxtChapter txtChapter) { + upTextChapter(txtChapter); + } + + @Override + public void onError(Throwable e) { + if (nextChapter().txtChapter == null || nextChapter().txtChapter.getStatus() != TxtChapter.Status.FINISH) { + nextChapter().txtChapter = new TxtChapter(nextChapterPos); + nextChapter().txtChapter.setStatus(TxtChapter.Status.ERROR); + nextChapter().txtChapter.setMsg(e.getMessage()); + } + } + }); + } + + private void upTextChapter(TxtChapter txtChapter) { + if (txtChapter.getPosition() == mCurChapterPos - 1) { + prevChapter().txtChapter = txtChapter; + if (mPageMode == PageAnimation.Mode.SCROLL) { + mPageView.drawContent(-1); + } else { + mPageView.drawPage(-1); + } + } else if (txtChapter.getPosition() == mCurChapterPos) { + curChapter().txtChapter = txtChapter; + reSetPage(); + chapterChangeCallback(); + pagingEnd(PageAnimation.Direction.NONE); + } else if (txtChapter.getPosition() == mCurChapterPos + 1) { + nextChapter().txtChapter = txtChapter; + if (mPageMode == PageAnimation.Mode.SCROLL) { + mPageView.drawContent(1); + } else { + mPageView.drawPage(1); + } + } + } + + private void drawScaledText(Canvas canvas, String line, float lineWidth, TextPaint paint, float top, int y, List txtLists) { + float x = mMarginLeft; + + if (isFirstLineOfParagraph(line)) { + canvas.drawText(indent, x, top, paint); + float bw = StaticLayout.getDesiredWidth(indent, paint); + x += bw; + line = line.substring(readBookControl.getIndent()); + } + int gapCount = line.length() - 1; + int i = 0; + + TxtLine txtList = new TxtLine();//每一行pzl + txtList.setCharsData(new ArrayList<>());//pzl + + float d = ((mDisplayWidth - (mMarginLeft + mMarginRight)) - lineWidth) / gapCount; + for (; i < line.length(); i++) { + String c = String.valueOf(line.charAt(i)); + float cw = StaticLayout.getDesiredWidth(c, paint); + canvas.drawText(c, x, top, paint); + //pzl + TxtChar txtChar = new TxtChar(); + txtChar.setChardata(line.charAt(i)); + if (i == 0) txtChar.setCharWidth(cw + d / 2); + if (i == gapCount) txtChar.setCharWidth(cw + d / 2); + txtChar.setCharWidth(cw + d); + ;//字宽 + //txtChar.Index = y;//每页每个字的位置 + txtList.getCharsData().add(txtChar); + //pzl + x += cw + d; + } + if (txtLists != null) { + txtLists.set(y, txtList);//pzl + } + } + + //判断是不是d'hou + private boolean isFirstLineOfParagraph(String line) { + return line.length() > 3 && line.charAt(0) == (char) 12288 && line.charAt(1) == (char) 12288; + } + + private boolean needScale(String line) {//判断不是空行 + return line != null && line.length() != 0 && line.charAt(line.length() - 1) != '\n'; + } + + private void chapterChangeCallback() { + if (callback != null) { + readAloudParagraph = -1; + callback.onChapterChange(mCurChapterPos); + callback.onPageCountChange(curChapter().txtChapter != null ? curChapter().txtChapter.getPageSize() : 0); + } + } + /** + * 获取书籍进度 + * + * @param durChapterIndex + * @param chapterAll + * @param durPageIndex + * @param durPageAll + * @return + */ + private static String getReadProgress(int durChapterIndex, int chapterAll, int durPageIndex, int durPageAll) { + DecimalFormat df = new DecimalFormat("0.0%"); + if (chapterAll == 0 || (durPageAll == 0 && durChapterIndex == 0)) { + return "0.0%"; + } else if (durPageAll == 0) { + return df.format((durChapterIndex + 1.0f) / chapterAll); + } + String percent = df.format(durChapterIndex * 1.0f / chapterAll + 1.0f / chapterAll * (durPageIndex + 1) / durPageAll); + if (percent.equals("100.0%") && (durChapterIndex + 1 != chapterAll || durPageIndex + 1 != durPageAll)) { + percent = "99.9%"; + } + return percent; + } + + public abstract void updateChapter(); + + /** + * 根据当前状态,决定是否能够翻页 + */ + private boolean canNotTurnPage() { + return !isChapterListPrepare + || getPageStatus() == TxtChapter.Status.CHANGE_SOURCE; + } + + /** + * 关闭书本 + */ + public void closeBook() { + compositeDisposable.dispose(); + compositeDisposable = null; + + isChapterListPrepare = false; + isClose = true; + + prevChapter().txtChapter = null; + curChapter().txtChapter = null; + nextChapter().txtChapter = null; + } + + public boolean isClose() { + return isClose; + } + + private ChapterContainer prevChapter() { + return chapterContainers.get(0); + } + + ChapterContainer curChapter() { + return chapterContainers.get(1); + } + + private ChapterContainer nextChapter() { + return chapterContainers.get(2); + } + + /*****************************************interface*****************************************/ + + static class ChapterContainer { + TxtChapter txtChapter; + } + + /** + * -------------------- + * TODO 检测获取按压坐标所在位置的字符,没有的话返回null + * -------------------- + * author: huangwei + * 2017年7月4日上午10:23:19 + */ + TxtChar detectPressTxtChar(float down_X2, float down_Y2) { + TxtPage txtPage = curChapter().txtChapter.getPage(mCurPagePos); + if (txtPage == null) return null; + List txtLines = txtPage.getTxtLists(); + if (txtLines == null) return null; + for (TxtLine l : txtLines) { + List txtChars = l.getCharsData(); + if (txtChars != null) { + for (TxtChar c : txtChars) { + Point leftPoint = c.getBottomLeftPosition(); + Point rightPoint = c.getBottomRightPosition(); + if (leftPoint != null && down_Y2 > leftPoint.y) { + break;// 说明是在下一行 + } + if (leftPoint != null && rightPoint != null && down_X2 >= leftPoint.x && down_X2 <= rightPoint.x) { + return c; + } + + } + } + } + return null; + } + + public interface Callback { + List getChapterList(); + + /** + * 作用:章节切换的时候进行回调 + * + * @param pos:切换章节的序号 + */ + void onChapterChange(int pos); + + /** + * 作用:章节目录加载完成时候回调 + * + * @param chapters:返回章节目录 + */ + void onCategoryFinish(List chapters); + + /** + * 作用:章节页码数量改变之后的回调。==> 字体大小的调整,或者是否关闭虚拟按钮功能都会改变页面的数量。 + * + * @param count:页面的数量 + */ + void onPageCountChange(int count); + + /** + * 作用:当页面改变的时候回调 + * + * @param chapterIndex 章节序号 + * @param pageIndex 页数 + * @param resetReadAloud 是否重置朗读 + */ + void onPageChange(int chapterIndex, int pageIndex, boolean resetReadAloud); + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java new file mode 100644 index 0000000..dd7b691 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageLoaderNet.java @@ -0,0 +1,266 @@ +package xyz.fycz.myreader.widget.page2; + +import android.annotation.SuppressLint; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.util.utils.FileUtils; + +/** + * 网络页面加载器 + */ + +public class PageLoaderNet extends PageLoader { + private static final String TAG = "PageLoaderNet"; + private List downloadingChapterList = new ArrayList<>(); + + PageLoaderNet(PageView pageView, Book bookShelfBean, Callback callback) { + super(pageView, bookShelfBean, callback); + } + + @Override + public void refreshChapterList() { + if (!callback.getChapterList().isEmpty()) { + isChapterListPrepare = true; + // 打开章节 + skipToChapter(book.getHisttoryChapterNum(), book.getLastReadPosition()); + } else { + /*WebBookModel.getInstance().getChapterList(book) + .compose(RxUtils::toSimpleSingle) + .subscribe(new MyObserver>() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onNext(List chapterBeanList) { + isChapterListPrepare = true; + // 目录加载完成 + if (!chapterBeanList.isEmpty()) { + BookshelfHelp.delChapterList(book.getNoteUrl()); + callback.onCategoryFinish(chapterBeanList); + } + // 加载并显示当前章节 + skipToChapter(book.getDurChapter(), book.getDurChapterPage()); + } + + @Override + public void onError(Throwable e) { + if (e instanceof WebBook.NoSourceThrowable) { + mPageView.autoChangeSource(); + } else { + durDhapterError(e.getMessage()); + } + } + });*/ + } + } + + + public void changeSourceFinish(Book bookShelfBean) { + if (bookShelfBean == null) { + openChapter(book.getHisttoryChapterNum()); + } else { + this.book = bookShelfBean; + refreshChapterList(); + } + } + + /*@SuppressLint("DefaultLocale") + private synchronized void loadContent(final int chapterIndex) { + if (downloadingChapterList.size() >= 20) return; + if (chapterIndex >= callback.getChapterList().size() + || DownloadingList(listHandle.CHECK, callback.getChapterList().get(chapterIndex).getUrl())) + return; + if (null != book && callback.getChapterList().size() > 0) { + Observable.create((ObservableOnSubscribe) e -> { + if (shouldRequestChapter(chapterIndex)) { + DownloadingList(listHandle.ADD, callback.getChapterList().get(chapterIndex).getUrl()); + e.onNext(chapterIndex); + } + e.onComplete(); + }) + .flatMap(index -> WebBookModel.getInstance().getBookContent(book, callback.getChapterList().get(chapterIndex), null)) + .subscribeOn(scheduler) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @SuppressLint("DefaultLocale") + @Override + public void onNext(BookContentBean bookContentBean) { + DownloadingList(listHandle.REMOVE, bookContentBean.getDurChapterUrl()); + finishContent(bookContentBean.getDurChapterIndex()); + } + + @Override + public void onError(Throwable e) { + DownloadingList(listHandle.REMOVE, callback.getChapterList().get(chapterIndex).getDurChapterUrl()); + if (chapterIndex == book.getDurChapter()) { + if (e instanceof WebBook.NoSourceThrowable) { + mPageView.autoChangeSource(); + } else if (e instanceof VipThrowable) { + callback.vipPop(); + } else { + durDhapterError(e.getMessage()); + } + } + } + }); + } + }*/ + + /** + * 编辑下载列表 + */ + private synchronized boolean DownloadingList(listHandle editType, String value) { + if (editType == listHandle.ADD) { + downloadingChapterList.add(value); + return true; + } else if (editType == listHandle.REMOVE) { + downloadingChapterList.remove(value); + return true; + } else { + return downloadingChapterList.indexOf(value) != -1; + } + } + + /** + * 章节下载完成 + */ + private void finishContent(int chapterIndex) { + if (chapterIndex == mCurChapterPos) { + super.parseCurChapter(); + } + if (chapterIndex == mCurChapterPos - 1) { + super.parsePrevChapter(); + } + if (chapterIndex == mCurChapterPos + 1) { + super.parseNextChapter(); + } + } + + /** + * 刷新当前章节 + */ + @SuppressLint("DefaultLocale") + /*public void refreshDurChapter() { + if (callback.getChapterList().isEmpty()) { + updateChapter(); + return; + } + if (callback.getChapterList().size() - 1 < mCurChapterPos) { + mCurChapterPos = callback.getChapterList().size() - 1; + } + BookshelfHelp.delChapter(BookshelfHelp.getCachePathName(book.getBookInfoBean().getName(), book.getTag()), + mCurChapterPos, callback.getChapterList().get(mCurChapterPos).getDurChapterName()); + skipToChapter(mCurChapterPos, 0); + }*/ + + @Override + protected String getChapterContent(Chapter chapter) { + File file = new File(APPCONST.BOOK_CACHE_PATH + book.getId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY); + if (!file.exists()) return null; + byte[] contentByte = FileUtils.getBytes(file); + return new String(contentByte, StandardCharsets.UTF_8); + //return BookshelfHelp.getChapterCache(book, chapter); + } + + @SuppressLint("DefaultLocale") + @Override + protected boolean noChapterData(Chapter chapter) { + //return !BookshelfHelp.isChapterCached(book.getBookInfoBean().getName(), book.getTag(), chapter, book.isAudio()); + return !ChapterService.isChapterCached(book.getId(), chapter.getTitle()); + } + + /*private boolean shouldRequestChapter(Integer chapterIndex) { + return NetworkUtils.isNetWorkAvailable() && noChapterData(callback.getChapterList().get(chapterIndex)); + }*/ + + // 装载上一章节的内容 + @Override + void parsePrevChapter() { + /*if (mCurChapterPos >= 1) { + loadContent(mCurChapterPos - 1); + }*/ + super.parsePrevChapter(); + } + + // 装载当前章内容。 + @Override + void parseCurChapter() { + /*for (int i = mCurChapterPos; i < Math.min(mCurChapterPos + 5, book.getChapterTotalNum()); i++) { + loadContent(i); + }*/ + super.parseCurChapter(); + } + + // 装载下一章节的内容 + @Override + void parseNextChapter() { + /*for (int i = mCurChapterPos; i < Math.min(mCurChapterPos + 5, book.getChapterTotalNum()); i++) { + loadContent(i); + }*/ + super.parseNextChapter(); + } + + @Override + public void updateChapter() { + /*mPageView.getActivity().toast("目录更新中"); + WebBookModel.getInstance().getChapterList(book) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer>() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onNext(List chapterBeanList) { + isChapterListPrepare = true; + + if (chapterBeanList.size() > callback.getChapterList().size()) { + mPageView.getActivity().toast("更新完成,有新章节"); + callback.onCategoryFinish(chapterBeanList); + } else { + mPageView.getActivity().toast("更新完成,无新章节"); + } + + // 加载并显示当前章节 + skipToChapter(book.getDurChapter(), book.getDurChapterPage()); + } + + @Override + public void onError(Throwable e) { + durDhapterError(e.getMessage()); + } + + @Override + public void onComplete() { + + } + });*/ + } + + @Override + public void closeBook() { + super.closeBook(); + } + + public enum listHandle { + ADD, REMOVE, CHECK + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java new file mode 100644 index 0000000..3099089 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/PageView.java @@ -0,0 +1,713 @@ +package xyz.fycz.myreader.widget.page2; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import com.gyf.immersionbar.ImmersionBar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import xyz.fycz.myreader.entity.ReadBookControl; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.ui.activity.ReadActivity; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.ScreenUtils; +import xyz.fycz.myreader.util.utils.SnackbarUtils; +import xyz.fycz.myreader.widget.page2.animation.CoverPageAnim; +import xyz.fycz.myreader.widget.page2.animation.HorizonPageAnim; +import xyz.fycz.myreader.widget.page2.animation.NonePageAnim; +import xyz.fycz.myreader.widget.page2.animation.PageAnimation; +import xyz.fycz.myreader.widget.page2.animation.ScrollPageAnim; +import xyz.fycz.myreader.widget.page2.animation.SimulationPageAnim; +import xyz.fycz.myreader.widget.page2.animation.SlidePageAnim; + +import static xyz.fycz.myreader.util.utils.ScreenUtils.getDisplayMetrics; + + +/** + * 绘制页面显示内容的类 + */ +public class PageView extends View implements PageAnimation.OnPageChangeListener { + + private ReadActivity activity; + + private int mViewWidth = 0; // 当前View的宽 + private int mViewHeight = 0; // 当前View的高 + private int statusBarHeight = 0; //状态栏高度 + + private boolean actionFromEdge = false; + // 初始化参数 + private ReadBookControl readBookControl = ReadBookControl.getInstance(); + private boolean isPrepare; + // 动画类 + private PageAnimation mPageAnim; + //点击监听 + private TouchListener mTouchListener; + //内容加载器 + private PageLoader mPageLoader; + + //文字选择画笔 + private Paint mTextSelectPaint = null; + //文字选择画笔颜色 + private int TextSelectColor = Color.parseColor("#77fadb08"); + + private Path mSelectTextPath = new Path(); + + //触摸到起始位置 + private int mStartX = 0; + private int mStartY = 0; + // 是否发触了长按事件 + private boolean isLongPress = false; + //第一个选择的文字 + private TxtChar firstSelectTxtChar = null; + //最后选择的一个文字 + private TxtChar lastSelectTxtChar = null; + //选择模式 + private SelectMode selectMode = SelectMode.Normal; + //文本高度 + private float textHeight = 0; + // 唤醒菜单的区域 + private RectF mCenterRect = null; + //是否在移动 + private boolean isMove = false; + //长按的runnable + private Runnable mLongPressRunnable; + //长按时间 + private static final int LONG_PRESS_TIMEOUT = 1000; + //选择的列 + private List mSelectLines = new ArrayList(); + + + public PageView(Context context) { + this(context, null); + } + + public PageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + //初始化画笔 + mTextSelectPaint = new Paint(); + mTextSelectPaint.setAntiAlias(true); + mTextSelectPaint.setTextSize(19); + mTextSelectPaint.setColor(TextSelectColor); + + mLongPressRunnable = () -> { + if (mPageLoader == null) return; + performLongClick(); + if (mStartX > 0 && mStartY > 0) {// 说明还没释放,是长按事件 + isLongPress = true;//长按 + TxtChar p = mPageLoader.detectPressTxtChar(mStartX, mStartY);//找到长按的点 + firstSelectTxtChar = p;//设置开始位置字符 + lastSelectTxtChar = p;//设置结束位置字符 + selectMode = SelectMode.PressSelectText;//设置模式为长按选择 + mTouchListener.onLongPress();//响应长按事件,供上层调用 + } + }; + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + mViewWidth = width; + mViewHeight = height; + + isPrepare = true; + + if (mPageLoader != null) { + mPageLoader.prepareDisplay(width, height); + } + //设置中间区域范围 + mCenterRect = new RectF(mViewWidth / 3f, mViewHeight / 3f, + mViewWidth * 2f / 3, mViewHeight * 2f / 3); + } + + //设置翻页的模式 + void setPageMode(PageAnimation.Mode pageMode, int marginTop, int marginBottom) { + //视图未初始化的时候,禁止调用 + if (mViewWidth == 0 || mViewHeight == 0 || mPageLoader == null) return; + if (!readBookControl.getHideStatusBar()) { + marginTop = marginTop + statusBarHeight; + } + switch (pageMode) { + case COVER: + mPageAnim = new CoverPageAnim(mViewWidth, mViewHeight, this, this); + break; + case SLIDE: + mPageAnim = new SlidePageAnim(mViewWidth, mViewHeight, this, this); + break; + case NONE: + mPageAnim = new NonePageAnim(mViewWidth, mViewHeight, this, this); + break; + case SCROLL: + mPageAnim = new ScrollPageAnim(mViewWidth, mViewHeight, 0, + marginTop, marginBottom, this, this); + break; + default: + mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, this); + } + } + + public ReadActivity getActivity() { + return activity; + } + + public int getStatusBarHeight() { + return statusBarHeight; + } + + public Bitmap getBgBitmap(int pageOnCur) { + if (mPageAnim == null) return null; + return mPageAnim.getBgBitmap(pageOnCur); + } + + public void autoPrevPage() { + if (mPageAnim instanceof ScrollPageAnim) { + ((ScrollPageAnim) mPageAnim).startAnim(PageAnimation.Direction.PREV); + } else { + startHorizonPageAnim(PageAnimation.Direction.PREV); + } + } + + public void autoNextPage() { + if (mPageAnim instanceof ScrollPageAnim) { + ((ScrollPageAnim) mPageAnim).startAnim(PageAnimation.Direction.NEXT); + } else { + startHorizonPageAnim(PageAnimation.Direction.NEXT); + } + } + + private synchronized void startHorizonPageAnim(PageAnimation.Direction direction) { + if (mTouchListener == null) return; + //结束动画 + mPageAnim.abortAnim(); + if (direction == PageAnimation.Direction.NEXT) { + int x = mViewWidth; + int y = mViewHeight; + //初始化动画 + mPageAnim.setStartPoint(x, y); + //设置点击点 + mPageAnim.setTouchPoint(x, y); + //设置方向 + boolean hasNext = hasNextPage(0); + + mPageAnim.setDirection(direction); + if (!hasNext) { + ((HorizonPageAnim) mPageAnim).setNoNext(true); + return; + } + } else if (direction == PageAnimation.Direction.PREV) { + int x = 0; + int y = mViewHeight; + //初始化动画 + mPageAnim.setStartPoint(x, y); + //设置点击点 + mPageAnim.setTouchPoint(x, y); + mPageAnim.setDirection(direction); + //设置方向方向 + boolean hashPrev = hasPrevPage(); + if (!hashPrev) { + ((HorizonPageAnim) mPageAnim).setNoNext(true); + return; + } + } else { + return; + } + ((HorizonPageAnim) mPageAnim).setNoNext(false); + ((HorizonPageAnim) mPageAnim).setCancel(false); + mPageAnim.startAnim(); + } + + public void drawPage(int pageOnCur) { + if (!isPrepare) return; + if (mPageLoader != null) { + mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur); + } + invalidate(); + } + + /** + * 绘制滚动背景 + */ + @Override + public void drawBackground(Canvas canvas) { + if (!isPrepare) return; + if (mPageLoader != null) { + mPageLoader.drawBackground(canvas); + } + } + + /** + * 绘制滚动内容 + */ + @Override + public void drawContent(Canvas canvas, float offset) { + if (!isPrepare) return; + if (mPageLoader != null) { + mPageLoader.drawContent(canvas, offset); + } + } + + /** + * 绘制横翻背景 + */ + public void drawBackground(int pageOnCur) { + if (!isPrepare) return; + if (mPageLoader != null) { + mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur); + } + invalidate(); + } + + /** + * 绘制横翻内容 + * + * @param pageOnCur 相对当前页的位置 + */ + public void drawContent(int pageOnCur) { + if (!isPrepare) return; + if (mPageLoader != null) { + mPageLoader.drawPage(getBgBitmap(pageOnCur), pageOnCur); + } + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mPageAnim instanceof ScrollPageAnim) + super.onDraw(canvas); + //绘制动画 + if (mPageAnim != null) { + mPageAnim.draw(canvas); + } + if (selectMode != SelectMode.Normal && !isRunning() && !isMove) { + DrawSelectText(canvas); + } + } + + private void DrawSelectText(Canvas canvas) { + if (selectMode == SelectMode.PressSelectText) { + drawPressSelectText(canvas); + } else if (selectMode == SelectMode.SelectMoveForward) { + drawMoveSelectText(canvas); + } else if (selectMode == SelectMode.SelectMoveBack) { + drawMoveSelectText(canvas); + } + } + + + private void drawPressSelectText(Canvas canvas) { + if (lastSelectTxtChar != null) {// 找到了选择的字符 + mSelectTextPath.reset(); + mSelectTextPath.moveTo(firstSelectTxtChar.getTopLeftPosition().x, firstSelectTxtChar.getTopLeftPosition().y); + mSelectTextPath.lineTo(firstSelectTxtChar.getTopRightPosition().x, firstSelectTxtChar.getTopRightPosition().y); + mSelectTextPath.lineTo(firstSelectTxtChar.getBottomRightPosition().x, firstSelectTxtChar.getBottomRightPosition().y); + mSelectTextPath.lineTo(firstSelectTxtChar.getBottomLeftPosition().x, firstSelectTxtChar.getBottomLeftPosition().y); + canvas.drawPath(mSelectTextPath, mTextSelectPaint); + } + } + + + public String getSelectStr() { + + if (mSelectLines.size() == 0) { + return String.valueOf(firstSelectTxtChar.getChardata()); + } + StringBuilder sb = new StringBuilder(); + for (TxtLine l : mSelectLines) { + //Log.e("selectline", l.getLineData() + ""); + sb.append(l.getLineData()); + } + + return sb.toString(); + } + + + private void drawMoveSelectText(Canvas canvas) { + if (firstSelectTxtChar == null || lastSelectTxtChar == null) + return; + getSelectData(); + drawSelectLines(canvas); + } + + List mLinseData = null; + + private void getSelectData() { + TxtPage txtPage = mPageLoader.curChapter().txtChapter.getPage(mPageLoader.getCurPagePos()); + if (txtPage != null) { + mLinseData = txtPage.getTxtLists(); + + Boolean Started = false; + Boolean Ended = false; + + mSelectLines.clear(); + + // 找到选择的字符数据,转化为选择的行,然后将行选择背景画出来 + for (TxtLine l : mLinseData) { + + TxtLine selectline = new TxtLine(); + selectline.setCharsData(new ArrayList<>()); + + for (TxtChar c : l.getCharsData()) { + if (!Started) { + if (c.getIndex() == firstSelectTxtChar.getIndex()) { + Started = true; + selectline.getCharsData().add(c); + if (c.getIndex() == lastSelectTxtChar.getIndex()) { + Ended = true; + break; + } + } + } else { + if (c.getIndex() == lastSelectTxtChar.getIndex()) { + Ended = true; + if (!selectline.getCharsData().contains(c)) { + selectline.getCharsData().add(c); + } + break; + } else { + selectline.getCharsData().add(c); + } + } + } + + mSelectLines.add(selectline); + + if (Started && Ended) { + break; + } + } + } + } + + public SelectMode getSelectMode() { + return selectMode; + } + + public void setSelectMode(SelectMode mCurrentMode) { + this.selectMode = mCurrentMode; + } + + private void drawSelectLines(Canvas canvas) { + drawOaleSeletLinesBg(canvas); + } + + public void clearSelect() { + firstSelectTxtChar = null; + lastSelectTxtChar = null; + selectMode = SelectMode.Normal; + mSelectTextPath.reset(); + invalidate(); + + } + + + //根据当前坐标返回文字 + public TxtChar getCurrentTxtChar(float x, float y) { + return mPageLoader.detectPressTxtChar(x, y); + } + + private void drawOaleSeletLinesBg(Canvas canvas) {// 绘制椭圆型的选中背景 + for (TxtLine l : mSelectLines) { + if (l.getCharsData() != null && l.getCharsData().size() > 0) { + + TxtChar fistchar = l.getCharsData().get(0); + TxtChar lastchar = l.getCharsData().get(l.getCharsData().size() - 1); + + float fw = fistchar.getCharWidth(); + float lw = lastchar.getCharWidth(); + + RectF rect = new RectF(fistchar.getTopLeftPosition().x, fistchar.getTopLeftPosition().y, + lastchar.getTopRightPosition().x, lastchar.getBottomRightPosition().y); + + canvas.drawRoundRect(rect, fw / 2, + textHeight / 2, mTextSelectPaint); + } + } + } + + public TxtChar getFirstSelectTxtChar() { + return firstSelectTxtChar; + } + + public void setFirstSelectTxtChar(TxtChar firstSelectTxtChar) { + this.firstSelectTxtChar = firstSelectTxtChar; + } + + public TxtChar getLastSelectTxtChar() { + return lastSelectTxtChar; + } + + public void setLastSelectTxtChar(TxtChar lastSelectTxtChar) { + this.lastSelectTxtChar = lastSelectTxtChar; + } + + @Override + public void computeScroll() { + //进行滑动 + if (mPageAnim != null) { + mPageAnim.scrollAnim(); + } + super.computeScroll(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + if (mPageAnim == null) return true; + if (mPageLoader == null) return true; + + Paint.FontMetrics fontMetrics = mPageLoader.mTextPaint.getFontMetrics(); + textHeight = Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent); + + if (actionFromEdge) { + if (event.getAction() == MotionEvent.ACTION_UP) + actionFromEdge = false; + return true; + } + + int x = (int) event.getX(); + int y = (int) event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mPageAnim.initTouch(x, y); + if (event.getEdgeFlags() != 0 || event.getRawY() < ScreenUtils.dpToPx(5) || event.getRawY() > getDisplayMetrics().heightPixels - ScreenUtils.dpToPx(5)) { + actionFromEdge = true; + return true; + } + mStartX = x; + mStartY = y; + isMove = false; + + // + if (readBookControl.isCanSelectText() && mPageLoader.getPageStatus() == TxtChapter.Status.FINISH) { + postDelayed(mLongPressRunnable, LONG_PRESS_TIMEOUT); + } + + // + isLongPress = false; + mTouchListener.onTouch(); + mPageAnim.onTouchEvent(event); + + selectMode = SelectMode.Normal; + + mTouchListener.onTouchClearCursor(); + + break; + case MotionEvent.ACTION_MOVE: + mPageAnim.initTouch(x, y); + // 判断是否大于最小滑动值。 + int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + if (!isMove) { + isMove = Math.abs(mStartX - event.getX()) > slop || Math.abs(mStartY - event.getY()) > slop; + } + + // 如果滑动了,且不是长按,则进行翻页。 + if (isMove) { + if (readBookControl.isCanSelectText()) { + removeCallbacks(mLongPressRunnable); + } + mPageAnim.onTouchEvent(event); + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mPageAnim.initTouch(x, y); + mPageAnim.setTouchInitFalse(); + if (!isMove) { + if (readBookControl.isCanSelectText()) { + removeCallbacks(mLongPressRunnable); + } + + //是否点击了中间 + if (mCenterRect.contains(x, y)) { + if (firstSelectTxtChar == null) { + if (mTouchListener != null) { + mTouchListener.center(); + } + } else { + if (mSelectTextPath != null) {//长安选择删除选中状态 + if (!isLongPress) { + firstSelectTxtChar = null; + mSelectTextPath.reset(); + invalidate(); + } + } + //清除移动选择状态 + } + return true; + } + + if (!readBookControl.getCanClickTurn()) { + return true; + } + + if (mPageAnim instanceof ScrollPageAnim && readBookControl.disableScrollClickTurn()) { + return true; + } + } + + if (firstSelectTxtChar == null || isMove) {//长安选择删除选中状态 + mPageAnim.onTouchEvent(event); + } else { + if (!isLongPress) { + //释放了 + if (LONG_PRESS_TIMEOUT != 0) { + removeCallbacks(mLongPressRunnable); + } + firstSelectTxtChar = null; + mSelectTextPath.reset(); + invalidate(); + } + } + break; + } + return true; + } + + /** + * 判断是否存在上一页 + */ + private boolean hasPrevPage() { + if (mPageLoader.hasPrev()) { + return true; + } else { + showSnackBar("已经是第一页了"); + return false; + } + } + + /** + * 判断是否下一页存在 + */ + private boolean hasNextPage(int pageOnCur) { + if (mPageLoader.hasNext(pageOnCur)) { + return true; + } else { + showSnackBar("已经是最后一页了"); + return false; + } + } + + public boolean isRunning() { + return mPageAnim != null && mPageAnim.isRunning(); + } + + public boolean isPrepare() { + return isPrepare; + } + + public void setTouchListener(TouchListener mTouchListener) { + this.mTouchListener = mTouchListener; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mPageAnim != null) { + mPageAnim.abortAnim(); + mPageAnim.clear(); + } + + mPageLoader = null; + mPageAnim = null; + } + + @Override + public void resetScroll() { + mPageLoader.resetPageOffset(); + } + + @Override + public boolean hasPrev() { + return PageView.this.hasPrevPage(); + } + + @Override + public boolean hasNext(int pageOnCur) { + return PageView.this.hasNextPage(pageOnCur); + } + + @Override + public void changePage(PageAnimation.Direction direction) { + mPageLoader.pagingEnd(direction); + } + + /** + * 获取 PageLoader + */ + public PageLoader getPageLoader(ReadActivity activity, Book bookShelfBean, PageLoader.Callback callback) { + this.activity = activity; + this.statusBarHeight = ImmersionBar.getStatusBarHeight(activity); + // 判是否已经存在 + if (mPageLoader != null) { + return mPageLoader; + } + // 根据书籍类型,获取具体的加载器 + //if (!Objects.equals(bookShelfBean.getTag(), BookShelfBean.LOCAL_TAG)) { + if (!"本地书籍".equals(bookShelfBean.getType())) { + mPageLoader = new PageLoaderNet(this, bookShelfBean, callback); + } else { + /*String fileSuffix = FileUtils.getFileSuffix(bookShelfBean.getChapterUrl()); + if (fileSuffix.equalsIgnoreCase(FileUtils.SUFFIX_EPUB)) { + mPageLoader = new PageLoaderEpub(this, bookShelfBean, callback); + } else { + mPageLoader = new PageLoaderText(this, bookShelfBean, callback); + }*/ + } + // 判断是否 PageView 已经初始化完成 + if (mViewWidth != 0 || mViewHeight != 0) { + // 初始化 PageLoader 的屏幕大小 + mPageLoader.prepareDisplay(mViewWidth, mViewHeight); + } + + return mPageLoader; + } + + public void autoChangeSource() { + mPageLoader.setStatus(TxtChapter.Status.CHANGE_SOURCE); + //activity.autoChangeSource(); + } + + /** + * 显示tips + * + * @param msg + */ + public void showSnackBar(String msg) { + SnackbarUtils.show(this, msg); + } + public enum SelectMode { + Normal, PressSelectText, SelectMoveForward, SelectMoveBack + } + + public interface TouchListener { + void onTouch(); + + void onTouchClearCursor(); + + void onLongPress(); + + void center(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt new file mode 100644 index 0000000..aa12d38 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChapter.kt @@ -0,0 +1,57 @@ +package xyz.fycz.myreader.widget.page2 + +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * 章节 + */ + +class TxtChapter internal constructor(val position: Int) { + val txtPageList = ArrayList() + val txtPageLengthList = ArrayList() + val paragraphLengthList = ArrayList() + var status = Status.LOADING + var msg: String? = null + + val pageSize: Int + get() = txtPageList.size + + fun addPage(txtPage: TxtPage) { + txtPageList.add(txtPage) + } + + fun getPage(page: Int): TxtPage? { + return if (txtPageList.isNotEmpty()) { + txtPageList[max(0, min(page, txtPageList.size - 1))] + } else null + } + + fun getPageLength(position: Int): Int { + return if (position >= 0 && position < txtPageLengthList.size) { + txtPageLengthList[position] + } else -1 + } + + fun addTxtPageLength(length: Int) { + txtPageLengthList.add(length) + } + + fun addParagraphLength(length: Int) { + paragraphLengthList.add(length) + } + + fun getParagraphIndex(length: Int): Int { + for (i in paragraphLengthList.indices) { + if ((i == 0 || paragraphLengthList[i - 1] < length) && length <= paragraphLengthList[i]) { + return i + } + } + return -1 + } + + enum class Status { + LOADING, FINISH, ERROR, EMPTY, CATEGORY_EMPTY, CHANGE_SOURCE + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt new file mode 100644 index 0000000..f1cc31b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtChar.kt @@ -0,0 +1,26 @@ +package xyz.fycz.myreader.widget.page2 + +import android.graphics.Point + + +class TxtChar { + var chardata: Char = ' '//字符数据 + + var selected: Boolean? = false//当前字符是否被选中 + + //记录文字的左上右上左下右下四个点坐标 + var topLeftPosition: Point? = null//左上 + var topRightPosition: Point? = null//右上 + var bottomLeftPosition: Point? = null//左下 + var bottomRightPosition: Point? = null//右下 + + var charWidth = 0f//字符宽度 + var Index = 0//当前字符位置 + + override fun toString(): String { + return ("ShowChar [chardata=" + chardata + ", Selected=" + selected + ", TopLeftPosition=" + topLeftPosition + + ", TopRightPosition=" + topRightPosition + ", BottomLeftPosition=" + bottomLeftPosition + + ", BottomRightPosition=" + bottomRightPosition + ", charWidth=" + charWidth + ", Index=" + Index + + "]"); + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt new file mode 100644 index 0000000..15c58c8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtLine.kt @@ -0,0 +1,23 @@ +package xyz.fycz.myreader.widget.page2 + +class TxtLine { + + var charsData: List? = null + + fun getLineData(): String { + var linedata = "" + if (charsData == null) return linedata + charsData?.let { + if (it.isEmpty()) return linedata + for (c in it) { + linedata += c.chardata + } + } + return linedata + } + + override fun toString(): String { + return "ShowLine [Linedata=" + getLineData() + "]" + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt new file mode 100644 index 0000000..9723907 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/TxtPage.kt @@ -0,0 +1,44 @@ +package xyz.fycz.myreader.widget.page2 + +import java.util.* + +/** + * 页面 + */ + +class TxtPage(val position: Int) { + var title: String? = null + var titleLines: Int = 0 //当前 lines 中为 title 的行数。 + private val lines = ArrayList() + //存放每个字的位置 + var txtLists: List? = null + + val content: String + get() { + val s = StringBuilder() + for (i in lines.indices) { + s.append(lines[i]) + } + return s.toString() + } + + fun addLine(line: String) { + lines.add(line) + } + + fun addLines(lines: List) { + this.lines.addAll(lines) + } + + fun getLine(i: Int): String { + return lines[i] + } + + fun getLines(): List { + return lines + } + + fun size(): Int { + return lines.size + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java new file mode 100644 index 0000000..59eedca --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/CoverPageAnim.java @@ -0,0 +1,87 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.view.View; + +/** + * 覆盖翻页 + */ + +public class CoverPageAnim extends HorizonPageAnim { + + private Rect mSrcRect, mDestRect; + private GradientDrawable mBackShadowDrawableLR; + + public CoverPageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + mSrcRect = new Rect(0, 0, mViewWidth, mViewHeight); + mDestRect = new Rect(0, 0, mViewWidth, mViewHeight); + int[] mBackShadowColors = new int[]{0x66000000, 0x00000000}; + mBackShadowDrawableLR = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors); + mBackShadowDrawableLR.setGradientType(GradientDrawable.LINEAR_GRADIENT); + } + + @Override + public void drawMove(Canvas canvas) { + int dis; + if (mDirection == Direction.NEXT) { + dis = (int) (mViewWidth - mStartX + mTouchX); + if (dis > mViewWidth) { + dis = mViewWidth; + } + //计算bitmap截取的区域 + mSrcRect.left = mViewWidth - dis; + //计算bitmap在canvas显示的区域 + mDestRect.right = dis; + canvas.drawBitmap(bitmapList.get(2), 0, 0, null); + canvas.drawBitmap(bitmapList.get(1), mSrcRect, mDestRect, null); + addShadow(dis, canvas); + } else { + dis = (int) (mTouchX - mStartX); + if (dis > mViewWidth) { + dis = mViewWidth; + } + mSrcRect.left = mViewWidth - dis; + mDestRect.right = dis; + canvas.drawBitmap(bitmapList.get(1), 0, 0, null); + canvas.drawBitmap(bitmapList.get(0), mSrcRect, mDestRect, null); + addShadow(dis, canvas); + } + } + + //添加阴影 + private void addShadow(int left, Canvas canvas) { + mBackShadowDrawableLR.setBounds(left, 0, left + 30, mScreenHeight); + mBackShadowDrawableLR.draw(canvas); + } + + @Override + public void startAnim() { + int dx; + if (mDirection == Direction.NEXT) { + if (isCancel) { + int dis = (int) ((mViewWidth - mStartX) + mTouchX); + if (dis > mViewWidth) { + dis = mViewWidth; + } + dx = mViewWidth - dis; + } else { + dx = (int) -(mTouchX + (mViewWidth - mStartX)); + } + } else { + if (isCancel) { + dx = (int) -(mTouchX - mStartX); + } else { + dx = (int) (mViewWidth - (mTouchX - mStartX)); + } + } + + //滑动速度保持一致 + int duration = (animationSpeed * Math.abs(dx)) / mViewWidth; + mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); + super.startAnim(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java new file mode 100644 index 0000000..bb0d89f --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/HorizonPageAnim.java @@ -0,0 +1,190 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 横向动画的模板 + */ + +public abstract class HorizonPageAnim extends PageAnimation { + private static final String TAG = "HorizonPageAnim"; + List bitmapList = new ArrayList<>(); + + HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + //创建图片 + for (int i = 0; i < 3; i++) { + bitmapList.add(Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.ARGB_8888)); + } + } + + /** + * 转换页面,在显示下一章的时候,必须首先调用此方法 + */ + @Override + public boolean changePage() { + if (isCancel) return false; + switch (mDirection) { + case NEXT: + Collections.swap(bitmapList, 0, 1); + Collections.swap(bitmapList, 1, 2); + break; + case PREV: + Collections.swap(bitmapList, 1, 2); + Collections.swap(bitmapList, 0, 1); + break; + default: + return false; + } + return true; + } + + public abstract void drawMove(Canvas canvas); + + @Override + public void onTouchEvent(MotionEvent event) { + abortAnim(); + final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + //获取点击位置 + int x = (int) event.getX(); + int y = (int) event.getY(); + //设置触摸点 + setTouchPoint(x, y); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + break; + case MotionEvent.ACTION_MOVE: + //判断是否移动了 + if (!isMove) { + isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop; + } + + if (isMove) { + //判断是否是准备移动的状态(将要移动但是还没有移动) + if (mMoveX == 0 && mMoveY == 0) { + //判断翻得是上一页还是下一页 + if (x - mStartX > 0) { + //上一页的参数配置 + isNext = false; + boolean hasPrev = mListener.hasPrev(); + setDirection(Direction.PREV); + //如果上一页不存在 + if (!hasPrev) { + noNext = true; + return; + } + } else { + //进行下一页的配置 + isNext = true; + //判断是否下一页存在 + boolean hasNext = mListener.hasNext(0); + //如果存在设置动画方向 + setDirection(Direction.NEXT); + + //如果不存在表示没有下一页了 + if (!hasNext) { + noNext = true; + return; + } + } + } else { + //判断是否取消翻页 + isCancel = isNext ? x - mMoveX > 0 : x - mMoveX < 0; + } + + mMoveX = x; + mMoveY = y; + isRunning = true; + mView.invalidate(); + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + isRunning = false; + if (!isMove) { + + if (!readBookControl.getCanClickTurn()) { + return; + } + + isNext = x > mScreenWidth / 2 || readBookControl.getClickAllNext(); + + if (isNext) { + //判断是否下一页存在 + boolean hasNext = mListener.hasNext(0); + //设置动画方向 + if (!hasNext) { + return; + } + setDirection(Direction.NEXT); + } else { + boolean hasPrev = mListener.hasPrev(); + if (!hasPrev) { + return; + } + setDirection(Direction.PREV); + } + } else { + isCancel = Math.abs(mLastX - mStartX) < slop * 3 || isCancel; + } + + // 开启翻页效果 + if (!noNext) { + startAnim(); + } + mView.invalidate(); + break; + } + } + + @Override + public void draw(Canvas canvas) { + if (isRunning && !noNext) { + drawMove(canvas); + } else { + canvas.drawBitmap(getBgBitmap(0), 0, 0, null); + isCancel = true; + } + } + + @Override + public void abortAnim() { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + if (changePage()) { + mListener.changePage(mDirection); + setDirection(PageAnimation.Direction.NONE); + } + movingFinish(); + setTouchPoint(mScroller.getFinalX(), mScroller.getFinalY()); + mView.invalidate(); + } + } + + @Override + public Bitmap getBgBitmap(int pageOnCur) { + if (pageOnCur < 0) { + return bitmapList.get(0); + } else if (pageOnCur > 0) { + return bitmapList.get(2); + } + return bitmapList.get(1); + } + + public void setCancel(boolean cancel) { + isCancel = cancel; + } + + public void setNoNext(boolean noNext) { + this.noNext = noNext; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java new file mode 100644 index 0000000..097d6a7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/NonePageAnim.java @@ -0,0 +1,26 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Canvas; +import android.view.View; + +/** + * 无动画翻页 + */ + +public class NonePageAnim extends HorizonPageAnim { + + public NonePageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + } + + @Override + public void drawMove(Canvas canvas) { + canvas.drawBitmap(bitmapList.get(1), 0, 0, null); + } + + @Override + public void startAnim() { + super.startAnim(); + isRunning = false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java new file mode 100644 index 0000000..d347f89 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/PageAnimation.java @@ -0,0 +1,273 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.LinearInterpolator; +import android.widget.Scroller; + +import androidx.annotation.NonNull; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.entity.ReadBookControl; + + +/** + * 翻页动画抽象类 + */ + +public abstract class PageAnimation { + //动画速度 + static final int animationSpeed = 300; + //正在使用的View + protected View mView; + protected ReadBookControl readBookControl = ReadBookControl.getInstance(); + //滑动装置 + Scroller mScroller; + //监听器 + protected OnPageChangeListener mListener; + //移动方向 + Direction mDirection = Direction.NONE; + + //屏幕的尺寸 + int mScreenWidth; + int mScreenHeight; + int mMarginTop; + //视图的尺寸 + int mViewWidth; + int mViewHeight; + //起始点 + float mStartX; + float mStartY; + //触碰点 + float mTouchX; + float mTouchY; + //上一个触碰点 + float mLastX; + float mLastY; + private boolean isMoving = false; + boolean isRunning = false; + private boolean touchInit = false; + //是否取消翻页 + boolean isCancel = false; + //可以使用 mLast代替 + int mMoveX = 0; + int mMoveY = 0; + //是否移动了 + boolean isMove = false; + //是否翻阅下一页。true表示翻到下一页,false表示上一页。 + boolean isNext = false; + //是否没下一页或者上一页 + boolean noNext = false; + + PageAnimation(int w, int h, View view, OnPageChangeListener listener) { + this(w, h, 0, 0, 0, view, listener); + } + + PageAnimation(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) { + mScreenWidth = w; + mScreenHeight = h; + + //屏幕的间距 + mMarginTop = marginTop; + + mViewWidth = mScreenWidth - marginWidth * 2; + mViewHeight = mScreenHeight - mMarginTop - marginBottom; + + mView = view; + mListener = listener; + + mScroller = new Scroller(mView.getContext(), new LinearInterpolator()); + } + + public Scroller getScroller() { + return mScroller; + } + + public void setStartPoint(float x, float y) { + mStartX = x; + mStartY = y; + + mLastX = mStartX; + mLastY = mStartY; + } + + public void setTouchPoint(float x, float y) { + mLastX = mTouchX; + mLastY = mTouchY; + + mTouchX = x; + mTouchY = y; + } + + public void setTouchInitFalse() { + touchInit = false; + } + + public void initTouch(int x, int y) { + if (!touchInit) { + //移动的点击位置 + mMoveX = 0; + mMoveY = 0; + //是否移动 + isMove = false; + //是否存在下一章 + noNext = false; + //是下一章还是前一章 + isNext = false; + //是否正在执行动画 + isRunning = false; + //取消 + isCancel = false; + //设置起始位置的触摸点 + setStartPoint(x, y); + touchInit = true; + } + } + + public boolean isRunning() { + return isRunning; + } + + void movingFinish() { + isMoving = false; + isRunning = false; + } + + /** + * 开启翻页动画 + */ + public void startAnim() { + isRunning = true; + isMoving = true; + mView.invalidate(); + } + + public void setDirection(Direction direction) { + mDirection = direction; + } + + public void clear() { + mView = null; + } + + /** + * 点击事件的处理 + */ + public abstract void onTouchEvent(MotionEvent event); + + /** + * 绘制图形 + */ + public abstract void draw(Canvas canvas); + + /** + * 滚动动画 + * 必须放在computeScroll()方法中执行 + */ + public void scrollAnim() { + if (mScroller.computeScrollOffset()) { + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + + setTouchPoint(x, y); + + mView.postInvalidate(); + } else if (isMoving) { + if (changePage()) { + mListener.changePage(mDirection); + setDirection(Direction.NONE); + } + movingFinish(); + } + } + + /** + * 取消动画 + */ + public abstract void abortAnim(); + + public abstract boolean changePage(); + + /** + * 获取背景板 + * pageOnCur: 位于当前页的位置, 小于0上一页, 0 当前页, 大于0下一页 + */ + public abstract Bitmap getBgBitmap(int pageOnCur); + + /** + * 翻页模式 + */ + public enum Mode { + COVER(MyApplication.getApplication().getString(R.string.page_mode_COVER)), + SIMULATION(MyApplication.getApplication().getString(R.string.page_mode_SIMULATION)), + SLIDE(MyApplication.getApplication().getString(R.string.page_mode_SLIDE)), + SCROLL(MyApplication.getApplication().getString(R.string.page_mode_SCROLL)), + NONE(MyApplication.getApplication().getString(R.string.page_mode_NONE)); + + private String name; + + Mode(String name) { + this.name = name; + } + + public static Mode getPageMode(int pageMode) { + switch (pageMode) { + case 0: + return COVER; + case 1: + return SIMULATION; + case 2: + return SLIDE; + case 3: + return SCROLL; + case 4: + return NONE; + default: + return COVER; + } + } + + public static String[] getAllPageMode() { + return new String[]{COVER.name, SIMULATION.name, SLIDE.name, SCROLL.name, NONE.name}; + } + + @NonNull + @Override + public String toString() { + return this.name; + } + } + + /** + * 翻页方向 + */ + public enum Direction { + NONE(true), NEXT(true), PREV(true); + + public final boolean isHorizontal; + + Direction(boolean isHorizontal) { + this.isHorizontal = isHorizontal; + } + } + + public interface OnPageChangeListener { + + void resetScroll(); + + boolean hasPrev(); + + boolean hasNext(int pageOnCur); + + void drawContent(Canvas canvas, float offset); + + void drawBackground(Canvas canvas); + + void changePage(Direction direction); + + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java new file mode 100644 index 0000000..bde29de --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/ScrollPageAnim.java @@ -0,0 +1,148 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; + + +public class ScrollPageAnim extends PageAnimation { + private static final String TAG = "ScrollAnimation"; + // 滑动追踪的时间 + private static final int VELOCITY_DURATION = 1000; + private VelocityTracker mVelocity; + // 整个Bitmap的背景显示 + private Bitmap mBgBitmap; + + public ScrollPageAnim(int w, int h, int marginWidth, int marginTop, int marginBottom, View view, OnPageChangeListener listener) { + super(w, h, marginWidth, marginTop, marginBottom, view, listener); + mListener.resetScroll(); + mBgBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.ARGB_8888); + } + + @Override + public void onTouchEvent(MotionEvent event) { + final int slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + int x = (int) event.getX(); + int y = (int) event.getY(); + + // 初始化速度追踪器 + if (mVelocity == null) { + mVelocity = VelocityTracker.obtain(); + } + + mVelocity.addMovement(event); + // 设置触碰点 + setTouchPoint(x, y); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + isMove = false; + isRunning = false; + // 设置起始点 + setStartPoint(x, y); + // 停止动画 + abortAnim(); + break; + case MotionEvent.ACTION_MOVE: + if (!isMove) { + isMove = Math.abs(mStartX - x) > slop || Math.abs(mStartY - y) > slop; + } + mVelocity.computeCurrentVelocity(VELOCITY_DURATION); + isRunning = true; + // 进行刷新 + mView.postInvalidate(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + isRunning = false; + if (!isMove) { + + if (!readBookControl.getCanClickTurn() || readBookControl.disableScrollClickTurn()) { + return; + } + + //是否翻阅下一页。true表示翻到下一页,false表示上一页。 + boolean isNext = x > mScreenWidth / 2 || readBookControl.getClickAllNext(); + if (isNext) { + startAnim(Direction.NEXT); + } else { + startAnim(Direction.PREV); + } + } else { + // 开启动画 + startAnim(); + } + // 删除检测器 + if (mVelocity != null) { + mVelocity.recycle(); + mVelocity = null; + } + break; + } + } + + @Override + public void draw(Canvas canvas) { + //进行布局 + float offset = mLastY - mTouchY; + + //绘制背景 + mListener.drawBackground(canvas); + //绘制内容 + canvas.save(); + //移动位置 + canvas.translate(0, mMarginTop); + //裁剪显示区域 + canvas.clipRect(0, 0, mViewWidth, mViewHeight); + mListener.drawContent(canvas, offset); + canvas.restore(); + } + + @Override + public synchronized void startAnim() { + super.startAnim(); + //惯性滚动 + mScroller.fling(0, (int) mTouchY, 0, (int) mVelocity.getYVelocity(), + 0, 0, -10 * mViewHeight, 10 * mViewHeight); + } + + /** + * 翻页动画 + */ + public void startAnim(Direction direction) { + setStartPoint(0, 0); + setTouchPoint(0, 0); + switch (direction) { + case NEXT: + super.startAnim(); + mScroller.startScroll(0, 0, 0, -mViewHeight + 300, animationSpeed); + break; + case PREV: + super.startAnim(); + mScroller.startScroll(0, 0, 0, mViewHeight - 300, animationSpeed); + break; + } + } + + @Override + public void abortAnim() { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + isRunning = false; + } + } + + @Override + public boolean changePage() { + return false; + } + + @Override + public Bitmap getBgBitmap(int pageOnCur) { + return mBgBitmap; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java new file mode 100644 index 0000000..3cb3a03 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SimulationPageAnim.java @@ -0,0 +1,569 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.Region; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.view.View; + +/** + * 仿真翻页 + */ +public class SimulationPageAnim extends HorizonPageAnim { + private static final String TAG = "SimulationPageAnim"; + + private int mCornerX = 1; // 拖拽点对应的页脚 + private int mCornerY = 1; + private Path mPath0; + private Path mPath1; + + private PointF mBezierStart1 = new PointF(); // 贝塞尔曲线起始点 + private PointF mBezierControl1 = new PointF(); // 贝塞尔曲线控制点 + private PointF mBezierVertex1 = new PointF(); // 贝塞尔曲线顶点 + private PointF mBezierEnd1 = new PointF(); // 贝塞尔曲线结束点 + + private PointF mBezierStart2 = new PointF(); // 另一条贝塞尔曲线 + private PointF mBezierControl2 = new PointF(); + private PointF mBezierVertex2 = new PointF(); + private PointF mBezierEnd2 = new PointF(); + + private float mMiddleX; + private float mMiddleY; + private float mDegrees; + private float mTouchToCornerDis; + private ColorMatrixColorFilter mColorMatrixFilter; + private Matrix mMatrix; + private float[] mMatrixArray = {0, 0, 0, 0, 0, 0, 0, 0, 1.0f}; + + private boolean mIsRT_LB; // 是否属于右上左下 + private float mMaxLength; + private int[] mBackShadowColors;// 背面颜色组 + private int[] mFrontShadowColors;// 前面颜色组 + private GradientDrawable mBackShadowDrawableLR; // 有阴影的GradientDrawable + private GradientDrawable mBackShadowDrawableRL; + private GradientDrawable mFolderShadowDrawableLR; + private GradientDrawable mFolderShadowDrawableRL; + + private GradientDrawable mFrontShadowDrawableHBT; + private GradientDrawable mFrontShadowDrawableHTB; + private GradientDrawable mFrontShadowDrawableVLR; + private GradientDrawable mFrontShadowDrawableVRL; + + private Paint mPaint; + + + public SimulationPageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + mPath0 = new Path(); + mPath1 = new Path(); + mMaxLength = (float) Math.hypot(mScreenWidth, mScreenHeight); + mPaint = new Paint(); + + mPaint.setStyle(Paint.Style.FILL); + + createDrawable(); + + ColorMatrix cm = new ColorMatrix();//设置颜色数组 + float[] array = {1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0}; + cm.set(array); + mColorMatrixFilter = new ColorMatrixColorFilter(cm); + mMatrix = new Matrix(); + + mTouchX = 0.01f; // 不让x,y为0,否则在点计算时会有问题 + mTouchY = 0.01f; + } + + @Override + public void setStartPoint(float x, float y) { + super.setStartPoint(x, y); + calcCornerXY(x, y); + } + + @Override + public void setTouchPoint(float x, float y) { + super.setTouchPoint(x, y); + //触摸y中间位置吧y变成屏幕高度 + if ((mStartY > mScreenHeight / 3.0 && mStartY < mScreenHeight * 2 / 3.0) || mDirection.equals(Direction.PREV)) { + mTouchY = mScreenHeight; + } + + if (mStartY > mScreenHeight / 3.0 && mStartY < mScreenHeight / 2.0 && mDirection.equals(Direction.NEXT)) { + mTouchY = 1; + } + } + + @Override + public void setDirection(Direction direction) { + super.setDirection(direction); + + switch (direction) { + case PREV: + //上一页滑动不出现对角 + if (mStartX > mScreenWidth / 2.0) { + calcCornerXY(mStartX, mScreenHeight); + } else { + calcCornerXY(mScreenWidth - mStartX, mScreenHeight); + } + break; + case NEXT: + if (mScreenWidth / 2.0 > mStartX) { + calcCornerXY(mScreenWidth - mStartX, mStartY); + } + break; + } + } + + @Override + public void startAnim() { + int dx, dy; + // dx 水平方向滑动的距离,负值会使滚动向左滚动 + // dy 垂直方向滑动的距离,负值会使滚动向上滚动 + if (isCancel) { + + if (mCornerX > 0 && mDirection.equals(Direction.NEXT)) { + dx = (int) (mScreenWidth - mTouchX); + } else { + dx = -(int) mTouchX; + } + + if (!mDirection.equals(Direction.NEXT)) { + dx = (int) -(mScreenWidth + mTouchX); + } + + if (mCornerY > 0) { + dy = (int) (mScreenHeight - mTouchY); + } else { + dy = -(int) mTouchY; // 防止mTouchY最终变为0 + } + } else { + if (mCornerX > 0 && mDirection.equals(Direction.NEXT)) { + dx = -(int) (mScreenWidth + mTouchX); + } else { + dx = (int) (mScreenWidth - mTouchX + mScreenWidth); + } + if (mCornerY > 0) { + dy = (int) (mScreenHeight - mTouchY); + } else { + dy = (int) (1 - mTouchY); // 防止mTouchY最终变为0 + } + } + mScroller.startScroll((int) mTouchX, (int) mTouchY, dx, dy, animationSpeed); + super.startAnim(); + } + + @Override + public void drawMove(Canvas canvas) { + if (mDirection == Direction.NEXT) { + calcPoints(); + drawCurrentPageArea(canvas, bitmapList.get(1));//绘制翻页时的正面页 + drawNextPageAreaAndShadow(canvas, bitmapList.get(2)); + drawCurrentPageShadow(canvas); + drawCurrentBackArea(canvas, bitmapList.get(1)); + } else { + calcPoints(); + drawCurrentPageArea(canvas, bitmapList.get(0)); + drawNextPageAreaAndShadow(canvas, bitmapList.get(1)); + drawCurrentPageShadow(canvas); + drawCurrentBackArea(canvas, bitmapList.get(0)); + } + } + + /** + * 创建阴影的GradientDrawable + */ + private void createDrawable() { + int[] color = {0x333333, 0xb0333333}; + mFolderShadowDrawableRL = new GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, color); + mFolderShadowDrawableRL + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mFolderShadowDrawableLR = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, color); + mFolderShadowDrawableLR + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mBackShadowColors = new int[]{0xff111111, 0x111111}; + mBackShadowDrawableRL = new GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, mBackShadowColors); + mBackShadowDrawableRL.setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mBackShadowDrawableLR = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors); + mBackShadowDrawableLR.setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mFrontShadowColors = new int[]{0x80111111, 0x111111}; + mFrontShadowDrawableVLR = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, mFrontShadowColors); + mFrontShadowDrawableVLR + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + mFrontShadowDrawableVRL = new GradientDrawable( + GradientDrawable.Orientation.RIGHT_LEFT, mFrontShadowColors); + mFrontShadowDrawableVRL + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mFrontShadowDrawableHTB = new GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, mFrontShadowColors); + mFrontShadowDrawableHTB + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + + mFrontShadowDrawableHBT = new GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, mFrontShadowColors); + mFrontShadowDrawableHBT + .setGradientType(GradientDrawable.LINEAR_GRADIENT); + } + + /** + * 绘制翻起页背面 + */ + private void drawCurrentBackArea(Canvas canvas, Bitmap bitmap) { + int i = (int) (mBezierStart1.x + mBezierControl1.x) / 2; + float f1 = Math.abs(i - mBezierControl1.x); + int i1 = (int) (mBezierStart2.y + mBezierControl2.y) / 2; + float f2 = Math.abs(i1 - mBezierControl2.y); + float f3 = Math.min(f1, f2); + mPath1.reset(); + mPath1.moveTo(mBezierVertex2.x, mBezierVertex2.y); + mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y); + mPath1.lineTo(mBezierEnd1.x, mBezierEnd1.y); + mPath1.lineTo(mTouchX, mTouchY); + mPath1.lineTo(mBezierEnd2.x, mBezierEnd2.y); + mPath1.close(); + GradientDrawable mFolderShadowDrawable; + int left; + int right; + if (mIsRT_LB) { + left = (int) (mBezierStart1.x - 1); + right = (int) (mBezierStart1.x + f3 + 1); + mFolderShadowDrawable = mFolderShadowDrawableLR; + } else { + left = (int) (mBezierStart1.x - f3 - 1); + right = (int) (mBezierStart1.x + 1); + mFolderShadowDrawable = mFolderShadowDrawableRL; + } + canvas.save(); + try { + canvas.clipPath(mPath0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipPath(mPath1); + } else { + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } + } catch (Exception ignored) { + } + + mPaint.setColorFilter(mColorMatrixFilter); + + float dis = (float) Math.hypot(mCornerX - mBezierControl1.x, + mBezierControl2.y - mCornerY); + float f8 = (mCornerX - mBezierControl1.x) / dis; + float f9 = (mBezierControl2.y - mCornerY) / dis; + mMatrixArray[0] = 1 - 2 * f9 * f9; + mMatrixArray[1] = 2 * f8 * f9; + mMatrixArray[3] = mMatrixArray[1]; + mMatrixArray[4] = 1 - 2 * f8 * f8; + mMatrix.reset(); + mMatrix.setValues(mMatrixArray); + mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y); + mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y); + canvas.drawBitmap(bitmap, mMatrix, mPaint); + + mPaint.setColorFilter(null); + + canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y); + mFolderShadowDrawable.setBounds( + left, (int) mBezierStart1.y, + right, (int) (mBezierStart1.y + mMaxLength) + ); + mFolderShadowDrawable.draw(canvas); + canvas.restore(); + } + + /** + * 绘制翻起页的阴影 + */ + private void drawCurrentPageShadow(Canvas canvas) { + double degree; + if (mIsRT_LB) { + degree = Math.PI / 4 - Math.atan2(mBezierControl1.y - mTouchY, mTouchX - mBezierControl1.x); + } else { + degree = Math.PI / 4 - Math.atan2(mTouchY - mBezierControl1.y, mTouchX - mBezierControl1.x); + } + // 翻起页阴影顶点与touch点的距离 + double d1 = (float) 25 * 1.414 * Math.cos(degree); + double d2 = (float) 25 * 1.414 * Math.sin(degree); + float x = (float) (mTouchX + d1); + float y; + if (mIsRT_LB) { + y = (float) (mTouchY + d2); + } else { + y = (float) (mTouchY - d2); + } + mPath1.reset(); + mPath1.moveTo(x, y); + mPath1.lineTo(mTouchX, mTouchY); + mPath1.lineTo(mBezierControl1.x, mBezierControl1.y); + mPath1.lineTo(mBezierStart1.x, mBezierStart1.y); + mPath1.close(); + canvas.save(); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(mPath0); + } else { + canvas.clipPath(mPath0, Region.Op.XOR); + } + + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception ignored) { + } + + int leftx; + int rightx; + GradientDrawable mCurrentPageShadow; + if (mIsRT_LB) { + leftx = (int) (mBezierControl1.x); + rightx = (int) mBezierControl1.x + 25; + mCurrentPageShadow = mFrontShadowDrawableVLR; + } else { + leftx = (int) (mBezierControl1.x - 25); + rightx = (int) mBezierControl1.x + 1; + mCurrentPageShadow = mFrontShadowDrawableVRL; + } + + float rotateDegrees; + rotateDegrees = (float) Math.toDegrees(Math.atan2(mTouchX - mBezierControl1.x, mBezierControl1.y - mTouchY)); + canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y); + mCurrentPageShadow.setBounds( + leftx, (int) (mBezierControl1.y - mMaxLength), + rightx, (int) (mBezierControl1.y)); + mCurrentPageShadow.draw(canvas); + canvas.restore(); + + mPath1.reset(); + mPath1.moveTo(x, y); + mPath1.lineTo(mTouchX, mTouchY); + mPath1.lineTo(mBezierControl2.x, mBezierControl2.y); + mPath1.lineTo(mBezierStart2.x, mBezierStart2.y); + mPath1.close(); + canvas.save(); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(mPath0); + } else { + canvas.clipPath(mPath0, Region.Op.XOR); + } + + canvas.clipPath(mPath1); + } catch (Exception ignored) { + } + + if (mIsRT_LB) { + leftx = (int) (mBezierControl2.y); + rightx = (int) (mBezierControl2.y + 25); + mCurrentPageShadow = mFrontShadowDrawableHTB; + } else { + leftx = (int) (mBezierControl2.y - 25); + rightx = (int) (mBezierControl2.y + 1); + mCurrentPageShadow = mFrontShadowDrawableHBT; + } + rotateDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl2.y - mTouchY, mBezierControl2.x - mTouchX)); + canvas.rotate(rotateDegrees, mBezierControl2.x, mBezierControl2.y); + float temp; + if (mBezierControl2.y < 0) temp = mBezierControl2.y - mScreenHeight; + else temp = mBezierControl2.y; + + int hmg = (int) Math.hypot(mBezierControl2.x, temp); + if (hmg > mMaxLength) + mCurrentPageShadow.setBounds( + (int) (mBezierControl2.x - 25) - hmg, leftx, + (int) (mBezierControl2.x + mMaxLength) - hmg, rightx + ); + else + mCurrentPageShadow.setBounds( + (int) (mBezierControl2.x - mMaxLength), leftx, + (int) (mBezierControl2.x), rightx); + + mCurrentPageShadow.draw(canvas); + canvas.restore(); + } + + private void drawNextPageAreaAndShadow(Canvas canvas, Bitmap bitmap) { + mPath1.reset(); + mPath1.moveTo(mBezierStart1.x, mBezierStart1.y); + mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y); + mPath1.lineTo(mBezierVertex2.x, mBezierVertex2.y); + mPath1.lineTo(mBezierStart2.x, mBezierStart2.y); + mPath1.lineTo(mCornerX, mCornerY); + mPath1.close(); + + mDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl1.x - mCornerX, + mBezierControl2.y - mCornerY)); + int leftx; + int rightx; + GradientDrawable mBackShadowDrawable; + if (mIsRT_LB) { //左下及右上 + leftx = (int) (mBezierStart1.x); + rightx = (int) (mBezierStart1.x + mTouchToCornerDis / 4); + mBackShadowDrawable = mBackShadowDrawableLR; + } else { + leftx = (int) (mBezierStart1.x - mTouchToCornerDis / 4); + rightx = (int) mBezierStart1.x; + mBackShadowDrawable = mBackShadowDrawableRL; + } + canvas.save(); + try { + canvas.clipPath(mPath0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipPath(mPath1); + } else { + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } + //canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception ignored) { + } + + canvas.drawBitmap(bitmap, 0, 0, null); + canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y); + mBackShadowDrawable.setBounds( + leftx, (int) mBezierStart1.y, + rightx, (int) (mMaxLength + mBezierStart1.y));//左上及右下角的xy坐标值,构成一个矩形 + mBackShadowDrawable.draw(canvas); + canvas.restore(); + } + + private void drawCurrentPageArea(Canvas canvas, Bitmap bitmap) { + mPath0.reset(); + mPath0.moveTo(mBezierStart1.x, mBezierStart1.y); + mPath0.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, mBezierEnd1.y); + mPath0.lineTo(mTouchX, mTouchY); + mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y); + mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y); + mPath0.lineTo(mCornerX, mCornerY); + mPath0.close(); + + canvas.save(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + canvas.clipOutPath(mPath0); + } else { + canvas.clipPath(mPath0, Region.Op.XOR); + } + canvas.drawBitmap(bitmap, 0, 0, null); + try { + canvas.restore(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 计算拖拽点对应的拖拽脚 + */ + private void calcCornerXY(float x, float y) { + if (x <= mScreenWidth / 2.0) { + mCornerX = 0; + } else { + mCornerX = mScreenWidth; + } + if (y <= mScreenHeight / 2.0) { + mCornerY = 0; + } else { + mCornerY = mScreenHeight; + } + + mIsRT_LB = (mCornerX == 0 && mCornerY == mScreenHeight) + || (mCornerX == mScreenWidth && mCornerY == 0); + + } + + private void calcPoints() { + mMiddleX = (mTouchX + mCornerX) / 2; + mMiddleY = (mTouchY + mCornerY) / 2; + mBezierControl1.x = + mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX); + mBezierControl1.y = mCornerY; + + mBezierControl2.x = mCornerX; + if (mCornerY - mMiddleY == 0) { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f; + } else { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) + * (mCornerX - mMiddleX) / (mCornerY - mMiddleY); + } + mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2; + mBezierStart1.y = mCornerY; + + // 固定左边上下两个点 + if (mTouchX > 0 && mTouchX < mScreenWidth) { + if (mBezierStart1.x < 0 || mBezierStart1.x > mScreenWidth) { + if (mBezierStart1.x < 0) + mBezierStart1.x = mScreenWidth - mBezierStart1.x; + + float f1 = Math.abs(mCornerX - mTouchX); + float f2 = mScreenWidth * f1 / mBezierStart1.x; + mTouchX = Math.abs(mCornerX - f2); + + float f3 = Math.abs(mCornerX - mTouchX) * Math.abs(mCornerY - mTouchY) / f1; + mTouchY = Math.abs(mCornerY - f3); + + mMiddleX = (mTouchX + mCornerX) / 2; + mMiddleY = (mTouchY + mCornerY) / 2; + + mBezierControl1.x = + mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX); + mBezierControl1.y = mCornerY; + + mBezierControl2.x = mCornerX; + float f5 = mCornerY - mMiddleY; + if (f5 == 0) { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) + * (mCornerX - mMiddleX) / 0.1f; + } else { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) + * (mCornerX - mMiddleX) / (mCornerY - mMiddleY); + } + + mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2; + } + } + mBezierStart2.x = mCornerX; + mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2; + + mTouchToCornerDis = (float) Math.hypot((mTouchX - mCornerX), (mTouchY - mCornerY)); + + mBezierEnd1 = + getCross(new PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1, mBezierStart2); + mBezierEnd2 = + getCross(new PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1, mBezierStart2); + + mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4; + mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4; + mBezierVertex2.x = (mBezierStart2.x + 2 * mBezierControl2.x + mBezierEnd2.x) / 4; + mBezierVertex2.y = (2 * mBezierControl2.y + mBezierStart2.y + mBezierEnd2.y) / 4; + } + + /** + * 求解直线P1P2和直线P3P4的交点坐标 + */ + private PointF getCross(PointF P1, PointF P2, PointF P3, PointF P4) { + PointF CrossP = new PointF(); + // 二元函数通式: y=ax+b + float a1 = (P2.y - P1.y) / (P2.x - P1.x); + float b1 = ((P1.x * P2.y) - (P2.x * P1.y)) / (P1.x - P2.x); + + float a2 = (P4.y - P3.y) / (P4.x - P3.x); + float b2 = ((P3.x * P4.y) - (P4.x * P3.y)) / (P3.x - P4.x); + CrossP.x = (b2 - b1) / (a1 - a2); + CrossP.y = a1 * CrossP.x + b1; + return CrossP; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java new file mode 100644 index 0000000..fba5a4e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page2/animation/SlidePageAnim.java @@ -0,0 +1,85 @@ +package xyz.fycz.myreader.widget.page2.animation; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +/** + * 滑动翻页 + */ + +public class SlidePageAnim extends HorizonPageAnim { + private Rect mSrcRect, mDestRect, mNextSrcRect, mNextDestRect; + + public SlidePageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + mSrcRect = new Rect(0, 0, mViewWidth, mViewHeight); + mDestRect = new Rect(0, 0, mViewWidth, mViewHeight); + mNextSrcRect = new Rect(0, 0, mViewWidth, mViewHeight); + mNextDestRect = new Rect(0, 0, mViewWidth, mViewHeight); + } + + @Override + public void drawMove(Canvas canvas) { + int dis; + if (mDirection == Direction.NEXT) {//左半边的剩余区域 + dis = (int) (mScreenWidth - mStartX + mTouchX); + if (dis > mScreenWidth) { + dis = mScreenWidth; + } + //计算bitmap截取的区域 + mSrcRect.left = mScreenWidth - dis; + //计算bitmap在canvas显示的区域 + mDestRect.right = dis; + //计算下一页截取的区域 + mNextSrcRect.right = mScreenWidth - dis; + //计算下一页在canvas显示的区域 + mNextDestRect.left = dis; + + canvas.drawBitmap(bitmapList.get(2), mNextSrcRect, mNextDestRect, null); + canvas.drawBitmap(bitmapList.get(1), mSrcRect, mDestRect, null); + } else { + dis = (int) (mTouchX - mStartX); + if (dis < 0) { + dis = 0; + mStartX = mTouchX; + } + mSrcRect.left = mScreenWidth - dis; + mDestRect.right = dis; + + //计算下一页截取的区域 + mNextSrcRect.right = mScreenWidth - dis; + //计算下一页在canvas显示的区域 + mNextDestRect.left = dis; + + canvas.drawBitmap(bitmapList.get(1), mNextSrcRect, mNextDestRect, null); + canvas.drawBitmap(bitmapList.get(0), mSrcRect, mDestRect, null); + } + } + + @Override + public void startAnim() { + int dx; + if (mDirection == Direction.NEXT) { + if (isCancel) { + int dis = (int) ((mScreenWidth - mStartX) + mTouchX); + if (dis > mScreenWidth) { + dis = mScreenWidth; + } + dx = mScreenWidth - dis; + } else { + dx = (int) -(mTouchX + (mScreenWidth - mStartX)); + } + } else { + if (isCancel) { + dx = (int) -Math.abs(mTouchX - mStartX); + } else { + dx = (int) (mScreenWidth - (mTouchX - mStartX)); + } + } + //滑动速度保持一致 + int duration = (animationSpeed * Math.abs(dx)) / mScreenWidth; + mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); + super.startAnim(); + } +} diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml index 95a9075..092a560 100644 --- a/app/src/main/res/anim/fade_in.xml +++ b/app/src/main/res/anim/fade_in.xml @@ -1,8 +1,8 @@ + android:duration="500" + android:fromAlpha="0" + android:toAlpha="1" + android:interpolator="@android:anim/decelerate_interpolator"/> \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml index 3dfb4a7..fcbca92 100644 --- a/app/src/main/res/anim/fade_out.xml +++ b/app/src/main/res/anim/fade_out.xml @@ -1,8 +1,8 @@ + android:duration="500" + android:fromAlpha="1" + android:toAlpha="0" + android:interpolator="@android:anim/decelerate_interpolator"/> \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml b/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml new file mode 100644 index 0000000..75975f7 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/image_btn_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml b/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml new file mode 100644 index 0000000..bc8ec2f --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/image_view_bg1.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml b/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml new file mode 100644 index 0000000..cb09085 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/image_view_bg2.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml index 4cef809..86c01b3 100644 --- a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml @@ -1,6 +1,7 @@ + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml index 7aae36a..34078c8 100644 --- a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml @@ -1,6 +1,7 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml b/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml new file mode 100644 index 0000000..ab08311 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_circle1.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_circle.xml b/app/src/main/res/drawable-xxxhdpi/setting_circle2.xml similarity index 84% rename from app/src/main/res/drawable-xxxhdpi/setting_circle.xml rename to app/src/main/res/drawable-xxxhdpi/setting_circle2.xml index 128b945..5bea095 100644 --- a/app/src/main/res/drawable-xxxhdpi/setting_circle.xml +++ b/app/src/main/res/drawable-xxxhdpi/setting_circle2.xml @@ -4,7 +4,7 @@ + android:color="@color/textSecondary" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_aa.xml b/app/src/main/res/drawable/ic_aa.xml index d204794..34c4503 100644 --- a/app/src/main/res/drawable/ic_aa.xml +++ b/app/src/main/res/drawable/ic_aa.xml @@ -1,9 +1,9 @@ + android:pathData="M268.97,205.07h44.37l267.95,659.87h-39.73L458.41,656.04L124.79,656.04L40.69,864.94L0,864.94l268.97,-659.87zM137.69,622.87h307.75L293,243.85L290.13,243.85l-152.51,378.88zM837.29,374.1c64.72,0 113.73,18.5 146.09,55.5 26.76,30.45 40.62,73.05 40.62,126.57v308.7h-34.13v-102.6a286.72,286.72 0,0 1,-79.53 76.73c-41.64,25.94 -86.02,38.84 -133.12,38.84 -45.26,0 -82.19,-12.01 -109.91,-34.2 -30.52,-23.14 -45.33,-54.55 -45.33,-94.28 0,-55.43 23.07,-95.23 69.29,-121.11 38.84,-24.03 95.23,-35.09 169.16,-35.09l128.48,-0.96v-32.29c0,-102.6 -50.86,-153.46 -152.51,-153.46 -42.53,0 -77.62,8.33 -105.4,26.83a126.36,126.36 0,0 0,-57.28 84.99l-34.13,-3.69c9.22,-46.22 31.4,-81.31 68.33,-105.34 34.13,-24.03 77.62,-35.16 129.37,-35.16zM988.84,622.73l-127.52,0.96c-135.85,0.89 -203.3,43.42 -203.3,126.57 0,29.63 11.06,52.7 34.13,70.31 23.21,16.59 53.66,25.8 91.55,25.8 51.75,0 98.92,-18.43 142.34,-55.43 41.64,-35.09 62.81,-71.13 62.81,-108.13v-60.07z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_auto_page.xml b/app/src/main/res/drawable/ic_auto_page.xml new file mode 100644 index 0000000..6a212c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_auto_page.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_catalog.xml b/app/src/main/res/drawable/ic_catalog.xml index e372f4b..8679a85 100644 --- a/app/src/main/res/drawable/ic_catalog.xml +++ b/app/src/main/res/drawable/ic_catalog.xml @@ -1,18 +1,9 @@ - - - + android:pathData="M106.66,192a21.34,21.34 0,1 0,0 42.69L192,234.69A21.34,21.34 0,1 0,192 192L106.66,192zM106.66,490.66a21.34,21.34 0,1 0,0 42.69L192,533.34a21.34,21.34 0,1 0,0 -42.69L106.66,490.66zM106.66,789.34a21.34,21.34 0,1 0,0 42.69L192,832.03a21.34,21.34 0,1 0,0 -42.69L106.66,789.34zM320,192a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,192zM320,490.66a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,490.66zM320,789.34a21.34,21.34 0,1 0,0 42.69h597.34a21.34,21.34 0,1 0,0 -42.69L320,789.34z"/> diff --git a/app/src/main/res/drawable/ic_hv_screen.xml b/app/src/main/res/drawable/ic_hv_screen.xml new file mode 100644 index 0000000..d7a4437 --- /dev/null +++ b/app/src/main/res/drawable/ic_hv_screen.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_line_spacing2.xml b/app/src/main/res/drawable/ic_line_spacing2.xml new file mode 100644 index 0000000..bd8f720 --- /dev/null +++ b/app/src/main/res/drawable/ic_line_spacing2.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_line_spacing3.xml b/app/src/main/res/drawable/ic_line_spacing3.xml new file mode 100644 index 0000000..16dfd97 --- /dev/null +++ b/app/src/main/res/drawable/ic_line_spacing3.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_line_spacing4.xml b/app/src/main/res/drawable/ic_line_spacing4.xml new file mode 100644 index 0000000..3b8c896 --- /dev/null +++ b/app/src/main/res/drawable/ic_line_spacing4.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_listen_book.xml b/app/src/main/res/drawable/ic_listen_book.xml index 8663f4a..ee7517a 100644 --- a/app/src/main/res/drawable/ic_listen_book.xml +++ b/app/src/main/res/drawable/ic_listen_book.xml @@ -4,9 +4,6 @@ android:viewportWidth="1024" android:viewportHeight="1024"> - + android:pathData="M511.3,159.6C289.9,159.6 110.6,339 112,560.3v193.4c0,61.6 46.2,110.7 105.1,110.7 57.4,0 105.1,-50.4 105.1,-110.7L322.2,629c0,-61.6 -46.2,-110.7 -105.1,-110.7 -23.8,0 -46.2,7 -63,21 9.8,-189.1 166.7,-337.6 357.3,-337.6 190.5,0 347.5,148.5 358.7,337.6 -18.2,-14 -39.2,-21 -63,-22.4 -57.4,0 -105.1,50.4 -105.1,110.7v124.7c0,61.6 46.2,110.7 105.1,110.7 57.4,0 105.1,-50.4 105.1,-110.7v-192C912,339 732.7,159.6 511.3,159.6zM215.7,560.3c35,0 63,29.4 63,67.2v124.7c0,36.4 -28,67.2 -63,67.2s-63,-29.4 -63,-67.2L152.7,627.6c-0.1,-36.5 28,-67.3 63,-67.3zM870,752.3c0,36.4 -28,67.2 -63,67.2s-63,-29.4 -63,-67.2L744,627.6c0,-36.4 28,-67.2 63,-67.2s63,29.4 63,67.2v124.7z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_more_setting.xml b/app/src/main/res/drawable/ic_more_setting.xml new file mode 100644 index 0000000..9d4b944 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_setting.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_night_mode.xml b/app/src/main/res/drawable/ic_night_mode.xml index bddd4c1..53a611a 100644 --- a/app/src/main/res/drawable/ic_night_mode.xml +++ b/app/src/main/res/drawable/ic_night_mode.xml @@ -1,9 +1,9 @@ + android:fillColor="#FF000000" + android:pathData="M504.03,895.62l0.09,-0.08c103.99,-0.59 201.66,-41.34 275.27,-114.94 74.12,-74.13 114.94,-172.68 114.94,-277.53 0,-86.19 -27.67,-167.56 -80.05,-235.28 -25.09,-32.46 -55.12,-60.72 -89.24,-84.03 -34.48,-23.53 -72.32,-41.47 -112.47,-53.28 -2.22,-0.66 -4.55,-0.99 -6.87,-0.99 -7.25,0 -14.09,3.2 -18.75,8.8 -6.21,7.5 -7.36,17.94 -2.92,26.62 23.95,46.67 35.09,91.13 35.09,139.95 0,173.97 -141.55,315.51 -315.51,315.51 -48.83,0 -93.3,-11.15 -139.95,-35.07 -3.44,-1.76 -7.27,-2.69 -11.11,-2.69 -5.66,0 -11.16,1.99 -15.51,5.6 -7.47,6.2 -10.55,16.25 -7.82,25.61 11.9,40.28 29.83,78.12 53.29,112.49 23.28,34.11 51.56,64.14 84.02,89.24 67.72,52.38 149.08,80.05 235.27,80.05l2.25,0L504.03,895.62zM650.19,196.65c119.17,56.25 195.5,175.18 195.5,306.43 0,189.59 -154.25,343.84 -343.84,343.84 -131.24,0 -250.16,-76.32 -306.43,-195.51 35.32,11.81 70.84,17.58 108.2,17.58 97.27,0 188.7,-37.86 257.47,-106.62 68.79,-68.79 106.68,-160.26 106.68,-257.52C667.78,267.59 662.01,232.07 650.19,196.65L650.19,196.65z"/> diff --git a/app/src/main/res/drawable/ic_page_mode.xml b/app/src/main/res/drawable/ic_page_mode.xml new file mode 100644 index 0000000..e4a4e14 --- /dev/null +++ b/app/src/main/res/drawable/ic_page_mode.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml new file mode 100644 index 0000000..90eec6f --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml new file mode 100644 index 0000000..f1b4cf5 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml index 0022d3b..3ce5e52 100644 --- a/app/src/main/res/drawable/ic_share.xml +++ b/app/src/main/res/drawable/ic_share.xml @@ -1,14 +1,9 @@ - - - - - \ No newline at end of file + android:viewportWidth="1024" + android:viewportHeight="1024"> + + diff --git a/app/src/main/res/drawable/ic_sun.xml b/app/src/main/res/drawable/ic_sun.xml new file mode 100644 index 0000000..5562789 --- /dev/null +++ b/app/src/main/res/drawable/ic_sun.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_image.png b/app/src/main/res/drawable/icon_image.png new file mode 100644 index 0000000..56f64f0 Binary files /dev/null and b/app/src/main/res/drawable/icon_image.png differ diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..dcda132 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +