commit a3c5587896b46bf22022033e63d7248361621b37 Author: fengyue <1021300691@qq.com> Date: Wed Jul 29 18:03:59 2020 +0800 风月读书v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000..a364427 Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..214996a --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,11 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://47.105.152.62:3306/fyreader + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..4b8f40c --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..5cd135a --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..703cc0f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5da0664 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bf5d30b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..fb48575 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..67ee645 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## FYReader-master +风月读书,一款免费、无广告的小说阅读软件。 + +主要功能: + + 1、书籍可换源(共四个书源) + 笔趣阁44(www.wqge.cc)、笔趣阁(www.52bqg.com)、天籁小说(www.23txt.com)、品书网(www.vodtw.la) + + 2、多种阅读方式(覆盖、仿真、滑动、滚动、无动画) + + 3、书架及设置可备份/恢复 + + 4、支持联网下载字体、支持本地字体 + + 5、支持切换书架布局(列表模式、宫格模式) + + 6、缓存可导出成txt文件 + + 7、支持导入本地书籍,支持直接打开txt书籍文件 + + 8、支持将书架及设置同步至网络 + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..d51a284 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,125 @@ +apply plugin: 'com.android.application' +apply plugin: 'org.greenrobot.greendao'//greendao插件dependencies + +def releaseTime() { + return new Date().format("yy.MMddHH", TimeZone.getTimeZone("GMT+08:00")) +} + +def getVersionCode() { + def versionCodeFile = file('version_code.properties') + if (versionCodeFile.canRead()) { + Properties properties = new Properties() + properties.load(new FileInputStream(versionCodeFile)) + def versionCode = properties['VERSION_CODE'].toInteger()//读取version_code.properties文件存放的版本号。 + def runTasks = gradle.startParameter.taskNames + def task = ':app:assembleRelease' + if (task in runTasks) { + properties['VERSION_CODE'] = (++versionCode).toString() + properties.store(versionCodeFile.newWriter(), null) + } + return versionCode + } else { + throw new GradleException("无法读取version_code.properties文件!") + } +} +def name = "风月读书" +def version = getVersionCode() + +android { + compileSdkVersion 29 + buildToolsVersion '29.0.3' + defaultConfig { + applicationId "xyz.fycz.myreader" + minSdkVersion 21 + targetSdkVersion 29 + versionCode version + versionName '1.' + releaseTime() + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + javaCompileOptions { + annotationProcessorOptions { + includeCompileClasspath = true + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + ndk { + abiFilters "x86", "armeabi", "armeabi-v7a" + } + } + debug { + ndk { + abiFilters "x86", "armeabi", "armeabi-v7a" + } + } + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "${name}v${defaultConfig.versionName}.apk" + } + } + } + sourceSets.main { + jni.srcDirs = [] + jniLibs.srcDir "libs/" + } + + compileOptions { + + sourceCompatibility JavaVersion.VERSION_1_8 + + targetCompatibility JavaVersion.VERSION_1_8 + + } + productFlavors { + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + + + implementation 'com.jakewharton:butterknife:10.0.0' + + annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0' + + //Glide + implementation 'com.github.bumptech.glide:glide:4.9.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' + + implementation 'com.squareup.okhttp3:okhttp:3.14.7' + + implementation 'com.google.code.gson:gson:2.8.5' + + implementation 'com.journeyapps:zxing-android-embedded:3.5.0' + + implementation 'org.greenrobot:greendao:3.2.2' + + implementation 'org.jsoup:jsoup:1.11.3' + + implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.2' + + implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.2' + + implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.3' + + implementation 'me.gujun.android.taggroup:library:1.4@aar' + + implementation 'uk.co.chrisjenx:calligraphy:2.3.0' + + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + implementation 'io.alterac.blurkit:blurkit:1.1.0' + + implementation 'com.google.android.material:material:1.1.0' + + implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0' + +} + +greendao { + schemaVersion 9 + daoPackage 'xyz.fycz.myreader.greendao.gen' +// targetGenDir 'src/main/java' +} diff --git a/app/libs/ZHConverter.jar b/app/libs/ZHConverter.jar new file mode 100644 index 0000000..53a12bb Binary files /dev/null and b/app/libs/ZHConverter.jar differ diff --git a/app/libs/adapter-rxjava2-2.7.2.jar b/app/libs/adapter-rxjava2-2.7.2.jar new file mode 100644 index 0000000..0f5e9a8 Binary files /dev/null and b/app/libs/adapter-rxjava2-2.7.2.jar differ diff --git a/app/libs/antlr-2.7.4.jar b/app/libs/antlr-2.7.4.jar new file mode 100644 index 0000000..45e45b5 Binary files /dev/null and b/app/libs/antlr-2.7.4.jar differ diff --git a/app/libs/chardet-1.0.jar b/app/libs/chardet-1.0.jar new file mode 100644 index 0000000..2e84a8d Binary files /dev/null and b/app/libs/chardet-1.0.jar differ diff --git a/app/libs/classes.jar b/app/libs/classes.jar new file mode 100644 index 0000000..331c692 Binary files /dev/null and b/app/libs/classes.jar differ diff --git a/app/libs/cpdetector_1.0.10.jar b/app/libs/cpdetector_1.0.10.jar new file mode 100644 index 0000000..47329f2 Binary files /dev/null and b/app/libs/cpdetector_1.0.10.jar differ diff --git a/app/libs/jargs-1.0.jar b/app/libs/jargs-1.0.jar new file mode 100644 index 0000000..92d9b9b Binary files /dev/null and b/app/libs/jargs-1.0.jar differ diff --git a/app/libs/rxjava-2.2.19.jar b/app/libs/rxjava-2.2.19.jar new file mode 100644 index 0000000..0f760be Binary files /dev/null and b/app/libs/rxjava-2.2.19.jar differ diff --git a/app/libs/sun.misc.BASE64Decoder.jar b/app/libs/sun.misc.BASE64Decoder.jar new file mode 100644 index 0000000..9811c13 Binary files /dev/null and b/app/libs/sun.misc.BASE64Decoder.jar differ diff --git a/app/myReader_key.jks b/app/myReader_key.jks new file mode 100644 index 0000000..cf184ed Binary files /dev/null and b/app/myReader_key.jks differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..9786e21 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e2eb0fb --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/disclaimer.fy b/app/src/main/assets/disclaimer.fy new file mode 100644 index 0000000..344923a --- /dev/null +++ b/app/src/main/assets/disclaimer.fy @@ -0,0 +1,4 @@ +1、 风月读书是一款提供网络文学搜索的工具,为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。 +2、 当您搜索一本书的时,风月读书会将该书的书名以关键词的形式提交到各个第三方网络文学网站。各第三方网站返回的内容与风月读书无关,风月读书对其概不负责,亦不承担任何法律责任。任何通过使用风月读书而链接到的第三方网页均系他人制作或提供,您可能从第三方网页上获得其他服务,风月读书对其合法性概不负责,亦不承担任何法律责任。第三方搜索引擎结果根据您提交的书名自动搜索获得并提供试读,不代表风月读书赞成或被搜索链接到的第三方网页上的内容或立场。您应该对使用搜索引擎的结果自行承担风险。 +3、 风月读书不做任何形式的保证:不保证第三方搜索引擎的搜索结果满足您的要求,不保证搜索服务不中断,不保证搜索结果的安全性、正确性、及时性、合法性。因网络状况、通讯线路、第三方网站等任何原因而导致您不能正常使用阅读,风月读书不承担任何法律责任。风月读书尊重并保护所有使用风月读书用户的个人隐私权,您注册的用户名、电子邮件地址等个人资料,非经您亲自许可或根据相关法律、法规的强制性规定,风月读书不会主动地泄露给第三方。 +4、 风月读书致力于最大程度地减少网络文学阅读者在自行搜寻过程中的无意义的时间浪费,通过专业搜索展示不同网站中网络文学的最新章节。风月读书在为广大小说爱好者提供方便、快捷舒适的试读体验的同时,也使优秀网络文学得以迅速、更广泛的传播,从而达到了在一定程度促进网络文学充分繁荣发展之目的。风月读书鼓励广大小说爱好者通过风月读书发现优秀网络小说及其提供商,并建议阅读正版图书。任何单位或个人认为通过风月读书搜索链接到的第三方网页内容可能涉嫌侵犯其信息网络传播权,应该及时向风月读书提出书面权力通知,并提供身份证明、权属证明及详细侵权情况证明。风月读书在收到上述法律文件后,将会依法尽快断开相关链接内容。 \ No newline at end of file diff --git a/app/src/main/assets/fonts/number.ttf b/app/src/main/assets/fonts/number.ttf new file mode 100644 index 0000000..f2804db Binary files /dev/null and b/app/src/main/assets/fonts/number.ttf differ diff --git a/app/src/main/java/xyz/fycz/myreader/ActivityManage.java b/app/src/main/java/xyz/fycz/myreader/ActivityManage.java new file mode 100644 index 0000000..9c1546c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ActivityManage.java @@ -0,0 +1,39 @@ +package xyz.fycz.myreader; + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; + +/** + * Created by zhao on 2016/4/16. + */ +public class ActivityManage { + + private static ArrayList activities = new ArrayList(); + + public static void addActivity(AppCompatActivity activity){ + activities.add(activity); + } + + public static void removeActivity(AppCompatActivity activity ){ + activities.remove(activity); + } + + public static void finishAllActivites() { + for (AppCompatActivity activity : activities) { + if (!activity.isFinishing()) { + activity.finish(); + } + } + } + + public static AppCompatActivity getActivityByCurrenlyRun(){ + if(activities.size() <= 0){ + return null; + } + return activities.get(activities.size() - 1); + } + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/MainActivity.java b/app/src/main/java/xyz/fycz/myreader/MainActivity.java new file mode 100644 index 0000000..2855961 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/MainActivity.java @@ -0,0 +1,13 @@ +package xyz.fycz.myreader; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java new file mode 100644 index 0000000..170e389 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java @@ -0,0 +1,358 @@ +package xyz.fycz.myreader.application; + + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; + +import androidx.appcompat.app.AppCompatActivity; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.creator.APPDownloadTip; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.entity.UpdateInfo; +import xyz.fycz.myreader.ui.home.MainActivity; +import xyz.fycz.myreader.ui.home.bookcase.BookcaseFragment; +import xyz.fycz.myreader.util.*; + + +public class MyApplication extends Application { + + private static Handler handler = new Handler(); + private static MyApplication application; + private ExecutorService mFixedThreadPool; + + + @Override + public void onCreate() { + super.onCreate(); + application = this; + HttpUtil.trustAllHosts();//信任所有证书 +// handleSSLHandshake(); + mFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());//初始化线程池 + + BaseActivity.setCloseAntiHijacking(true); + + } + + @SuppressLint("TrulyRandom") + public static void handleSSLHandshake() { + try { + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + }}; + + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } catch (Exception ignored) { + } + } + + + public static Context getmContext() { + return application; + } + + + public void newThread(Runnable runnable) { + try { + mFixedThreadPool.execute(runnable); + } catch (Exception e) { + e.printStackTrace(); + mFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());//初始化线程池 + mFixedThreadPool.execute(runnable); + } + } + + public void shutdownThreadPool() { + mFixedThreadPool.shutdownNow(); + } + + + @TargetApi(26) + private void createNotificationChannel() { + + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + notificationManager.createNotificationChannel(new NotificationChannel("gxdw_push_and_im", "gxdw", NotificationManager.IMPORTANCE_DEFAULT)); + } + + + /** + * 主线程执行 + * + * @param runnable + */ + public static void runOnUiThread(Runnable runnable) { + handler.post(runnable); + } + + public static MyApplication getApplication() { + return application; + } + + + private boolean isFolderExist(String dir) { + File folder = Environment.getExternalStoragePublicDirectory(dir); + return (folder.exists() && folder.isDirectory()) || folder.mkdirs(); + } + + /** + * 获取app版本号 + * + * @return + */ + public static int getVersionCode() { + try { + PackageManager manager = application.getPackageManager(); + PackageInfo info = manager.getPackageInfo(application.getPackageName(), 0); + + return info.versionCode; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 获取app版本号(String) + * + * @return + */ + public static String getStrVersionName() { + try { + PackageManager manager = application.getPackageManager(); + PackageInfo info = manager.getPackageInfo(application.getPackageName(), 0); + + return info.versionName; + } catch (Exception e) { + e.printStackTrace(); + return "1.0.0"; + } + } + + /** + * 检查更新 + */ + public static void checkVersion(AppCompatActivity activity) { + UpdateInfo updateInfo = (UpdateInfo) CacheHelper.readObject(APPCONST.FILE_NAME_UPDATE_INFO); + int versionCode = getVersionCode(); + if (updateInfo != null) { + if (updateInfo.getNewestVersionCode() > versionCode) { + //updateApp(activity, updateInfo.getDownLoadUrl(), versionCode); + } + } + } + + + /** + * 检查更新 + */ + /*public static void checkVersionByServer(final Activity activity, final boolean isManualCheck) { + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + Document doc = null; + try { + doc = Jsoup.connect("https://novel.fycz.xyz/app/update.html").get(); + int newestVersion = 0; + String updateContent = ""; + String downloadLink = null; + boolean isForceUpdate = false; + StringBuilder s = new StringBuilder(); + assert doc != null; + Elements nodes = doc.getElementsByClass("secd-rank-list"); + newestVersion = Integer.valueOf(nodes.get(0).getElementsByTag("a").get(1).text()); + downloadLink = nodes.get(0).getElementsByTag("a").get(1).attr("href"); + updateContent = nodes.get(0).getElementsByTag("a").get(2).text(); + isForceUpdate = Boolean.parseBoolean(nodes.get(0).getElementsByTag("a").get(3).text()); + String[] updateContents = updateContent.split("/"); + for (String string : updateContents) { + s.append(string); + s.append("\n"); + } + int versionCode = getVersionCode(); + if (newestVersion > versionCode) { + MyApplication m = new MyApplication(); + Setting setting = SysManager.getSetting(); + if (isManualCheck || setting.getNewestVersionCode() < newestVersion || isForceUpdate) { + setting.setNewestVersionCode(newestVersion); + SysManager.saveSetting(setting); + int i = setting.getNewestVersionCode(); + m.updateApp(activity, downloadLink, newestVersion, s.toString(), isForceUpdate); + } + } else if (isManualCheck) { + TextHelper.showText("已经是最新版本!"); + } + } catch (IOException e) { + e.printStackTrace(); + TextHelper.showText("无网络连接!"); + } + } + }); + }*/ + public static void checkVersionByServer(final MainActivity activity, final boolean isManualCheck, + final BookcaseFragment mBookcaseFragment) { + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + Document doc = null; + try { + String url = "https://shimo.im/docs/cqkgjPRRydYYhQKt/read"; + if (isApkInDebug(getmContext())){ + url = "https://shimo.im/docs/zfzpda7MUGskOC9v/read"; + } + doc = Jsoup.connect(url).get(); + String content = doc.getElementsByClass("ql-editor").text(); + if (StringHelper.isEmpty(content)){ + TextHelper.showText("检查更新失败!"); + return; + } + String[] contents = content.split(";"); + int newestVersion = 0; + String updateContent = ""; + String downloadLink = null; + boolean isForceUpdate = false; + StringBuilder s = new StringBuilder(); + newestVersion = Integer.parseInt(contents[0].substring(contents[0].indexOf(":") + 1)); + isForceUpdate = Boolean.parseBoolean(contents[1].substring(contents[1].indexOf(":") + 1)); + downloadLink = contents[2].substring(contents[2].indexOf(":") + 1); + updateContent = contents[3].substring(contents[3].indexOf(":") + 1); + SharedPreUtils spu = SharedPreUtils.getInstance(); + spu.putString("lanzousKeyStart", contents[4].substring(contents[4].indexOf(":") + 1)); + String[] updateContents = updateContent.split("/"); + for (String string : updateContents) { + s.append(string); + s.append("\n"); + } + int versionCode = getVersionCode(); + if (newestVersion > versionCode) { + MyApplication m = new MyApplication(); + Setting setting = SysManager.getSetting(); + if (isManualCheck || setting.getNewestVersionCode() < newestVersion || isForceUpdate) { + setting.setNewestVersionCode(newestVersion); + SysManager.saveSetting(setting); + m.updateApp(activity, downloadLink, newestVersion, s.toString(), isForceUpdate, + mBookcaseFragment); + } + } else if (isManualCheck) { + TextHelper.showText("已经是最新版本!"); + } + } catch (Exception e) { + e.printStackTrace(); + TextHelper.showText("无网络连接!"); + } + } + }); + } + + /** + * App自动升级 + * + * @param activity + * @param versionCode + */ + public void updateApp(final MainActivity activity, final String url, final int versionCode, String message, + final boolean isForceUpdate, final BookcaseFragment mBookcaseFragment) { + //String version = (versionCode / 100 % 10) + "." + (versionCode / 10 % 10) + "." + (versionCode % 10); + String cancelTitle; + if(isForceUpdate){ + cancelTitle = "退出"; + }else { + cancelTitle = "忽略此版本"; + } + DialogCreator.createThreeButtonDialog(activity, "发现新版本:", message, !isForceUpdate, cancelTitle, "直接下载", + "浏览器下载", (dialog, which) -> { + if (isForceUpdate) { + activity.finish(); + } + }, (dialog, which) -> { + if (url == null || "".equals(url)) { + TextHelper.showText("获取链接失败,请前往浏览器下载!"); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(URLCONST.APP_DIR_UR)); + activity.startActivity(intent); + if (isForceUpdate) { + activity.finish(); + } + } else { + APPDownloadTip downloadTip = new APPDownloadTip(url, mBookcaseFragment, activity, isForceUpdate); + downloadTip.downloadApp(); + } + }, (dialog, which) -> { + String downloadLink = url; + if (url == null || "".equals(url)) { + downloadLink = URLCONST.APP_DIR_UR; + } + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(downloadLink)); + activity.startActivity(intent); + if (isForceUpdate) { + activity.finish(); + } + }); + } + + /** + * 判断当前应用是否是debug状态 + */ + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/application/SysManager.java b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java new file mode 100644 index 0000000..27b5179 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/application/SysManager.java @@ -0,0 +1,77 @@ +package xyz.fycz.myreader.application; + +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.CacheHelper; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.widget.page.PageMode; + +import static xyz.fycz.myreader.application.MyApplication.getVersionCode; + + +public class SysManager { + + public static void logout() { + + } + + /** + * 获取设置 + * @return + */ + public static Setting getSetting() { + Setting setting = (Setting) CacheHelper.readObject(APPCONST.FILE_NAME_SETTING); + if (setting == null){ + setting = getDefaultSetting(); + saveSetting(setting); + } + return setting; + } + + /** + * 保存设置 + * @param setting + */ + public static void saveSetting(Setting setting) { + CacheHelper.saveObject(setting, APPCONST.FILE_NAME_SETTING); + } + + + /** + * 默认设置 + * @return + */ + private static Setting getDefaultSetting(){ + Setting setting = new Setting(); + setting.setDayStyle(true); + setting.setReadBgColor(R.color.sys_protect_eye_bg); + setting.setReadStyle(ReadStyle.protectedEye); + setting.setReadWordSize(25); + setting.setReadWordColor(R.color.sys_protect_eye_word); + setting.setBrightProgress(50); + setting.setBrightFollowSystem(true); + setting.setLanguage(Language.simplified); + setting.setFont(Font.默认字体); + setting.setAutoScrollSpeed(300); + setting.setPageMode(PageMode.SIMULATION); + setting.setVolumeTurnPage(false); + setting.setBookcaseStyle(BookcaseStyle.listMode); + setting.setNewestVersionCode(getVersionCode()); + setting.setLocalFontName(""); + setting.setSettingVersion(APPCONST.SETTING_VERSION); + return setting; + } + + + /** + * 重置设置 + */ + + public static void resetSetting(){ + saveSetting(getDefaultSetting()); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/application/TrustAllCerts.java b/app/src/main/java/xyz/fycz/myreader/application/TrustAllCerts.java new file mode 100644 index 0000000..d6d62ad --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/application/TrustAllCerts.java @@ -0,0 +1,16 @@ +package xyz.fycz.myreader.application; + +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +public class TrustAllCerts implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + + @Override + public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];} +} diff --git a/app/src/main/java/xyz/fycz/myreader/backup/BackupAndRestore.java b/app/src/main/java/xyz/fycz/myreader/backup/BackupAndRestore.java new file mode 100644 index 0000000..05698c1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/backup/BackupAndRestore.java @@ -0,0 +1,138 @@ +package xyz.fycz.myreader.backup; + +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.util.utils.FileUtils; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author fengyue + * @date 2020/4/25 9:07 + */ + +public class BackupAndRestore { + + BookService mBookService = new BookService(); + + /** + * 备份书架 + * @return 是否备份成功 + */ + public boolean backup(String backupName){ + List books = mBookService.getAllBooks(); + StringBuilder s = new StringBuilder(); + for (Book book : books) { + s.append(book); + s.append(",\n"); + } + s.deleteCharAt(s.lastIndexOf(",")); + File booksFile = FileUtils.getFile(APPCONST.FILE_DIR + backupName + "/books" + FileUtils.SUFFIX_FY); + File settingFile = FileUtils.getFile(APPCONST.FILE_DIR + backupName + "/setting" + FileUtils.SUFFIX_FY); + BufferedWriter bw = null; + ObjectOutputStream oos = null; + try { + bw = new BufferedWriter(new FileWriter(booksFile)); + bw.write(s.toString()); + bw.flush(); + oos = new ObjectOutputStream(new FileOutputStream(settingFile)); + oos.writeObject(SysManager.getSetting()); + oos.flush(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + if (bw != null) { + try { + bw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 恢复书架 + * @return 是否恢复成功 + */ + public boolean restore(String backupName) { + File booksFile = FileUtils.getFile(APPCONST.FILE_DIR + backupName + "/books" + FileUtils.SUFFIX_FY); + File settingFile = FileUtils.getFile(APPCONST.FILE_DIR + backupName + "/setting" + FileUtils.SUFFIX_FY); + if (!booksFile.exists() || !settingFile.exists()){ + return false; + } + BufferedReader br = null; + ObjectInputStream ois = null; + try { + br = new BufferedReader(new FileReader(booksFile)); + String tem = ""; + StringBuilder s = new StringBuilder(); + while ((tem = br.readLine()) != null){ + s.append(tem).append("\n"); + } + String[] sBooks = s.toString().split("\\},"); + List books = new ArrayList<>(); + for (String sBook : sBooks){ + sBook.replace("{", ""); + sBook.replace("}", ""); + String[] sBookFields = sBook.split(",\n"); + for (int i = 0; i < sBookFields.length; i++) { + sBookFields[i] = sBookFields[i].substring(sBookFields[i].indexOf("'") + 1, sBookFields[i].lastIndexOf("'")); + } + String source = "null"; + if(!sBookFields[2].contains("novel.fycz.xyz")){ + source = sBookFields[17]; + } + if ("本地书籍".equals(sBookFields[4])){ + sBookFields[15] = "0"; + } + Book book = new Book(sBookFields[0], sBookFields[1], sBookFields[2], sBookFields[3], sBookFields[4], + sBookFields[5], sBookFields[6], sBookFields[7], sBookFields[8], sBookFields[9], sBookFields[10], + sBookFields[11], Integer.parseInt(sBookFields[12]), Integer.parseInt(sBookFields[13]), + Integer.parseInt(sBookFields[14]), Integer.parseInt(sBookFields[15]), Integer.parseInt(sBookFields[16]) + , source); + books.add(book); + } + mBookService.deleteAllBooks(); + mBookService.addBooks(books); + ois = new ObjectInputStream(new FileInputStream(settingFile)); + Object obj = ois.readObject(); + if (obj instanceof Setting){ + Setting setting = (Setting) obj; + SysManager.saveSetting(setting); + } + return true; + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + return false; + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/backup/UserService.java b/app/src/main/java/xyz/fycz/myreader/backup/UserService.java new file mode 100644 index 0000000..dd451fc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/backup/UserService.java @@ -0,0 +1,324 @@ +package xyz.fycz.myreader.backup; + +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.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(new Runnable() { + @Override + public void run() { + 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 { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 读配置 + * @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 { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + 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 boolean webBackup(){ + Map userInfo = readConfig(); + if (userInfo == null){ + return false; + } + BackupAndRestore bar = new BackupAndRestore(); + bar.backup("webBackup"); + File inputFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup"); + if (!inputFile.exists()) { + return false; + } + 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()); + return code == 104; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + IOUtils.close(fis); + if (conn != null) { + conn.disconnect(); + } + } + } + + /** + * 网络恢复 + * @return + */ + public static boolean webRestore(){ + Map userInfo = readConfig(); + if (userInfo == null){ + return false; + } + 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(); + return false; + } + ZipUtils.unzipFile(zipFile.getAbsolutePath(), APPCONST.FILE_DIR); + BackupAndRestore bar = new BackupAndRestore(); + bar.restore("webBackup"); + zipFile.delete(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + }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/base/BaseActivity.java b/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java new file mode 100644 index 0000000..ee040e9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java @@ -0,0 +1,134 @@ +package xyz.fycz.myreader.base; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.appcompat.app.AppCompatActivity; +import xyz.fycz.myreader.ActivityManage; +import xyz.fycz.myreader.util.Anti_hijackingUtils; +import xyz.fycz.myreader.util.SystemBarTintManager; + + + +public class BaseActivity extends AppCompatActivity { + + public static int width = 0; + public static int height = 0; + public static boolean home; + public static boolean back; + private boolean catchHomeKey = false; + private boolean disallowAntiHijacking;//暂停防界面劫持 + + private static boolean closeAntiHijacking;//关闭防界面劫持 + + + private InputMethodManager mInputMethodManager; //输入管理器 + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //将每一个Activity都加入activity管理器 + ActivityManage.addActivity(this); + Log.d("ActivityName: ",getLocalClassName()); + DisplayMetrics dm = new DisplayMetrics(); + //获取屏幕宽高 + if(height == 0 || height == 0){ + getWindowManager().getDefaultDisplay().getMetrics(dm); + width = dm.widthPixels; + height = dm.heightPixels; + } + + mInputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + } + + public static void setCloseAntiHijacking(boolean closeAntiHijacking) { + BaseActivity.closeAntiHijacking = closeAntiHijacking; + } + + @Override + protected void onDestroy() { + ActivityManage.removeActivity(this); + super.onDestroy(); + } + + + @Override + protected void onPause() { + if (!disallowAntiHijacking && !closeAntiHijacking) { + Anti_hijackingUtils.getinstance().onPause(this);//防界面劫持提示任务 + } + super.onPause(); + } + + @Override + protected void onResume() { + if (!closeAntiHijacking) { + Anti_hijackingUtils.getinstance().onResume(this);//注销防界面劫持提示任务 + } + BaseActivity.home = false; + BaseActivity.back = false; + disallowAntiHijacking = false; + super.onResume(); + } + + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK){ + back = true;//以便于判断是否按返回键触发界面劫持提示 + } + return super.onKeyDown(keyCode, event); + } + + @TargetApi(19) + protected void setTranslucentStatus(boolean on) { + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; + if (on) { + winParams.flags |= bits; + } else { + winParams.flags &= ~bits; + } + win.setAttributes(winParams); + } + + public void setDisallowAntiHijacking(boolean disallowAntiHijacking) { + this.disallowAntiHijacking = disallowAntiHijacking; + } + + + + /** + * 设置状态栏颜色 + * @param colorId + */ + public void setStatusBar(int colorId){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + setTranslucentStatus(true); + SystemBarTintManager tintManager = new SystemBarTintManager(this); + tintManager.setStatusBarTintEnabled(true); + tintManager.setStatusBarTintResource(colorId);//通知栏所需颜色ID + } + } + + public InputMethodManager getmInputMethodManager() { + return mInputMethodManager; + } + + + + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/base/BasePresenter.java b/app/src/main/java/xyz/fycz/myreader/base/BasePresenter.java new file mode 100644 index 0000000..984f059 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/base/BasePresenter.java @@ -0,0 +1,6 @@ +package xyz.fycz.myreader.base; + + +public interface BasePresenter { + void start(); +} diff --git a/app/src/main/java/xyz/fycz/myreader/callback/HttpCallback.java b/app/src/main/java/xyz/fycz/myreader/callback/HttpCallback.java new file mode 100644 index 0000000..8a8a8dc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/callback/HttpCallback.java @@ -0,0 +1,15 @@ +package xyz.fycz.myreader.callback; + +import android.graphics.Bitmap; + +import java.io.InputStream; + +/** + * Created by zhao on 2016/4/16. + */ +public interface HttpCallback { + void onFinish(String response); + void onFinish(InputStream in); + void onFinish(Bitmap bm); + void onError(Exception e); +} diff --git a/app/src/main/java/xyz/fycz/myreader/callback/JsonCallback.java b/app/src/main/java/xyz/fycz/myreader/callback/JsonCallback.java new file mode 100644 index 0000000..28e3a11 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/callback/JsonCallback.java @@ -0,0 +1,16 @@ +package xyz.fycz.myreader.callback; + + +import xyz.fycz.myreader.entity.JsonModel; + +/** + * Created by zhao on 2016/10/25. + */ + +public interface JsonCallback { + + void onFinish(JsonModel jsonModel); + + void onError(Exception e); + +} diff --git a/app/src/main/java/xyz/fycz/myreader/callback/ResultCallback.java b/app/src/main/java/xyz/fycz/myreader/callback/ResultCallback.java new file mode 100644 index 0000000..bd7a3eb --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/callback/ResultCallback.java @@ -0,0 +1,14 @@ +package xyz.fycz.myreader.callback; + + +/** + * Created by zhao on 2016/10/25. + */ + +public interface ResultCallback { + + void onFinish(Object o, int code); + + void onError(Exception e); + +} diff --git a/app/src/main/java/xyz/fycz/myreader/callback/URLConnectionCallback.java b/app/src/main/java/xyz/fycz/myreader/callback/URLConnectionCallback.java new file mode 100644 index 0000000..c38354e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/callback/URLConnectionCallback.java @@ -0,0 +1,9 @@ +package xyz.fycz.myreader.callback; + +/** + * Created by zhao on 2016/4/16. + */ +public interface URLConnectionCallback { + void onFinish(boolean checked); + +} diff --git a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java new file mode 100644 index 0000000..b399d81 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java @@ -0,0 +1,70 @@ +package xyz.fycz.myreader.common; + +import android.os.Environment; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.util.utils.FileUtils; + +import java.io.File; + + +public class APPCONST { + + public static String publicKey = "fyds1.0";//服务端公钥 + public static String privateKey;//app私钥 + public final static String s = "11940364935628058505"; + public static final String KEY = "readerByFengyue"; + + public static final String ALARM_SCHEDULE_MSG = "alarm_schedule_msg"; + + public static final String FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/"; + 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/"; + public static final String FONT_BOOK_DIR = Environment.getExternalStorageDirectory() + "/FYReader/font/"; + public static final String UPDATE_APK_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/apk/"; + public static final String QQ_DATA_DIR = Environment.getExternalStorageDirectory() + "/tencent/MobileQQ/data/"; + //BookCachePath (因为getCachePath引用了Context,所以必须是静态变量,不能够是静态常量) + public static String BOOK_CACHE_PATH = FileUtils.getCachePath() + File.separator + + "book_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; + + public static final String BOOK = "book"; + public static final String CHAPTER_PAGE = "chapter_page"; + public static final String SETTING = "setting"; + public static final String SEARCH_BOOK_BEAN = "SearchBookBean"; + public static final String FONT = "font"; + + + 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.mipmap.theme_leather_bg};//羊皮纸 + public static final int[] READ_STYLE_BREEN_EYE = {R.color.sys_breen_word, R.color.sys_breen_bg};//棕绿色 + + + public static final String FILE_NAME_SETTING = "setting"; + public static final String FILE_NAME_UPDATE_INFO = "updateInfo"; + + public static final int REQUEST_FONT = 1001; + public static final int REQUEST_CHAPTER_PAGE = 1002; + + public static final String RESULT_IS_COLLECTED = "result_is_collected"; + public static final int REQUEST_READ = 1; + + public static final CharSequence[] DIALOG_DOWNLOAD = { + "下载后面五十章", "下载前后五十章", + "下载后面全部章", "下载本书所有章" + }; + + public static final int APP_INSTALL_CODE = 10086; + + public static final int SELECT_FILE_CODE = 10000; + + //设置版本号 + public static final int SETTING_VERSION = 1; +} diff --git a/app/src/main/java/xyz/fycz/myreader/common/Common.java b/app/src/main/java/xyz/fycz/myreader/common/Common.java new file mode 100644 index 0000000..ea1cb64 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/common/Common.java @@ -0,0 +1,11 @@ +package xyz.fycz.myreader.common; + + + +public interface Common { + + int SUCCESS = 1; + int INSUCCESS = 0; + int LOGINEXIT = 2; + int MESSAGE_HINT = 3; +} diff --git a/app/src/main/java/xyz/fycz/myreader/common/ErrorCode.java b/app/src/main/java/xyz/fycz/myreader/common/ErrorCode.java new file mode 100644 index 0000000..daf9c77 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/common/ErrorCode.java @@ -0,0 +1,34 @@ +package xyz.fycz.myreader.common; + + + +public class ErrorCode { + + public static final int login_not_user = 1; // 用户不存在 + public static final int login_not_password = 2;// 密码错误 + public static final int internet_waring = 3;// 网络访问错误 + public static final int no_security = 4;// 没有权限(登陆过期) + public static final int no_login_ = 5;// 没有登录 + public static final int meeting_user_no = 6;// 报名用户不存在 + public static final int old_password = 8;// 原密码错误 + public static final int execute_error = 7;//程序运行异常 + public static final int order_timeout = 9;// 订餐超时 + public static final int email_or_emailPwd_empty = 10;//邮箱或邮箱密码为空 + public static final int email_login_error = 11;//邮箱登陆失败 + public static final int email_params_error = 12;//邮箱接口参数不完整 + public static final int suggestion_params_error = 13;//反馈意见接口参数不完整 + public static final int email_addresses_error = 15;//邮箱地址无效 + public static final int uploadfile_extend_error = 16;//上传附件后缀不支持 + public static final int params_overlength = 17;//接口参数过长 + public static final int leave_error = 18;//请假审批人跟请假人不能相同 + public static final int login_simple_pwd = 19;//简单登录密码 + + + public static final int more_depart = 1011;//多部门用户 + public static final int Certificate_sys_no_user = 1012;//认证系统内无此用户 + public static final int Certificate_no_bind_user = 1013;//用户没绑定证书在系统内 + public static final int ukey_duplicate_user = 1014;//ukey序列号系统中不存在或存在多笔数据,请联系管理员 + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java new file mode 100644 index 0000000..6a832c8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java @@ -0,0 +1,39 @@ +package xyz.fycz.myreader.common; + +/** + * Created by zhao on 2016/10/20. + */ + +public class URLCONST { + + // 命名空间 + public static String nameSpace_FY = "https://novel.fycz.xyz"; + + public static String nameSpace_biquge = "https://www.52bqg.com/"; + + public static String nameSpace_system = "https://novel.fycz.xyz"; + + public static boolean isRSA = false; + + // 搜索小说 + public static String method_fengyue_search = "https://novel.fycz.xyz/search.html"; + public static String method_buxiu_search = "https://www.23txt.com/search.php"; + + // 获取最新版本号 + public static String method_getCurAppVersion = nameSpace_system + "/mReaderController.do?getCurAppVersion"; + + // app下载 + public static String method_downloadApp = nameSpace_system + "/app/FYReader.apk"; + + public static String APP_DIR_UR = "https://www.lanzous.com/b00ngso7e"; + + public static String LAN_ZOUS_URL = "https://fycz.lanzous.com"; + + //字体下载 + public static final String FONT_DOWNLOAD_URL = "https://novel.fycz.xyz/app/fonts/"; + + public static final String APP_WEB_URL = "http://fyreader.fycz.xyz:8080/FYReader/"; + + +} + diff --git a/app/src/main/java/xyz/fycz/myreader/controller/SyncChaptersController.java b/app/src/main/java/xyz/fycz/myreader/controller/SyncChaptersController.java new file mode 100644 index 0000000..d863cc6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/controller/SyncChaptersController.java @@ -0,0 +1,63 @@ +package xyz.fycz.myreader.controller; + +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.ChapterService; + +import java.util.ArrayList; + + + +public class SyncChaptersController { + + private ArrayList mLocalChapters;//本地章节 + private ArrayList mNetChapters;//网络章节 + private Book mBook;//书 + private ChapterService mChapterService; + + public SyncChaptersController(ArrayList localChapters,ArrayList netChapters,Book book){ + mLocalChapters = localChapters; + mNetChapters = netChapters; + mBook = book; + mChapterService = new ChapterService(); + } + + /** + * 开始同步 + */ + public void sync(){ + + } + + /** + * 更新所有章节 + * + * @param newChapters + */ + private void updateAllOldChapterData(ArrayList newChapters) { + int i; + for (i = 0; i < mLocalChapters.size() && i < newChapters.size(); i++) { + Chapter oldChapter = mLocalChapters.get(i); + Chapter newChapter = newChapters.get(i); + if (!oldChapter.getTitle().equals(newChapter.getTitle())) { + oldChapter.setTitle(newChapter.getTitle()); + oldChapter.setUrl(newChapter.getUrl()); + oldChapter.setContent(null); + mChapterService.updateEntity(oldChapter); + } + } + if (mLocalChapters.size() < newChapters.size()) { + for (int j = mLocalChapters.size(); j < newChapters.size(); j++) { + + mLocalChapters.add(newChapters.get(j)); + mChapterService.addChapter(newChapters.get(j), null); + } + } else if (mLocalChapters.size() > newChapters.size()) { + for (int j = newChapters.size(); j < mLocalChapters.size(); j++) { + mChapterService.deleteEntity(mLocalChapters.get(j)); + } + mLocalChapters.subList(0, newChapters.size()); + } + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGe44ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGe44ReadCrawler.java new file mode 100644 index 0000000..57673d0 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGe44ReadCrawler.java @@ -0,0 +1,144 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import xyz.fycz.myreader.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.greendao.entity.Chapter; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static xyz.fycz.myreader.webapi.BaseApi.getCommonReturnHtmlStringApi; + + + +public class BiQuGe44ReadCrawler implements ReadCrawler, BookInfoCrawler{ + public static final String NAME_SPACE = "https://www.wqge.cc"; + public static final String NOVEL_SEARCH = "https://www.wqge.cc/modules/article/search.php"; + public static final String SEARCH_KEY = "searchkey"; + public static final String CHARSET = "GBK"; + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public String getSearchKey() { + return SEARCH_KEY; + } + + /** + * 从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); + String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content"); + Element divList = doc.getElementById("list"); + String lastTile = null; + int i = 0; + Elements elementsByTag = divList.getElementsByTag("dd"); + for (int j = 9; j < elementsByTag.size(); j++) { + Element dd = elementsByTag.get(j); + Elements as = dd.getElementsByTag("a"); + if (as.size() > 0) { + Element a = as.get(0); + String title = a.text() ; + if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) { + continue; + } + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + String url = readUrl + a.attr("href"); + chapter.setUrl(url); + chapters.add(chapter); + lastTile = title; + } + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByTag("table"); + Element div = divs.get(0); + Elements elementsByTag = div.getElementsByTag("tr"); + for (int i = 1; i < elementsByTag.size(); i++) { + Element element = elementsByTag.get(i); + Book book = new Book(); + Elements info = element.getElementsByTag("td"); + book.setName(info.get(0).text()); + book.setChapterUrl(NAME_SPACE + info.get(0).getElementsByTag("a").attr("href")); + book.setAuthor(info.get(2).text()); + book.setNewestChapterTitle(info.get(1).text()); + book.setSource(BookSource.biquge44.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.getElementById("fmimg"); + book.setImgUrl(img.getElementsByTag("img").get(0).attr("src")); + Element desc = doc.getElementById("intro"); + book.setDesc(desc.getElementsByTag("p").get(0).text()); + Element type = doc.getElementsByClass("con_top").get(0); + book.setType(type.getElementsByTag("a").get(2).text()); + return book; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGeReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGeReadCrawler.java new file mode 100644 index 0000000..e05d957 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/BiQuGeReadCrawler.java @@ -0,0 +1,215 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import xyz.fycz.myreader.callback.ResultCallback; +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.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class BiQuGeReadCrawler implements ReadCrawler, BookInfoCrawler { + private static final String NAME_SPACE = "https://www.52bqg.com"; + private static final String NOVEL_SEARCH = "https://www.52bqg.com/modules/article/search.php"; + private static final String SEARCH_KEY = "searchkey"; + private static final String CHARSET = "GBK"; + private ReadCrawler rc = new TianLaiReadCrawler(); + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public String getSearchKey() { + return SEARCH_KEY; + } + + @Override + public String getCharset() { + return CHARSET; + } + + /** + * 获取书城小说分类列表 + * + * @param html + * @return + */ + public static List getBookTypeList(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByClass("nav"); + if (divs.size() > 0) { + Elements uls = divs.get(0).getElementsByTag("ul"); + if (uls.size() > 0) { + for (Element li : uls.get(0).children()) { + Element a = li.child(0); + BookType bookType = new BookType(); + bookType.setTypeName(a.attr("title")); + bookType.setUrl(a.attr("href")); + if (!bookType.getTypeName().contains("小说") || bookType.getTypeName().contains("排行")) continue; + if (!StringHelper.isEmpty(bookType.getTypeName())) { + bookTypes.add(bookType); + } + } + } + + } + return bookTypes; + } + + /** + * 获取某一分类小说排行榜列表 + * + * @param html + * @return + */ + public static List getBookRankList(String html) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByClass("r"); + if (divs.size() > 0) { + Elements uls = divs.get(0).getElementsByTag("ul"); + if (uls.size() > 0) { + for (Element li : uls.get(0).children()) { + Book book = new Book(); + Element scanS1 = li.getElementsByClass("s1").get(0); + Element scanS2 = li.getElementsByClass("s2").get(0); + Element scanS5 = li.getElementsByClass("s5").get(0); + book.setType(scanS1.html().replace("[", "").replace("]", "")); + Element a = scanS2.getElementsByTag("a").get(0); + book.setName(a.attr("title")); + book.setChapterUrl(a.attr("href")); + book.setAuthor(scanS5.html()); + book.setSource(BookSource.biquge.toString()); + books.add(book); + } + } + } + + return books; + + } + + + @Override + public String getContentFormHtml(String html) { + return rc.getContentFormHtml(html); + } + + @Override + public ArrayList getChaptersFromHtml(String html) { + return rc.getChaptersFromHtml(html); + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + final 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); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } else { + Elements divs = doc.getElementsByClass("novelslistss"); + Element div = divs.get(0); + Elements elementsByTag = div.getElementsByTag("li"); + for (Element element : elementsByTag) { + Book book = new Book(); + Elements info = element.getElementsByTag("span"); + book.setName(info.get(1).text()); + book.setChapterUrl(info.get(1).getElementsByTag("a").attr("href")); + book.setAuthor(info.get(3).text()); + book.setNewestChapterTitle(info.get(2).text()); + book.setSource(BookSource.biquge.toString()); + book.setType(info.get(0).text()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + } + return books; + } + + /** + * 获取小说详细信息 + * + * @param html + * @return + */ + public Book getBookInfo(String html, Book book) { + //小说源 + book.setSource(BookSource.biquge.toString()); + Document doc = Jsoup.parse(html); + //图片url + Element divImg = doc.getElementById("fmimg"); + Element img = divImg.getElementsByTag("img").get(0); + book.setImgUrl(img.attr("src")); + Element divInfo = doc.getElementById("info"); + + //书名 + Element h1 = divInfo.getElementsByTag("h1").get(0); + book.setName(h1.html()); + + Elements ps = divInfo.getElementsByTag("p"); + + //作者 + Element p0 = ps.get(0); + Element a = p0.getElementsByTag("a").get(0); + book.setAuthor(a.html()); + + //更新时间 + Element p2 = ps.get(2); + + Pattern pattern = Pattern.compile("更新时间:(.*) "); + Matcher matcher = pattern.matcher(p2.html()); + if (matcher.find()) { + book.setUpdateDate(matcher.group(1)); + } + + //最新章节 + Element p3 = ps.get(3); + a = p3.getElementsByTag("a").get(0); + book.setNewestChapterTitle(a.attr("title")); + book.setNewestChapterUrl(book.getChapterUrl() + a.attr("href")); + + //类型 + String type = doc.select("meta[property=og:novel:category]").attr("content"); + book.setType(type); + + //简介 + Element divIntro = doc.getElementById("intro"); + book.setDesc(Html.fromHtml(divIntro.html()).toString()); + return book; + + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/BookInfoCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/BookInfoCrawler.java new file mode 100644 index 0000000..3d96e87 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/BookInfoCrawler.java @@ -0,0 +1,12 @@ +package xyz.fycz.myreader.crawler; + +import xyz.fycz.myreader.greendao.entity.Book; + +/** + * @author fengyue + * @date 2020/5/19 19:50 + */ +public interface BookInfoCrawler { + String getCharset(); + Book getBookInfo(String html, Book book); +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/FYReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/FYReadCrawler.java new file mode 100644 index 0000000..219fc1b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/FYReadCrawler.java @@ -0,0 +1,121 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.entity.Book; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 风月小说网html解析工具 + */ + +public class FYReadCrawler implements ReadCrawler{ + public static final String NAME_SPACE = "https://novel.fycz.xyz"; + public static final String NOVEL_SEARCH = "https://novel.fycz.xyz/search.html"; + public static final String SEARCH_KEY = "keyword"; + public static final String 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 String getSearchKey() { + return SEARCH_KEY; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Pattern pattern = Pattern.compile("
[\\s\\S]*?
"); + Matcher matcher = pattern.matcher(html); + if (matcher.find()) { + String content = Html.fromHtml(matcher.group(0)).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); + Elements nodes = doc.getElementsByClass("cate-list"); + Element node = nodes.get(0); + Elements divs = node.getElementsByTag("a"); + for (int i = 0; i < divs.size(); i++) { + Chapter chapter = new Chapter(); + Element div = divs.get(i); + chapter.setNumber(i); + chapter.setUrl(div.attr("href").replace("https://novel.fycz.xyz","")); + chapter.setTitle(div.getElementsByTag("span").get(0).text()); + chapters.add(chapter); + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Elements nodes = doc.getElementsByClass("secd-rank-list"); + for (Element div : nodes) { + Book book = new Book(); + book.setName(div.getElementsByTag("a").get(1).text()); + book.setAuthor(div.getElementsByTag("a").get(2).text()); + book.setType(div.getElementsByTag("a").get(3).text()); + book.setNewestChapterTitle(div.getElementsByTag("a").get(4).text()); + book.setNewestChapterUrl(div.getElementsByTag("a").get(4).attr("href")); + Element img = div.getElementsByTag("img").get(0); + book.setImgUrl(img.attr("data-original")); + Element chapterUrl = div.getElementsByTag("a").get(1); + book.setChapterUrl(chapterUrl.attr("href")); + book.setDesc(div.getElementsByTag("p").get(1).text()); + book.setUpdateDate(div.getElementsByTag("span").get(1).text().replace("|", "")); + book.setSource(BookSource.fynovel.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + return books; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler.java new file mode 100644 index 0000000..0128e03 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler.java @@ -0,0 +1,139 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; +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.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; + +import java.util.ArrayList; + +/** + * @author fengyue + * @date 2020/5/19 19:50 + */ +public class PinShuReadCrawler implements ReadCrawler, BookInfoCrawler { + public static final String NAME_SPACE = "https://www.vodtw.com"; + public static final String NOVEL_SEARCH = "https://www.vodtw.com/Book/Search.aspx"; + public static final String SEARCH_KEY = "SearchKey"; + public static final String CHARSET = "gbk"; + + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public String getSearchKey() { + return SEARCH_KEY; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("BookText"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " ").replace("品书网", "") + .replace("手机阅读", ""); + return StringHelper.IgnoreCaseReplace(content, "www.vodtw.com", ""); + } 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").replace("index.html", ""); + Element divList = doc.getElementsByClass("insert_list").get(0); + String lastTile = null; + int i = 0; + Elements elementsByTag = divList.getElementsByTag("a"); + for (Element a : elementsByTag) { + String title = a.text(); + if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) { + continue; + } + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + String url = readUrl + a.attr("href"); + chapter.setUrl(url.replace(".la", ".com")); + chapters.add(chapter); + lastTile = title; + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementById("Content"); + Elements elementsSelected = div.select("[id=CListTitle]"); + for (Element element : elementsSelected) { + Book book = new Book(); + Elements info = element.getElementsByTag("a"); + book.setName(info.get(0).text()); + book.setChapterUrl(NAME_SPACE + info.get(0).attr("href")); + book.setAuthor(info.get(1).text()); + book.setType(info.get(3).text()); + book.setNewestChapterTitle(info.get(4).text()); + book.setSource(BookSource.pinshu.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.getElementsByClass("bookpic").get(0); + book.setImgUrl(img.getElementsByTag("img").get(0).attr("src")); + Element desc = doc.getElementsByClass("bookintro").get(0); + book.setDesc(desc.text()); + return book; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler2.java b/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler2.java new file mode 100644 index 0000000..9ebb22a --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/PinShuReadCrawler2.java @@ -0,0 +1,145 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; +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.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; + +import java.util.ArrayList; + +/** + * @author fengyue + * @date 2020/5/19 19:50 + */ +public class PinShuReadCrawler2 implements ReadCrawler, BookInfoCrawler { + public static final String NAME_SPACE = "https://www.vodtw.la"; + public static final String NOVEL_SEARCH = "https://www.vodtw.la/search.html"; + public static final String SEARCH_KEY = "q"; + public static final String 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 String getSearchKey() { + return SEARCH_KEY; + } + + /** + * 从html中获取章节正文 + * + * @param html + * @return + */ + public String getContentFormHtml(String html) { + Document doc = Jsoup.parse(html); + Element divContent = doc.getElementById("BookText"); + if (divContent != null) { + String content = Html.fromHtml(divContent.html()).toString(); + char c = 160; + String spaec = "" + c; + content = content.replace(spaec, " ").replace("品书网", "") + .replace("手机阅读", ""); + return StringHelper.IgnoreCaseReplace(content, "www.vodtw.com", ""); + } 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").replace("index.html", ""); + Element divList = doc.getElementsByClass("insert_list").get(0); + String lastTile = null; + int i = 0; + Elements elementsByTag = divList.getElementsByTag("a"); + for (Element a : elementsByTag) { + String title = a.text(); + if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) { + continue; + } + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + String url = readUrl + a.attr("href"); + chapter.setUrl(url); + chapters.add(chapter); + lastTile = title; + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementsByClass("booklist").first(); + Elements divs = div.getElementsByClass("clearfix"); + for (Element element : divs) { + Book book = new Book(); + Elements info = element.getElementsByTag("a"); + book.setName(info.get(1).text()); + book.setChapterUrl(NAME_SPACE + info.get(0).attr("href")); + book.setSource(BookSource.pinshu.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.getElementsByClass("bookimg").get(0); + book.setImgUrl(NAME_SPACE + img.getElementsByTag("img").get(0).attr("src")); + + String author = doc.select("meta[property=og:novel:author]").attr("content"); + String type = doc.select("meta[property=og:novel:category]").attr("content"); + String desc = doc.select("meta[property=og:description]").attr("content"); + String newestChapterTitle = doc.select("meta[property=og:novel:latest_chapter_name]").attr("content"); + + book.setAuthor(author); + book.setType(type); + book.setDesc(desc); + book.setNewestChapterTitle(newestChapterTitle); + + return book; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/QiDianRankList.java b/app/src/main/java/xyz/fycz/myreader/crawler/QiDianRankList.java new file mode 100644 index 0000000..a18110b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/QiDianRankList.java @@ -0,0 +1,77 @@ +package xyz.fycz.myreader.crawler; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.util.StringHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author fengyue + * @date 2020/5/27 11:17 + */ +public class QiDianRankList { + + public static final String[] TYPE_NAME = { + "月票榜", "畅销榜", "阅读榜", "推荐榜", "收藏榜" + }; + + /** + * 获取书城小说分类列表 + * + * @param html + * @return + */ + public static List getBookTypeList(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByClass("more"); + for (int i = 0; i < 5; i++) { + BookType bookType = new BookType(); + bookType.setTypeName(TYPE_NAME[i]); + bookType.setUrl("https:" + divs.get(i).attr("href")); + bookTypes.add(bookType); + } + return bookTypes; + } + + /** + * 获取某一分类小说排行榜列表 + * + * @param html + * @return + */ + public static List getBookRankList(String html) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Elements lis = doc.getElementsByTag("li"); + if (lis.size() > 0) { + Elements bookLis = lis.select("data-rid"); + if (bookLis.size() > 0) { + for (Element li : bookLis) { + Book book = new Book(); + String imgSrc = li.getElementsByClass("book-img-box").get(0) + .getElementsByTag("img").attr("src"); + book.setImgUrl("https:" + imgSrc); + Element bookInfo = li.getElementsByClass("book-mid-info").get(0); + Elements tagA = bookInfo.getElementsByTag("a"); + book.setName(tagA.get(0).text()); + book.setAuthor(tagA.get(1).text()); + book.setType(tagA.get(2).text()); + book.setNewestChapterTitle(tagA.get(3).text()); + String desc = bookInfo.getElementsByClass("intro").get(0).text(); + book.setDesc(desc); + books.add(book); + } + } + } + return books; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawler.java new file mode 100644 index 0000000..2afef4b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawler.java @@ -0,0 +1,23 @@ +package xyz.fycz.myreader.crawler; + +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; + +import java.util.ArrayList; + +/** + * @author fengyue + * @date 2020/4/28 16:18 + */ + +public interface ReadCrawler { + String getSearchLink(); + String getCharset(); + String getNameSpace(); + String getSearchKey(); + String getContentFormHtml(String html); + ArrayList getChaptersFromHtml(String html); + ConcurrentMultiValueMap getBooksFromSearchHtml(String html); +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawlerUtil.java b/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawlerUtil.java new file mode 100644 index 0000000..2acfde1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/ReadCrawlerUtil.java @@ -0,0 +1,23 @@ +package xyz.fycz.myreader.crawler; + + +import java.util.ResourceBundle; + +/** + * @author fengyue + * @date 2020/5/17 11:45 + */ +public class ReadCrawlerUtil { + private ReadCrawlerUtil() { + } + public static ReadCrawler getReadCrawler(String bookSource){ + ResourceBundle rb = ResourceBundle.getBundle("bookcrawler"); + try{ + String readCrawlerPath = rb.getString(bookSource); + Class clz = Class.forName(readCrawlerPath); + return (ReadCrawler) clz.newInstance(); + }catch (Exception e){ + return new FYReadCrawler(); + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/crawler/TianLaiReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/crawler/TianLaiReadCrawler.java new file mode 100644 index 0000000..1796922 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/crawler/TianLaiReadCrawler.java @@ -0,0 +1,154 @@ +package xyz.fycz.myreader.crawler; + +import android.text.Html; + +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.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; + +import java.util.ArrayList; + +/** + * 天籁小说网html解析工具 + * Created by zhao on 2017/7/24. + */ + +public class TianLaiReadCrawler implements ReadCrawler{ + public static final String NAME_SPACE = "https://www.23txt.com"; + public static final String NOVEL_SEARCH = "https://www.23txt.com/search.php"; + public static final String SEARCH_KEY = "q"; + public static final String CHARSET = "GBK"; + @Override + public String getSearchLink() { + return NOVEL_SEARCH; + } + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getNameSpace() { + return NAME_SPACE; + } + + @Override + public String getSearchKey() { + return SEARCH_KEY; + } + + /** + * 从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, " "); + content = content.replace("一秒记住【笔趣阁 www.52bqg.com】,精彩小说无弹窗免费阅读!", ""); + 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 divList = doc.getElementById("list"); + Element dl = divList.getElementsByTag("dl").get(0); + String lastTile = null; + int i = 0; + for(Element dd : dl.getElementsByTag("dd")){ + Elements as = dd.getElementsByTag("a"); + if (as.size() > 0) { + Element a = as.get(0); + String title = a.html(); + if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) { + continue; + } + Chapter chapter = new Chapter(); + chapter.setNumber(i++); + chapter.setTitle(title); + String url = a.attr("href"); + if (url.contains("files/article/html")) { + url = NAME_SPACE + url; + }else { + url = readUrl + url; + } + chapter.setUrl(url); + chapters.add(chapter); + lastTile = title; + } + + } + return chapters; + } + + /** + * 从搜索html中得到书列表 + * + * @param html + * @return + */ + public ConcurrentMultiValueMap getBooksFromSearchHtml(String html) { + ConcurrentMultiValueMap books = new ConcurrentMultiValueMap<>(); + Document doc = Jsoup.parse(html); +// Element node = doc.getElementById("results"); +// for (Element div : node.children()) { + Elements divs = doc.getElementsByClass("result-list"); + Element div = divs.get(0); +// if (!StringHelper.isEmpty(div.className()) && div.className().equals("result-list")) { + for (Element element : div.children()) { + Book book = new Book(); + Element img = element.child(0).child(0).child(0); + book.setImgUrl(img.attr("src")); + Element title = element.getElementsByClass("result-item-title result-game-item-title").get(0); + book.setName(title.child(0).attr("title")); + book.setChapterUrl(NAME_SPACE + title.child(0).attr("href")); + Element desc = element.getElementsByClass("result-game-item-desc").get(0); + book.setDesc(desc.text()); + Element info = element.getElementsByClass("result-game-item-info").get(0); + for (Element element1 : info.children()) { + String infoStr = element1.text(); + if (infoStr.contains("作者:")) { + book.setAuthor(infoStr.replace("作者:", "").replace(" ", "")); + } else if (infoStr.contains("类型:")) { + book.setType(infoStr.replace("类型:", "").replace(" ", "")); + } else if (infoStr.contains("更新时间:")) { + book.setUpdateDate(infoStr.replace("更新时间:", "").replace(" ", "")); + } else { + Element newChapter = element1.child(1); + book.setNewestChapterUrl(newChapter.attr("href")); + book.setNewestChapterTitle(newChapter.text()); + } + } + book.setSource(BookSource.tianlai.toString()); + SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor()); + books.add(sbb, book); + } + + return books; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/creator/APPDownloadTip.java b/app/src/main/java/xyz/fycz/myreader/creator/APPDownloadTip.java new file mode 100644 index 0000000..cee1252 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/creator/APPDownloadTip.java @@ -0,0 +1,199 @@ +package xyz.fycz.myreader.creator; + + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; +import androidx.core.content.FileProvider; +import android.view.View; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.ui.home.MainActivity; +import xyz.fycz.myreader.ui.home.bookcase.BookcaseFragment; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * @author fengyue + * @date 2020/5/20 20:50 + */ + +public class APPDownloadTip { + + private String url; + private BookcaseFragment mBookcaseFragment; + private MainActivity activity; + private boolean isForceUpdate; + + public APPDownloadTip(String url, BookcaseFragment mBookcaseFragment, MainActivity activity, boolean isForceUpdate) { + this.url = url; + this.mBookcaseFragment = mBookcaseFragment; + this.activity = activity; + this.isForceUpdate = isForceUpdate; + } + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + mBookcaseFragment.getTvDownloadTip().setText("获取下载链接失败,请前往浏览器下载!"); + mBookcaseFragment.getRlDownloadTip().setVisibility(View.GONE); + break; + case 2: + mBookcaseFragment.getTvDownloadTip().setText("连接中..."); + break; + case 3: + updateDownloadPro((double) msg.obj); + break; + case 4: + mBookcaseFragment.getRlDownloadTip().setVisibility(View.GONE); + break; + } + } + }; + + public void downloadApp(){ + mBookcaseFragment.getTvStopDownload().setVisibility(View.GONE); + mBookcaseFragment.getRlDownloadTip().setVisibility(View.VISIBLE); + mBookcaseFragment.getPbDownload().setProgress(0); + mBookcaseFragment.getTvDownloadTip().setText("正在获取下载链接..."); + CommonApi.getUrl(url, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + final String downloadUrl = (String) o; + if (downloadUrl == null) { + error(); + return; + } + MyApplication.getApplication().newThread(new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + HttpURLConnection con = null; + InputStream is = null; + FileOutputStream fos = null; + File appFile = null; + try { + URL webUrl = new URL(downloadUrl); + mHandler.sendMessage(mHandler.obtainMessage(2)); + con = (HttpURLConnection) webUrl.openConnection(); + is = con.getInputStream(); + String filePath = APPCONST.UPDATE_APK_FILE_DIR + "FYReader.apk.temp"; + appFile = FileUtils.getFile(filePath); + fos = new FileOutputStream(appFile); + byte[] tem = new byte[1024]; + long alreadyLen = 0; + long fileLength = con.getContentLength(); + int len; + double progress; + while ((len = is.read(tem)) != -1) { + fos.write(tem, 0, len); + alreadyLen += len; + progress = alreadyLen * 1.0f * 100f / fileLength; + mHandler.sendMessage(mHandler.obtainMessage(3, progress)); + } + fos.flush(); + if (fileLength == appFile.length()) { + String newPath = filePath.replace(".temp", ""); + final File newFile = new File(newPath); + if (appFile.renameTo(newFile)){ + mHandler.sendMessage(mHandler.obtainMessage(4)); + DialogCreator.createCommonDialog(activity, "提示", "风月读书下载完成,安装包路径:" + newPath, + !isForceUpdate, "取消", "立即安装", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (isForceUpdate){ + activity.finish(); + } + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + activity.installProcess(newFile, isForceUpdate); + } + }); + activity.installProcess(newFile, isForceUpdate); + }else { + appFile.delete(); + error(); + } + }else { + appFile.delete(); + error(); + } + } catch (IOException e) { + if (appFile != null) { + appFile.delete(); + } + error(); + e.printStackTrace(); + } finally { + if (con != null) { + con.disconnect(); + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + }); + } + + @Override + public void onError(Exception e) { + error(); + } + }); + } + + + private void error(){ + mHandler.sendMessage(mHandler.obtainMessage(1)); + TextHelper.showText("获取下载链接失败,请前往浏览器下载!"); + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + activity.startActivity(intent); + if (isForceUpdate) { + activity.finish(); + } + } + @SuppressLint({"SetTextI18n"}) + private void updateDownloadPro(double progress) { + mBookcaseFragment.getPbDownload().setProgress((int) progress); + //保留两位小数 + //mBookcaseFragment.getTvDownloadTip().setText("正在下载风月读书最新版本...[" + String.format("%.2f", progress) + "%]"); + mBookcaseFragment.getTvDownloadTip().setText("正在下载风月读书最新版本...[" + (int) progress + "%]"); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/creator/ChangeSourceDialog.java b/app/src/main/java/xyz/fycz/myreader/creator/ChangeSourceDialog.java new file mode 100644 index 0000000..a67f7d1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/creator/ChangeSourceDialog.java @@ -0,0 +1,143 @@ +package xyz.fycz.myreader.creator; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.crawler.*; +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + + +/** + * @author fengyue + * @date 2020/5/19 17:35 + */ +public class ChangeSourceDialog { + private Context context; + private Book mBook; + private ConcurrentMultiValueMap mBooks = new ConcurrentMultiValueMap<>(); + private ArrayList aBooks; + private SearchBookBean sbb; + private boolean isSearchSuccess; + private int threadCount; + private ResultCallback rc; + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + if (threadCount == 0) { + createaBooks(); + } + break; + case 2: + if (!isSearchSuccess && threadCount == 0) { + rc.onError(new Exception()); + }else if (threadCount == 0){ + createaBooks(); + } + break; + } + } + }; + + public ChangeSourceDialog(Context context, Book mBook) { + this.context = context; + this.mBook = mBook; + sbb = new SearchBookBean(mBook.getName(), mBook.getAuthor()); + } + + public void init(ResultCallback rc) { + this.rc = rc; + getData(); + } + + /** + * 获取搜索数据 + */ + private void getData() { + mBooks.clear(); + threadCount = 4; + isSearchSuccess = false; + searchBookByCrawler(new BiQuGe44ReadCrawler(), ""); + searchBookByCrawler(new TianLaiReadCrawler(), ""); + searchBookByCrawler(new BiQuGeReadCrawler(), "gbk"); + searchBookByCrawler(new PinShuReadCrawler(), "gbk"); + } + + private void searchBookByCrawler(final ReadCrawler rc, String charset) { + String searchKey = mBook.getName(); + if (charset.toLowerCase().equals("gbk")) { + try { + searchKey = URLEncoder.encode(mBook.getName(), charset); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + CommonApi.search(searchKey, rc, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + final ConcurrentMultiValueMap cmvm = + (ConcurrentMultiValueMap) o; + if (rc instanceof BookInfoCrawler) { + BookInfoCrawler bic = (BookInfoCrawler) rc; + final List aBooks = cmvm.getValues(sbb); + if (aBooks != null) { + for (int i = 0; i < aBooks.size(); i++) { + Book book = aBooks.get(i); + final int finalI = i; + CommonApi.getBookInfo(book, bic, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + if (finalI == aBooks.size() - 1) { + mBooks.addAll(cmvm); + isSearchSuccess = true; + threadCount--; + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + } + + @Override + public void onError(Exception e) { + threadCount--; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + }); + + } + } else { + isSearchSuccess = true; + threadCount--; + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + } else { + mBooks.addAll(cmvm); + isSearchSuccess = true; + threadCount--; + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + } + + @Override + public void onError(Exception e) { + threadCount--; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + }); + } + + private void createaBooks() { + aBooks = (ArrayList) mBooks.getValues(sbb); + rc.onFinish(aBooks, 1); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/creator/DialogCreator.java b/app/src/main/java/xyz/fycz/myreader/creator/DialogCreator.java new file mode 100644 index 0000000..46b6a94 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/creator/DialogCreator.java @@ -0,0 +1,751 @@ +package xyz.fycz.myreader.creator; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +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.ReadStyle; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.util.BrightUtil; +import xyz.fycz.myreader.util.StringHelper; + +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) { + 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() { + @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); + + //阅读背景风格 + 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(new View.OnClickListener() { + @Override + public void onClick(View 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); + + 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); + } + } + + + /** + * 阅读设置对话框 + * + * @param context + * @param isDayStyle + * @param chapterProgress + * @param backListener + * @param lastChapterListener + * @param nextChapterListener + * @param chapterListListener + * @param onClickNightAndDayListener + * @param settingListener + * @return + */ + public static Dialog createReadSetting(final Context context, final boolean isDayStyle, int chapterProgress, int maxProcess, final Book mBook, Chapter mChapter, + View.OnClickListener backListener, + View.OnClickListener refreshListener, + View.OnClickListener bookMarkListener, + final OnSkipChapterListener lastChapterListener, + final OnSkipChapterListener nextChapterListener, + View.OnClickListener chapterListListener, + final OnClickNightAndDayListener onClickNightAndDayListener, + View.OnClickListener settingListener, + SeekBar.OnSeekBarChangeListener onSeekBarChangeListener, + View.OnClickListener voiceOnClickListener, + final OnClickDownloadAllChapterListener onClickDownloadAllChapterListener) { + final Dialog dialog = new Dialog(context, R.style.jmui_default_dialog_style); + final View view = LayoutInflater.from(context).inflate(R.layout.dialog_read_setting, null); + dialog.setContentView(view); + LinearLayout llBack = (LinearLayout) view.findViewById(R.id.ll_title_back); + LinearLayout llBook = view.findViewById(R.id.ll_book_name); + TextView tvBookName = view.findViewById(R.id.tv_book_name_top); + ImageView ivRefresh = view.findViewById(R.id.iv_refresh); + ImageView ivBookMark = view.findViewById(R.id.iv_book_mark); + ImageView ivMore = view.findViewById(R.id.iv_more); + LinearLayout llChapter = view.findViewById(R.id.ll_chapter_view); + final TextView tvChapterTitle = view.findViewById(R.id.tv_chapter_title_top); + final TextView tvChapterUrl = view.findViewById(R.id.tv_chapter_url); + TextView tvLastChapter = (TextView) view.findViewById(R.id.tv_last_chapter); + TextView tvNextChapter = (TextView) view.findViewById(R.id.tv_next_chapter); + final SeekBar sbChapterProgress = (SeekBar) view.findViewById(R.id.sb_read_chapter_progress); + LinearLayout llChapterList = (LinearLayout) view.findViewById(R.id.ll_chapter_list); + LinearLayout llNightAndDay = (LinearLayout) view.findViewById(R.id.ll_night_and_day); + LinearLayout llSetting = (LinearLayout) view.findViewById(R.id.ll_setting); + final ImageView ivNightAndDay = (ImageView) view.findViewById(R.id.iv_night_and_day); + final TextView tvNightAndDay = (TextView) view.findViewById(R.id.tv_night_and_day); + ImageView ivVoice = (ImageView)view.findViewById(R.id.iv_voice_read); + + view.findViewById(R.id.rl_title_view).setOnClickListener(null); + view.findViewById(R.id.ll_bottom_view).setOnClickListener(null); + + Window window = dialog.getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + if (Build.VERSION.SDK_INT >= 21) { + window.setStatusBarColor(dialog.getContext().getResources().getColor(R.color.sys_dialog_setting_bg)); + } + + view.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + dialog.dismiss(); + return false; + } + }); + + tvBookName.setText(mBook.getName()); + llBook.setOnClickListener(v -> { + /*Intent intent = new Intent(context, BookInfoActivity.class); + intent.putExtra(APPCONST.BOOK, mBook); + context.startActivity(intent);*/ + }); + //刷新 + ivRefresh.setOnClickListener(refreshListener); + String url = mChapter.getUrl(); + if ("null".equals(mBook.getSource()) || BookSource.fynovel.equals(mBook.getSource())) { + if (!url.contains("novel.fycz.xyz")) { + url = URLCONST.nameSpace_FY + url; + } + } + + //书签 + ivBookMark.setOnClickListener(bookMarkListener); + + tvChapterTitle.setText(mChapter.getTitle()); + tvChapterUrl.setText(StringHelper.isEmpty(url) ? mChapter.getId() : url); + //跳转对应章节 + llChapter.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String url = tvChapterUrl.getText().toString(); + if (!"本地书籍".equals(mBook.getType()) && !StringHelper.isEmpty(url)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri uri = Uri.parse(url); + intent.setData(uri); + context.startActivity(intent); + } + } + }); + + + if (!isDayStyle) { + ivNightAndDay.setImageResource(R.mipmap.z4); + tvNightAndDay.setText(context.getString(R.string.day)); + } + + llBack.setOnClickListener(backListener); + tvLastChapter.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (lastChapterListener != null){ + lastChapterListener.onClick(tvChapterTitle, tvChapterUrl, sbChapterProgress); + } + } + }); + tvNextChapter.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (nextChapterListener != null){ + nextChapterListener.onClick(tvChapterTitle, tvChapterUrl, sbChapterProgress); + } + } + }); + sbChapterProgress.setProgress(chapterProgress); + sbChapterProgress.setMax(maxProcess); + llChapterList.setOnClickListener(chapterListListener); + llSetting.setOnClickListener(settingListener); + sbChapterProgress.setOnSeekBarChangeListener(onSeekBarChangeListener); + ivVoice.setOnClickListener(voiceOnClickListener); + //日夜切换 + llNightAndDay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + boolean isDay; + if (tvNightAndDay.getText().toString().equals(context.getString(R.string.day))) { + isDay = false; + ivNightAndDay.setImageResource(R.mipmap.ao); + tvNightAndDay.setText(context.getString(R.string.night)); + } else { + isDay = true; + ivNightAndDay.setImageResource(R.mipmap.z4); + tvNightAndDay.setText(context.getString(R.string.day)); + } + if (onClickNightAndDayListener != null) { + onClickNightAndDayListener.onClick(dialog, view, isDay); + } + } + }); + + //缓存章节 + final TextView tvDownloadProgress = (TextView)view.findViewById(R.id.tv_download_progress); + LinearLayout llDonwloadCache = (LinearLayout)view.findViewById(R.id.ll_download_cache); + llDonwloadCache.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onClickDownloadAllChapterListener != null){ + onClickDownloadAllChapterListener.onClick(dialog,v,tvDownloadProgress); + } + } + }); + + dialog.setCancelable(true); + dialog.setCanceledOnTouchOutside(true); + return dialog; + } + + + + /** + * 创建一个普通对话框(包含确定、取消按键) + * + * @param context + * @param title + * @param mesage + * @param isCancelable 是否允许返回键取消 + * @param positiveListener 确定按键动作 + * @param negativeListener 取消按键动作 + * @return + */ + public static AlertDialog createCommonDialog(Context context, String title, String mesage, boolean isCancelable, + DialogInterface.OnClickListener positiveListener, DialogInterface.OnClickListener negativeListener) { + + final AlertDialog.Builder normalDialog = new AlertDialog.Builder(context); +// normalDialog.setIcon(R.drawable.icon_dialog); + normalDialog.setTitle(title); + normalDialog.setCancelable(isCancelable); + normalDialog.setMessage(mesage); + normalDialog.setPositiveButton("确定", positiveListener); + normalDialog.setNegativeButton("取消", negativeListener); + // 显示 + final AlertDialog alertDialog = normalDialog.create(); + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + alertDialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + return alertDialog; + + } + + /** + * 创建一个普通对话框(包含key1、key2按键) + * + * @param context + * @param title + * @param mesage + * @param key1 + * @param key2 + * @param positiveListener key1按键动作 + * @param negativeListener key2按键动作 + */ + public static void createCommonDialog(Context context, String title, String mesage, boolean isCancelable, + String key1, String key2, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + try { + + final AlertDialog.Builder normalDialog = new AlertDialog.Builder(context); +// normalDialog.setIcon(R.drawable.icon_dialog); + normalDialog.setTitle(title); + normalDialog.setCancelable(isCancelable); + if (mesage != null) { + normalDialog.setMessage(mesage); + } + normalDialog.setPositiveButton(key1, positiveListener); + normalDialog.setNegativeButton(key2, negativeListener); + // 显示 +// final AlertDialog alertDialog = normalDialog.create(); + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + try { +// final AlertDialog alertDialog = normalDialog.create(); + normalDialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } +// return alertDialog; + } + + /** + * 单按键对话框 + * + * @param context + * @param title + * @param mesage + * @param key + * @param positiveListener + */ + public static void createCommonDialog(Context context, String title, String mesage, boolean isCancelable, + String key, DialogInterface.OnClickListener positiveListener + ) { + try { + final AlertDialog.Builder normalDialog = new AlertDialog.Builder(context); +// normalDialog.setIcon(R.drawable.icon_dialog); + normalDialog.setTitle(title); + normalDialog.setCancelable(isCancelable); + if (mesage != null) { + normalDialog.setMessage(mesage); + } + normalDialog.setPositiveButton(key, positiveListener); + + // 显示 +// final AlertDialog alertDialog = normalDialog.create(); + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + try { +// final AlertDialog alertDialog = normalDialog.create(); + normalDialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } +// return alertDialog; + } + + + /** + * 创建一个进度对话框(圆形、旋转) + * + * @param context + * @param title + * @param message + * @return + */ + public static ProgressDialog createProgressDialog + (Context context, String title, String message/*, + DialogInterface.OnClickListener positiveListener,DialogInterface.OnClickListener negativeListener*/) { + + final ProgressDialog progressDialog = new ProgressDialog(context); +// normalDialog.setIcon(R.drawable.icon_dialog); + if (!StringHelper.isEmpty(title)) { + progressDialog.setTitle(title); + } + if (!StringHelper.isEmpty(message)) { + progressDialog.setMessage(message); + } + progressDialog.setCancelable(false); + progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + /* progressDialog.setPositiveButton("确定",positiveListener); + progressDialog.setNegativeButton("取消",negativeListener);*/ + // 显示 + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + progressDialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + return progressDialog; + } + + + /** + * 三按键对话框 + * + * @param context + * @param title + * @param msg + * @param btnText1 + * @param btnText2 + * @param btnText3 + * @param positiveListener + * @param neutralListener + * @param negativeListener + * @return + */ + public static void createThreeButtonDialog(Context context, String title, String msg, boolean isCancelable, + String btnText1, String btnText2, String btnText3, + DialogInterface.OnClickListener neutralListener, + DialogInterface.OnClickListener negativeListener, + DialogInterface.OnClickListener positiveListener) { + /* final EditText et = new EditText(context);*/ + try { + final AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setTitle(title); + if (!StringHelper.isEmpty(msg)) { + dialog.setMessage(msg); + } + // 第一个按钮 + dialog.setNeutralButton(btnText1, neutralListener); + // 中间的按钮 + dialog.setNegativeButton(btnText2, negativeListener); + // 第三个按钮 + dialog.setPositiveButton(btnText3, positiveListener); + + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + try { +// final AlertDialog alertDialog = normalDialog.create(); + dialog.show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + dialog.setCancelable(isCancelable); + + // Diglog的显示 + }catch (Exception e){ + e.printStackTrace(); + } + } + + public static void createTipDialog(Context mContext, String message){ + DialogCreator.createCommonDialog(mContext, "提示", + message, true, "知道了", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + } + public static void createTipDialog(Context mContext, String title,String message){ + DialogCreator.createCommonDialog(mContext, title, + message, true, "知道了", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + } + + public interface OnClickPositiveListener { + void onClick(Dialog dialog, View view); + } + + public interface OnClickNegativeListener { + void onClick(Dialog dialog, View view); + } + + /** + * 白天黑夜切换监听 + */ + public interface OnClickNightAndDayListener { + void onClick(Dialog dialog, View view, boolean isDayStyle); + } + + /** + * 阅读style切换监听器 + */ + public interface OnReadStyleChangeListener { + void onChange(ReadStyle readStyle); + } + + public interface OnBrightFollowSystemChangeListener { + void onChange(boolean isFollowSystem); + } + + public interface OnClickDownloadAllChapterListener { + void onClick(Dialog dialog, View view,TextView tvDownloadProgress); + } + + public interface OnPageModeChangeListener { + void onChange(TextView tvPageMode); + } + + public interface OnSkipChapterListener{ + void onClick(TextView chapterTitle, TextView chapterUrl, SeekBar sbReadChapterProgress); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/creator/ListenerInterface.java b/app/src/main/java/xyz/fycz/myreader/creator/ListenerInterface.java new file mode 100644 index 0000000..0cb3767 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/creator/ListenerInterface.java @@ -0,0 +1,12 @@ +package xyz.fycz.myreader.creator; + +/** + * @author fengyue + * @date 2020/7/15 8:02 + */ +public class ListenerInterface { + public interface OnRefreshFinishListener{ + void onRefresh(); + void onFinish(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/BaseImageView.java b/app/src/main/java/xyz/fycz/myreader/custom/BaseImageView.java new file mode 100644 index 0000000..193ac68 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/BaseImageView.java @@ -0,0 +1,113 @@ +package xyz.fycz.myreader.custom; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Xfermode; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import androidx.appcompat.widget.AppCompatImageView; + +import java.lang.ref.WeakReference; + +public abstract class BaseImageView extends AppCompatImageView { + private static final String TAG = BaseImageView.class.getSimpleName(); + + protected Context mContext; + + private static final Xfermode sXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); +// private BitmapShader mBitmapShader; + private Bitmap mMaskBitmap; + private Paint mPaint; + private WeakReference mWeakBitmap; + + public BaseImageView(Context context) { + super(context); + sharedConstructor(context); + } + + public BaseImageView(Context context, AttributeSet attrs) { + super(context, attrs); + sharedConstructor(context); + } + + public BaseImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + sharedConstructor(context); + } + + private void sharedConstructor(Context context) { + mContext = context; + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + + public void invalidate() { + mWeakBitmap = null; + if (mMaskBitmap != null) { mMaskBitmap.recycle(); } + super.invalidate(); + } + + @SuppressLint("DrawAllocation") + @Override + protected void onDraw(Canvas canvas) { + if (!isInEditMode()) { + int i = canvas.saveLayer(0.0f, 0.0f, getWidth(), getHeight(), + null, Canvas.ALL_SAVE_FLAG); + try { + Bitmap bitmap = mWeakBitmap != null ? mWeakBitmap.get() : null; + // Bitmap not loaded. + if (bitmap == null || bitmap.isRecycled()) { + Drawable drawable = getDrawable(); + if (drawable != null) { + // Allocation onDraw but it's ok because it will not always be called. + bitmap = Bitmap.createBitmap(getWidth(), + getHeight(), Bitmap.Config.ARGB_8888); + Canvas bitmapCanvas = new Canvas(bitmap); + drawable.setBounds(0, 0, getWidth(), getHeight()); + drawable.draw(bitmapCanvas); + + // If mask is already set, skip and use cached mask. + if (mMaskBitmap == null || mMaskBitmap.isRecycled()) { + mMaskBitmap = getBitmap(); + } + + // Draw Bitmap. + mPaint.reset(); + mPaint.setFilterBitmap(false); + mPaint.setXfermode(sXfermode); +// mBitmapShader = new BitmapShader(mMaskBitmap, +// Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); +// mPaint.setShader(mBitmapShader); + bitmapCanvas.drawBitmap(mMaskBitmap, 0.0f, 0.0f, mPaint); + + mWeakBitmap = new WeakReference(bitmap); + } + } + + // Bitmap already loaded. + if (bitmap != null) { + mPaint.setXfermode(null); +// mPaint.setShader(null); + canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint); + return; + } + } catch (Exception e) { + System.gc(); + + Log.e(TAG, String.format("Failed to draw, Id :: %s. Error occurred :: %s", getId(), e.toString())); + } finally { + canvas.restoreToCount(i); + } + } else { + super.onDraw(canvas); + } + } + + public abstract Bitmap getBitmap(); + +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/CircleImageView.java b/app/src/main/java/xyz/fycz/myreader/custom/CircleImageView.java new file mode 100644 index 0000000..8c436da --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/CircleImageView.java @@ -0,0 +1,365 @@ +/* + * Copyright 2014 - 2015 Henning Dodenhof + * + * 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. + */ +package xyz.fycz.myreader.custom; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.widget.ImageView; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import xyz.fycz.myreader.R; + + +public class CircleImageView extends androidx.appcompat.widget.AppCompatImageView { + + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + private static final int COLORDRAWABLE_DIMENSION = 2; + + private static final int DEFAULT_BORDER_WIDTH = 0; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT; + private static final boolean DEFAULT_BORDER_OVERLAY = false; + + private final RectF mDrawableRect = new RectF(); + private final RectF mBorderRect = new RectF(); + + private final Matrix mShaderMatrix = new Matrix(); + + private final Paint mBitmapPaint = new Paint(); + private final Paint mBorderPaint = new Paint(); + private final Paint mFillPaint = new Paint(); + + private int mBorderColor = DEFAULT_BORDER_COLOR; + private int mBorderWidth = DEFAULT_BORDER_WIDTH; + private int mFillColor = DEFAULT_FILL_COLOR; + + private Bitmap mBitmap; + private BitmapShader mBitmapShader; + private int mBitmapWidth; + private int mBitmapHeight; + + private float mDrawableRadius; + private float mBorderRadius; + + private ColorFilter mColorFilter; + + private boolean mReady; + private boolean mSetupPending; + private boolean mBorderOverlay; + + public CircleImageView(Context context) { + super(context); + + init(); + } + + public CircleImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircleImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0); + + mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH); + mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR); + mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY); + mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR); + + a.recycle(); + + init(); + } + + private void init() { + super.setScaleType(SCALE_TYPE); + mReady = true; + + if (mSetupPending) { + setup(); + mSetupPending = false; + } + } + + @Override + public ScaleType getScaleType() { + return SCALE_TYPE; + } + + @Override + public void setScaleType(ScaleType scaleType) { + if (scaleType != SCALE_TYPE) { + throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)); + } + } + + @Override + public void setAdjustViewBounds(boolean adjustViewBounds) { + if (adjustViewBounds) { + throw new IllegalArgumentException("adjustViewBounds not supported."); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mBitmap == null) { + return; + } + + if (mFillColor != Color.TRANSPARENT) { + canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mFillPaint); + } + + canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mBitmapPaint); + + if (mBorderWidth != 0) { + canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mBorderRadius, mBorderPaint); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + setup(); + } + + public int getBorderColor() { + return mBorderColor; + } + + public void setBorderColor(@ColorInt int borderColor) { + if (borderColor == mBorderColor) { + return; + } + + mBorderColor = borderColor; + mBorderPaint.setColor(mBorderColor); + invalidate(); + } + + public void setBorderColorResource(@ColorRes int borderColorRes) { + setBorderColor(getContext().getResources().getColor(borderColorRes)); + } + + public int getFillColor() { + return mFillColor; + } + + public void setFillColor(@ColorInt int fillColor) { + if (fillColor == mFillColor) { + return; + } + + mFillColor = fillColor; + mFillPaint.setColor(fillColor); + invalidate(); + } + + public void setFillColorResource(@ColorRes int fillColorRes) { + setFillColor(getContext().getResources().getColor(fillColorRes)); + } + + public int getBorderWidth() { + return mBorderWidth; + } + + public void setBorderWidth(int borderWidth) { + if (borderWidth == mBorderWidth) { + return; + } + + mBorderWidth = borderWidth; + setup(); + } + + public boolean isBorderOverlay() { + return mBorderOverlay; + } + + public void setBorderOverlay(boolean borderOverlay) { + if (borderOverlay == mBorderOverlay) { + return; + } + + mBorderOverlay = borderOverlay; + setup(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + mBitmap = bm; + setup(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + mBitmap = getBitmapFromDrawable(drawable); + setup(); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + super.setImageResource(resId); + mBitmap = getBitmapFromDrawable(getDrawable()); + setup(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + mBitmap = uri != null ? getBitmapFromDrawable(getDrawable()) : null; + setup(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (cf == mColorFilter) { + return; + } + + mColorFilter = cf; + applyColorFilter(); + invalidate(); + } + + @Override + public ColorFilter getColorFilter() { + return mColorFilter; + } + + private void applyColorFilter() { + if (mBitmapPaint != null) { + mBitmapPaint.setColorFilter(mColorFilter); + } + } + + private Bitmap getBitmapFromDrawable(Drawable drawable) { + if (drawable == null) { + return null; + } + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + try { + Bitmap bitmap; + + if (drawable instanceof ColorDrawable) { + bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void setup() { + if (!mReady) { + mSetupPending = true; + return; + } + + if (getWidth() == 0 && getHeight() == 0) { + return; + } + + if (mBitmap == null) { + invalidate(); + return; + } + + mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + + mBitmapPaint.setAntiAlias(true); + mBitmapPaint.setShader(mBitmapShader); + + mBorderPaint.setStyle(Paint.Style.STROKE); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setColor(mBorderColor); + mBorderPaint.setStrokeWidth(mBorderWidth); + + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); + mFillPaint.setColor(mFillColor); + + mBitmapHeight = mBitmap.getHeight(); + mBitmapWidth = mBitmap.getWidth(); + + mBorderRect.set(0, 0, getWidth(), getHeight()); + mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f); + + mDrawableRect.set(mBorderRect); + if (!mBorderOverlay) { + mDrawableRect.inset(mBorderWidth, mBorderWidth); + } + mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f); + + applyColorFilter(); + updateShaderMatrix(); + invalidate(); + } + + private void updateShaderMatrix() { + float scale; + float dx = 0; + float dy = 0; + + mShaderMatrix.set(null); + + if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { + scale = mDrawableRect.height() / (float) mBitmapHeight; + dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f; + } else { + scale = mDrawableRect.width() / (float) mBitmapWidth; + dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f; + } + + mShaderMatrix.setScale(scale, scale); + mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top); + + mBitmapShader.setLocalMatrix(mShaderMatrix); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/ContainsEmojiEditText.java b/app/src/main/java/xyz/fycz/myreader/custom/ContainsEmojiEditText.java new file mode 100644 index 0000000..0589023 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/ContainsEmojiEditText.java @@ -0,0 +1,201 @@ +package xyz.fycz.myreader.custom; + +/** + * Created by zhao on 2017/2/10. + */ + +import android.content.Context; +import android.text.Editable; +import android.text.Selection; +import android.text.Spannable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.widget.EditText; +import android.widget.Toast; +import androidx.appcompat.widget.AppCompatEditText; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ContainsEmojiEditText extends AppCompatEditText { + //输入表情前的光标位置 + private int cursorPos; + //输入表情前EditText中的文本 + private String inputAfterText; + //是否重置了EditText的内容 + private boolean resetText; + + private ArrayList mListeners = null; + + @Override + public void addTextChangedListener(TextWatcher watcher) + { + if (mListeners == null) + { + mListeners = new ArrayList(); + } + mListeners.add(watcher); + + super.addTextChangedListener(watcher); + } + + @Override + public void removeTextChangedListener(TextWatcher watcher) + { + if (mListeners != null) + { + int i = mListeners.indexOf(watcher); + if (i >= 0) + { + mListeners.remove(i); + } + } + + super.removeTextChangedListener(watcher); + } + + public void clearTextChangedListeners() + { + if(mListeners != null) + { + for(TextWatcher watcher : mListeners) + { + super.removeTextChangedListener(watcher); + } + + mListeners.clear(); + mListeners = null; + } + } + + + private Context mContext; + + public ContainsEmojiEditText(Context context) { + super(context); + this.mContext = context; + initEditText(); + } + + public ContainsEmojiEditText(Context context, AttributeSet attrs) { + super(context, attrs); + this.mContext = context; + initEditText(); + } + + public ContainsEmojiEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.mContext = context; + initEditText(); + } + + // 初始化edittext 控件 + private void initEditText() { + addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int before, int count) { + if (!resetText) { + cursorPos = getSelectionEnd(); + // 这里用s.toString()而不直接用s是因为如果用s, + // 那么,inputAfterText和s在内存中指向的是同一个地址,s改变了, + // inputAfterText也就改变了,那么表情过滤就失败了 + inputAfterText= s.toString(); + } + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + if (!resetText) { + if (count >= 2) {//表情符号的字符长度最小为2 + CharSequence input = s.subSequence(cursorPos, cursorPos + count); + if (containsEmoji(input.toString())) { + resetText = true; + Toast.makeText(mContext, "不支持输入Emoji表情符号", Toast.LENGTH_SHORT).show(); + //是表情符号就将文本还原为输入表情符号之前的内容 + setText(inputAfterText); + CharSequence text = getText(); + if (/*text instanceof Spannable*/ text != null) { + Spannable spanText = (Spannable) text; + Selection.setSelection(spanText, text.length()); + } + } + } + } else { + resetText = false; + } + }catch (Exception e){ + e.printStackTrace(); + resetText = false; + } + } + + @Override + public void afterTextChanged(Editable editable) { + + + } + }); + } + + public void setAfterTextChanged(EditText editText){ + + } + + + /** + * 检测是否有emoji表情 + * + * @param source + * @return + */ + public static boolean containsEmoji(String source) { + int len = source.length(); + for (int i = 0; i < len; i++) { + char codePoint = source.charAt(i); + if (!isEmojiCharacter(codePoint)) { //如果不能匹配,则该字符是Emoji表情 + return true; + } + } + return false; + } + + + /** + * 判断是否是Emoji + * + * @param codePoint 比较的单个字符 + * @return + */ + private static boolean isEmojiCharacter(char codePoint) { + return (codePoint == 0x0) || (codePoint == 0x9) || (codePoint == 0xA) || + (codePoint == 0xD) || ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) || + ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) || ((codePoint >= 0x10000) + && (codePoint <= 0x10FFFF)); + } + + public String getInputAfterText() { + return inputAfterText; + } + + public boolean isResetText() { + return resetText; + } + + public boolean isEmoji(String string) { + Pattern p = Pattern.compile("/[\u1F60-\u1F64]|[\u2702-\u27B0]|[\u1F68-\u1F6C]|[\u1F30-\u1F70]|[\u2600-\u26ff]/g"); + Matcher m = p.matcher(string); + return m.matches(); + } + + public boolean isEmoji2(String string) { + Pattern p = Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", + Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(string); + return m.find(); + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/DragAdapter.java b/app/src/main/java/xyz/fycz/myreader/custom/DragAdapter.java new file mode 100644 index 0000000..71eb08f --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/DragAdapter.java @@ -0,0 +1,41 @@ +package xyz.fycz.myreader.custom; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +/** + * Created by zhao on 2017/5/15. + */ + +public abstract class DragAdapter extends BaseAdapter { + + /** + * + * @描述:当从from排序被拖到to排序时的处理方式,请对相应的数据做处理。 + * + * @param from + * @param to + * @作者 [pWX273343] 2015年6月24日 + */ + public abstract void onDataModelMove(int from, int to); + + /** + * 复制View使用的方法,默认直接使用getView方法获取 + * @param position + * @param convertView + * @param parent + * @return + */ + public View copyView(int position, View convertView, ViewGroup parent) { + return null; + } + + /** + * 是否启用copyView方法 + * @return true 使用copyView复制 false 使用getView直接获取镜像 + */ + public boolean isUseCopyView() { + return false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/DragFloatBtnT.java b/app/src/main/java/xyz/fycz/myreader/custom/DragFloatBtnT.java new file mode 100644 index 0000000..b076d50 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/DragFloatBtnT.java @@ -0,0 +1,134 @@ +package xyz.fycz.myreader.custom; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewGroup; +import android.view.animation.BounceInterpolator; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import xyz.fycz.myreader.util.DipPxUtil; + +public class DragFloatBtnT extends FloatingActionButton { + + //移动后的xy坐标 + private float mLastRawx; + private float mLastRawy; + private boolean isDrug = false; + private int mRootMeasuredWidth; + private int padding; + + public DragFloatBtnT(Context context) { + super(context); + init(); + } + + public DragFloatBtnT(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DragFloatBtnT(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + padding = DipPxUtil.dp2px(getContext(), 15); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + //当前手指的坐标 + float mRawx = ev.getRawX(); + float mRawy = ev.getRawY(); + ViewGroup mViewGroup = (ViewGroup) getParent(); + + switch (ev.getAction()) { + //手指按下 + case MotionEvent.ACTION_DOWN: + setPressed(true); + isDrug = false; + //记录按下的位置 + mLastRawx = mRawx; + mLastRawy = mRawy; + if (mViewGroup != null) { + int[] location = new int[2]; + mViewGroup.getLocationInWindow(location); + //获取父布局的宽度 + mRootMeasuredWidth = mViewGroup.getMeasuredWidth(); + } + break; + //手指滑动 + case MotionEvent.ACTION_MOVE: + if (mViewGroup != null) { + int[] location = new int[2]; + mViewGroup.getLocationInWindow(location); + //获取父布局的高度 + int mRootMeasuredHeight = mViewGroup.getMeasuredHeight(); + mRootMeasuredWidth = mViewGroup.getMeasuredWidth(); + //获取父布局顶点的坐标 + int rootTopy = location[1]; + if (mRawx >= 0 && mRawx <= mRootMeasuredWidth && mRawy >= rootTopy && mRawy <= (mRootMeasuredHeight + rootTopy)) { + //手指X轴滑动距离 + float differenceValuex = mRawx - mLastRawx; + //手指Y轴滑动距离 + float differenceValuey = mRawy - mLastRawy; + //判断是否为拖动操作 + if (!isDrug) { + isDrug = !(Math.sqrt(differenceValuex * differenceValuex + differenceValuey * differenceValuey) < 2); + } + //获取手指按下的距离与控件本身X轴的距离 + float ownx = getX(); + //获取手指按下的距离与控件本身Y轴的距离 + float owny = getY(); + //理论中X轴拖动的距离 + float endx = ownx + differenceValuex; + //理论中Y轴拖动的距离 + float endy = owny + differenceValuey; + //X轴可以拖动的最大距离 + float maxx = mRootMeasuredWidth - getWidth(); + //Y轴可以拖动的最大距离 + float maxy = mRootMeasuredHeight - getHeight(); + //X轴边界限制 + endx = endx < 0 ? 0 : Math.min(endx, maxx); + //Y轴边界限制 + endy = endy < 0 ? 0 : Math.min(endy, maxy); + //开始移动 + setX(endx); + setY(endy); + //记录位置 + mLastRawx = mRawx; + mLastRawy = mRawy; + } + } + break; + //手指离开 + case MotionEvent.ACTION_UP: + //恢复按压效果 + setPressed(false); + float center = mRootMeasuredWidth / 2; + //自动贴边 + if (mLastRawx <= center) { + //向左贴边 + DragFloatBtnT.this.animate() + .setInterpolator(new BounceInterpolator()) + .setDuration(500) + .x(padding + 20) + .start(); + } else { + //向右贴边 + DragFloatBtnT.this.animate() + .setInterpolator(new BounceInterpolator()) + .setDuration(500) + .x(mRootMeasuredWidth - getWidth() - padding - 20) + .start(); + } + break; + default: + } + //是否拦截事件 + return isDrug ? isDrug : super.onTouchEvent(ev); + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/custom/DragSortGridView.java b/app/src/main/java/xyz/fycz/myreader/custom/DragSortGridView.java new file mode 100644 index 0000000..163febb --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/DragSortGridView.java @@ -0,0 +1,767 @@ +package xyz.fycz.myreader.custom; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.widget.AdapterView; +import android.widget.FrameLayout; +import android.widget.GridView; +import android.widget.ScrollView; + + +import xyz.fycz.myreader.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Copyright (C), 2008-2015, Huawei Tech. Co., Ltd. + *

+ * Description : 拖动排序布局 + * + * @version V100R001 + * @since V100R001 + */ +@SuppressLint({"NewApi", "Override"}) +public class DragSortGridView extends FrameLayout { + protected NoScrollGridView mGridView; + private ScrollView mScrollView; + private int headDragPosition = 0; + private int footDragPosition = 0; + private FrameLayout mDragFrame; + private View mCopyView, hideView; + private GestureDetector detector; + + private ViewGroup touchClashparent; + private boolean isLongOnClick; + + /** + * 动画时间 + */ + private static final long ANIM_DURING = 250; + protected int mNumColumns = 3, mColHeight = 0, mColWidth = 0, mChildCount = 0, mMaxHeight = 0; + private int currentDragPosition = -1; + private DragAdapter adapter; + /** + * 持有子view + */ + private List mChilds = new ArrayList(); + private static final int TAG_KEY = R.id.first; + // private static final int TAG_KEY = R.id.tag_key; + private int mCurrentY = 0; + /** + * 触摸区域,0不滚动区域,1可向上滚动的区域,-1可向下滚动的区域 + */ + private int mTouchArea = 0; + /** + * gridview能否滚动,是否内容太多 + */ + private boolean canScroll = true; + /** + * 是否可以拖动,点击拖动策略下直接开启,长按拖动需要长按以后开启 + */ + private boolean isDragable = true; + /** + * 自动滚屏的动画 + */ + private ValueAnimator animator; + /** + * view是否加载完成,如果未加载完成,没有宽高,无法接受事件 + */ + private boolean isViewInitDone = false; + + /** + * 是否有位置发生改变,否则不用重绘 + */ + private boolean hasPositionChange = false; + + /** + * 适配器的观察者,观察适配器的数据改变 + */ + private DataSetObserver observer = new DataSetObserver() { + @Override + public void onChanged() { + mChildCount = adapter.getCount(); + // 下列属性状态清除,才会在被调用notifyDataSetChange时,在gridview测量布局完成后重新获取 + mChilds.clear(); + mColHeight = mColWidth = mMaxHeight = 0; + isViewInitDone = false; + } + + @Override + public void onInvalidated() { + mChildCount = adapter.getCount(); + } + }; + private float[] lastLocation = null; + /** + * 手势监听器,滚动和单击 + */ + private GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (hasSendDragMsg) { + hasSendDragMsg = false; + handler.removeMessages(0x123); + } + if (isDragable && mCopyView != null) {// 可以拖动,实现跟随手指的拖动效果 + + // /// 2015/11/27补充修正跟随手指移动方法,适用于当本控件在drag时同时滚动的情况 + if (lastLocation == null) { + lastLocation = new float[]{e1.getRawX(), e1.getRawY()}; + } + distanceX = lastLocation[0] - e2.getRawX(); + distanceY = lastLocation[1] - e2.getRawY(); + lastLocation[0] = e2.getRawX(); + lastLocation[1] = e2.getRawY(); + // //////// + + mCopyView.setX(mCopyView.getX() - distanceX); + mCopyView.setY(mCopyView.getY() - distanceY); + mCopyView.invalidate(); + int to = eventToPosition(e2); + if (to != currentDragPosition && to >= headDragPosition && to < mChildCount - footDragPosition) { + onDragPositionChange(currentDragPosition, to); + } + } + return true; + } + + + + @Override + public void onShowPress(final MotionEvent e) { + /** 响应长按拖拽 */ + + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (isLongOnClick) { + if (itemLongClickListener != null) { + itemLongClickListener.onItemLongClick(mGridView, childAt(currentDragPosition), currentDragPosition, 0); + } + handler.post(new Runnable() { + @Override + public void run() { + if (mDragMode == DRAG_BY_LONG_CLICK) { + + // 启动拖拽模式 + // isDragable = true; + // 通知父控件不拦截我的事件 + getParent().requestDisallowInterceptTouchEvent(true); + // 根据点击的位置生成该位置上的view镜像 + int position = eventToPosition(e); + if (position >= headDragPosition && position < mChildCount - footDragPosition) { + // copyView(currentDragPosition = position); + Message msg = handler.obtainMessage(0x123, position, 0); + // showpress本身大概需要170毫秒 +// handler.sendMessageDelayed(msg, dragLongPressTime - 170); + handler.sendMessage(msg); + hasSendDragMsg = true; + } + } + } + }); + } + } + },dragLongPressTime - 170); + }; + }; + private boolean hasSendDragMsg = false; + private Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case 0x123: + // 启动拖拽模式 + isDragable = true; + // 根据点击的位置生成该位置上的view镜像 + copyView(currentDragPosition = msg.arg1); + hasSendDragMsg = false; + break; + default: + break; + } + return false; + } + }); + + public DragSortGridView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DragSortGridView(Context context) { + super(context); + init(); + } + + private void init() { + Context context = getContext(); + mGridView = new NoScrollGridView(context); + mGridView.setVerticalScrollBarEnabled(false); + mGridView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); + mGridView.setSelector(new ColorDrawable()); + // View的宽高之类必须在测量,布局,绘制一系列过程之后才能获取到 + mGridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + @Override + public void onGlobalLayout() { + if (mChilds.isEmpty()) { + for (int i = 0; i < mGridView.getChildCount(); i++) { + View view = mGridView.getChildAt(i); + view.setTag(TAG_KEY, new int[]{0, 0}); + view.clearAnimation(); + mChilds.add(view); + } + } + if (!mChilds.isEmpty()) { + mColHeight = mChilds.get(0).getHeight(); + } + mColWidth = mGridView.getColumnWidth(); + if (mChildCount % mNumColumns == 0) { + mMaxHeight = mColHeight * mChildCount / mNumColumns; + } else { + mMaxHeight = mColHeight * (mChildCount / mNumColumns + 1); + } + canScroll = mMaxHeight - getHeight() > 0; + // 告知事件处理,完成View加载,许多属性也已经初始化了 + isViewInitDone = true; + } + }); + mScrollView = new ListenScrollView(context); + + mDragFrame = new FrameLayout(context); + addView(mScrollView, -1, -1); + mScrollView.addView(mGridView, -1, -1); + + addView(mDragFrame, new LayoutParams(-1, -1)); + detector = new GestureDetector(context, simpleOnGestureListener); + detector.setIsLongpressEnabled(false); + mGridView.setNumColumns(mNumColumns); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + try { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + isLongOnClick = true; + break; + + case MotionEvent.ACTION_UP: + isLongOnClick = false; + break; + default: + break; + } + if (l != null) { + l.onTouch(this, ev); + } + if (!isViewInitDone) { + return false; + } + + if (isDragable) { + handleScrollAndCreMirror(ev); + } else { + // 交给子控件自己处理 + if (canScroll) { + mScrollView.dispatchTouchEvent(ev); + } else { + mGridView.dispatchTouchEvent(ev); + } + } + // 处理拖动 + detector.onTouchEvent(ev); + if (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP) { + lastLocation = null; + if (hasSendDragMsg) { + hasSendDragMsg = false; + handler.removeMessages(0x123); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + + /** + * Author :[pWX273343] 2015年7月22日 + *

+ * Description :拦截所有事件 + */ + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + /** + * 处理自动滚屏,和单击生成镜像 + */ + private void handleScrollAndCreMirror(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + // 通知父控件不拦截我的事件 + getParent().requestDisallowInterceptTouchEvent(true); + if (touchClashparent != null) { + touchClashparent.requestDisallowInterceptTouchEvent(true); + } + // 根据点击的位置生成该位置上的view镜像 + int position = eventToPosition(ev); + if (position >= headDragPosition && position < mChildCount - footDragPosition) { + copyView(currentDragPosition = position); + } + break; + case MotionEvent.ACTION_MOVE: + // 通知父控件不拦截我的事件 + getParent().requestDisallowInterceptTouchEvent(true); + if (touchClashparent != null) { + touchClashparent.requestDisallowInterceptTouchEvent(true); + } + // 内容太多时,移动到边缘会自动滚动 + if (canScroll) { + + int touchArea = decodeTouchArea(ev); + if (touchArea != mTouchArea) { + onTouchAreaChange(touchArea); + mTouchArea = touchArea; + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (hideView != null) { + hideView.setVisibility(View.VISIBLE); + if (onDragSelectListener != null) { + onDragSelectListener.onPutDown(hideView); + } + } + mDragFrame.removeAllViews(); + // mDragFrame.scrollTo(0, 0); + // isNotifyByDragSort = true; + if (hasPositionChange) { + hasPositionChange = false; + adapter.notifyDataSetChanged(); + } else if (mDragMode == DRAG_BY_LONG_CLICK && itemLongClickListener != null) { + itemLongClickListener.onItemLongClick(mGridView, childAt(currentDragPosition), currentDragPosition, 0); + } + // 停止滚动 + if (canScroll) { + int scrollStates2 = decodeTouchArea(ev); + if (scrollStates2 != 0) { + onTouchAreaChange(0); + mTouchArea = 0; + } + } + // 放手时取消拖动排序模式 + if (mDragMode == DRAG_BY_LONG_CLICK) { + isDragable = false; + } + break; + default: + break; + } + } + + /** + * @param ev 事件 + * @return 0中间区域, 1底部,-1顶部 + * @描述: 检查当前触摸事件位于哪个区域, 顶部1/5可能触发下滚,底部1/5可能触发上滚 + * @作者 [pWX273343] 2015年6月30日 + */ + private int decodeTouchArea(MotionEvent ev) { + if (ev.getY() > getHeight() * 4 / (double) 5) { + return 1; + } else if (ev.getY() < getHeight() / (double) 5) { + return -1; + } else { + return 0; + } + } + + /** + * @param ev + * @return + * @描述 得到事件触发点, 摸到的是哪一个item + * @作者 [pWX273343] 2015年7月6日 + */ + public int eventToPosition(MotionEvent ev) { + + if (ev != null) { + int m = (int) ev.getX() / mColWidth; + int n = (int) (ev.getY() + mCurrentY) / mColHeight; + int position = n * mNumColumns + m; + if (position >= mChildCount) { + return mChildCount - 1; + } else { + return position; + } + } + return 0; + } + + // 这里把控件作为假的横向ListView,所以返回position跟高度无关,暂时这样 + // public int eventToPosition(MotionEvent ev) { + // + // if (ev != null) { + // int m = (int) ev.getX() / mColWidth; + // if (m >= mChildCount) { + // return mChildCount - 1; + // } else { + // return m; + // } + // } + // return 0; + // } + + /** + * @param dragPosition + * @描述:复制一个镜像,并添加到透明层 + * @作者 [pWX273343] 2015年7月6日 + */ + private void copyView(int dragPosition) { + hideView = mChilds.get(dragPosition); + int realPosition = mGridView.indexOfChild(hideView); + if (!adapter.isUseCopyView()) { + mCopyView = adapter.getView(realPosition, mCopyView, mDragFrame); + } else { + mCopyView = adapter.copyView(realPosition, mCopyView, mDragFrame); + } + hideView.setVisibility(View.INVISIBLE); + mDragFrame.addView(mCopyView, mColWidth, mColHeight); + + int[] l1 = new int[2]; + int[] l2 = new int[2]; + hideView.getLocationOnScreen(l1); + mDragFrame.getLocationOnScreen(l2); + + // mCopyView.setX(hideView.getLeft()); + // mCopyView.setY(hideView.getTop() - mCurrentY); + mCopyView.setX(l1[0] - l2[0]); + mCopyView.setY(l1[1] - l2[1]); + if (onDragSelectListener == null) { + mCopyView.setScaleX(1.2f); + mCopyView.setScaleY(1.2f); + } else { + onDragSelectListener.onDragSelect(mCopyView); + } + } + + /** + * @param from + * @param to + * @描述:动画效果移动View + * @作者 [pWX273343] 2015年6月24日 + */ + private void translateView(int from, int to) { + View view = mChilds.get(from); + int fromXValue = ((int[]) view.getTag(TAG_KEY))[0]; + int fromYValue = ((int[]) view.getTag(TAG_KEY))[1]; + int toXValue = to % mNumColumns - from % mNumColumns + fromXValue; + int toYValue = to / mNumColumns - from / mNumColumns + fromYValue; + Animation animation = new TranslateAnimation(1, fromXValue, 1, toXValue, 1, fromYValue, 1, toYValue); + animation.setDuration(ANIM_DURING); + animation.setFillAfter(true); + view.setTag(TAG_KEY, new int[]{toXValue, toYValue}); + view.startAnimation(animation); + } + + /** + * @param from + * @param to + * @描述:拖动View使位置发生改变时 + * @作者 [pWX273343] 2015年7月6日 + */ + private void onDragPositionChange(int from, int to) { + if (from > to) { + for (int i = to; i < from; i++) { + translateView(i, i + 1); + } + } else { + for (int i = to; i > from; i--) { + translateView(i, i - 1); + } + } + if (!hasPositionChange) { + hasPositionChange = true; + } + adapter.onDataModelMove(from, to); + View view = mChilds.remove(from); + mChilds.add(to, view); + currentDragPosition = to; + } + + /** + * Function :setAdapter + *

+ * Author :[pWX273343] 2015年6月24日 + *

+ * Description :设置适配器.该适配器必须实现一个方法,当view的位置发生变动时,对实际数据的改动 + * + * @param adapter + * @see GridView#setAdapter(android.widget.ListAdapter) + */ + public void setAdapter(DragAdapter adapter) { + if (this.adapter != null && observer != null) { + this.adapter.unregisterDataSetObserver(observer); + } + this.adapter = adapter; + mGridView.setAdapter(adapter); + adapter.registerDataSetObserver(observer); + mChildCount = adapter.getCount(); + } + + public int getNumColumns() { + return mNumColumns; + } + + /** + * 每行几个 + */ + public void setNumColumns(int numColumns) { + this.mNumColumns = numColumns; + mGridView.setNumColumns(numColumns); + } + + /** + * 设置前几个item不可以改变位置 + */ + public void setNoPositionChangeItemCount(int count) { + headDragPosition = count; + } + + /** + * 设置后几个item不可以改变位置 + */ + public void setFootNoPositionChangeItemCount(int count) { + footDragPosition = count; + } + + /** + * 控制自动滚屏的动画监听器. + */ + private ValueAnimator.AnimatorUpdateListener animUpdateListener = new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int targetY = Math.round((Float) animation.getAnimatedValue()); + if (targetY < 0) { + targetY = 0; + } else if (targetY > mMaxHeight - getHeight()) { + targetY = mMaxHeight - getHeight(); + } + // mGridView.scrollTo(0, targetY); + mScrollView.smoothScrollTo(0, targetY); + // mCurrentY = targetY; + } + + }; + + /** + * @param scrollStates + * @描述:触摸区域改变,做相应处理,开始滚动或停止滚动 + * @作者 [pWX273343] 2015年6月29日 + */ + protected void onTouchAreaChange(int scrollStates) { + if (!canScroll) { + return; + } + if (animator != null) { + animator.removeUpdateListener(animUpdateListener); + } + if (scrollStates == 1) {// 从普通区域进入触发向上滚动的区域 + int instance = mMaxHeight - getHeight() - mCurrentY; + animator = ValueAnimator.ofFloat(mCurrentY, mMaxHeight - getHeight()); + animator.setDuration((long) (instance / 0.5f)); + animator.setTarget(mGridView); + animator.addUpdateListener(animUpdateListener); + animator.start(); + } else if (scrollStates == -1) {// 进入触发向下滚动的区域 + animator = ValueAnimator.ofFloat(mCurrentY, 0); + animator.setDuration((long) (mCurrentY / 0.5f)); + animator.setTarget(mGridView); + animator.addUpdateListener(animUpdateListener); + animator.start(); + } + } + + private OnDragSelectListener onDragSelectListener; + + /** + * @描述:一个item view刚被拖拽和放下时起来生成镜像时调用. + * @作者 [pWX273343] 2015年6月30日 + */ + public void setOnDragSelectListener(OnDragSelectListener onDragSelectListener) { + this.onDragSelectListener = onDragSelectListener; + } + + public interface OnDragSelectListener { + /** + * @param mirror 所拖拽起来的view生成的镜像 ,并不是实际的view.可对这个镜像实施变换效果,但是并不改变放下后的效果 + * @描述:拖拽起一个view时调用 + * @作者 [pWX273343] 2015年6月30日 + */ + void onDragSelect(View mirror); + + /** + * @param itemView + * @描述:拖拽的View放下时调用 + * @作者 [pWX273343] 2015年7月3日 + */ + void onPutDown(View itemView); + } + + class NoScrollGridView extends GridView { + + public NoScrollGridView(Context context) { + super(context); + } + + /** + * @return + * @描述:兼容老版本的getColumWidth + * @作者 [pWX273343] 2015年7月1日 + */ + public int getColumnWidth() { + return getWidth() / getNumColumns(); + } + + public NoScrollGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, mExpandSpec); + } + } + + /** + * Copyright (C), 2008-2015, Huawei Tech. Co., Ltd. + *

+ * Description : 监听滚动的scrollview,我们需要实时知道他已滚动的距离 + * + * @author [pWX273343] 2015年7月22日 + * @version V100R001 + * @since V100R001 + */ + class ListenScrollView extends ScrollView { + public ListenScrollView(Context context) { + super(context); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + mCurrentY = getScrollY(); + } + } + + public View getChildViewAtIndex(int index) { + if (index < mChilds.size()) { + return mChilds.get(index); + } + return null; + } + + // 转交给gridview一些常用监听器 + private AdapterView.OnItemLongClickListener itemLongClickListener; + + /** + * @param itemClickListener + * @描述:item 转交给gridview一些常用监听器 + * @作者 [pWX273343] 2015年7月27日 + */ + public void setOnItemClickListener(AdapterView.OnItemClickListener itemClickListener) { + mGridView.setOnItemClickListener(itemClickListener); + } + + /** + * 长按监听器自己触发,点击拖动模式不存在长按 + * + * @param + */ + public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener itemLongClickListener) { + this.itemLongClickListener = itemLongClickListener; + } + + /** + * 点击拖动 + */ + public static final int DRAG_WHEN_TOUCH = 0; + /** + * 长按拖动 + */ + public static final int DRAG_BY_LONG_CLICK = 1; + + private int mDragMode = DRAG_WHEN_TOUCH; + + /** + * @param mode int类型 + * @描述:设置拖动的策略是点击还是长按 + * @作者 [pWX273343] 2015年7月20日 参考 DRAG_WHEN_TOUCH,DRAG_BY_LONG_CLICK + */ + public void setDragModel(int mode) { + this.mDragMode = mode; + isDragable = mode == DRAG_WHEN_TOUCH; + } + + public View childAt(int index) { + return mGridView.getChildAt(index); + } + + public int childCount() { + return mGridView.getChildCount(); + } + + public void setAnimFrame(FrameLayout mDragFrame) { + this.mDragFrame = mDragFrame; + } + + private OnTouchListener l; + + @Override + public void setOnTouchListener(OnTouchListener l) { + this.l = l; + } + + private long dragLongPressTime = 600; + + /** + * 设置长按需要用时 + * + * @param time + */ + public void setDragLongPressTime(long time) { + dragLongPressTime = time; + } + + /** + * 设置触摸事件冲突父控件 + * + * @param touchClashparent + */ + public void setTouchClashparent(ViewGroup touchClashparent) { + this.touchClashparent = touchClashparent; + } + + public ScrollView getmScrollView() { + return mScrollView; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/custom/MyTextView.java b/app/src/main/java/xyz/fycz/myreader/custom/MyTextView.java new file mode 100644 index 0000000..56704d0 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/MyTextView.java @@ -0,0 +1,69 @@ +package xyz.fycz.myreader.custom; + + +import android.content.Context; + + + +import android.util.AttributeSet; + + +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; +import androidx.appcompat.widget.AppCompatTextView; + + +public class MyTextView extends /*AppCompatEditText*/ AppCompatTextView { + + /* private OnTouchListener mOnTouchListener; + private long timeDown; + private long timeUp;*/ + + + public MyTextView(Context context) { + super(context); + + } + + public MyTextView(Context context, AttributeSet attrs) { + super(context, attrs); + + } + + public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + } + + + /* @Override + public boolean dispatchTouchEvent(MotionEvent event) { + + if (timeUp - timeDown > 0 && timeUp - timeDown < 1000){ + return false; + }else { + return super.dispatchTouchEvent(event); + } + + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN){ + timeDown = System.currentTimeMillis(); + }else if (event.getAction() == MotionEvent.ACTION_UP){ + timeUp = System.currentTimeMillis(); + } + return super.onTouchEvent(event); + }*/ + + + + +} + diff --git a/app/src/main/java/xyz/fycz/myreader/custom/ReadTextView.java b/app/src/main/java/xyz/fycz/myreader/custom/ReadTextView.java new file mode 100644 index 0000000..426fb96 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/custom/ReadTextView.java @@ -0,0 +1,13 @@ +package xyz.fycz.myreader.custom; + +import android.content.Context; +import android.widget.TextView; +import androidx.appcompat.widget.AppCompatTextView; + + +public class ReadTextView extends AppCompatTextView { + + public ReadTextView(Context context){ + super(context); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/ContactsTree.java b/app/src/main/java/xyz/fycz/myreader/entity/ContactsTree.java new file mode 100644 index 0000000..08c6437 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/ContactsTree.java @@ -0,0 +1,212 @@ +package xyz.fycz.myreader.entity; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Created by zhao on 2016/11/9. + * APP通迅录树结构 + * + */ + +public class ContactsTree implements Serializable { + + private static final long serialVersionUID = 7184368467231587092L; + + //根节点赋值字段(部门节点) + private String DepartId;//部门id + private String DepartName;//部门名 + private String orgCode; //部门编码 + private String departOrder; //部门排序 + private boolean select; + + //叶节点赋值字段(个人信息节点) + private String id; + private String userDepartId;//用户所属部门ID + private String userDepartName;//用户所属部门名 + private boolean moreDepartUser;// 用户是否多部门 true是 false不是 + private boolean transpondPerson;//是否转发人员 true是 false不是 + private String userName;//用户名 + private String realName;//姓名 + private String wholeSpellName;//姓名全拼 + private String firstLetterName;//拼音首字母 eg:hzh + private String mobilePhone;//手机 + private String sex;//性别 + private String email;//邮箱 + + private ArrayList children;//孩子节点 + + private ContactsTree parent;//父节点 + + public boolean isTranspondPerson() { + return transpondPerson; + } + + public void setTranspondPerson(boolean transpondPerson) { + this.transpondPerson = transpondPerson; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDepartOrder() { + return departOrder; + } + + public String getOrgCode() { + return orgCode; + } + + public void setDepartOrder(String departOrder) { + this.departOrder = departOrder; + } + + public void setOrgCode(String orgCode) { + this.orgCode = orgCode; + } + + public boolean isMoreDepartUser() { + return moreDepartUser; + } + + public void setMoreDepartUser(boolean moreDepartUser) { + this.moreDepartUser = moreDepartUser; + } + + public String getUserDepartId() { + return userDepartId; + } + + public String getUserDepartName() { + return userDepartName; + } + + public void setUserDepartId(String userDepartId) { + this.userDepartId = userDepartId; + } + + public void setUserDepartName(String userDepartName) { + this.userDepartName = userDepartName; + } + + public boolean isSelect() { + return select; + } + + public void setSelect(boolean select) { + this.select = select; + } + + public ContactsTree getParent() { + return parent; + } + + public void setParent(ContactsTree parent) { + this.parent = parent; + } + + public ContactsTree(){ + children = new ArrayList<>(); + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + public String getMobilePhone() { + return mobilePhone; + } + + public void setMobilePhone(String mobilePhone) { + this.mobilePhone = mobilePhone; + } + + public ArrayList getChildren() { + return children; + } + + public String getDepartId() { + return DepartId; + } + + public String getDepartName() { + return DepartName; + } + + public String getFirstLetterName() { + return firstLetterName; + } + + public String getRealName() { + return realName; + } + + public String getUserName() { + return userName; + } + + public String getWholeSpellName() { + return wholeSpellName; + } + + public void setChildren(ArrayList children) { + this.children = children; + } + + public void setDepartId(String departId) { + DepartId = departId; + } + + public void setDepartName(String departName) { + DepartName = departName; + } + + public void setFirstLetterName(String firstLetterName) { + this.firstLetterName = firstLetterName; + } + + public void setRealName(String realName) { + this.realName = realName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setWholeSpellName(String wholeSpellName) { + this.wholeSpellName = wholeSpellName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public String toString() { + return "ContactsTree{" + + "DepartId='" + DepartId + '\'' + + ", DepartName='" + DepartName + '\'' + + ", userName='" + userName + '\'' + + ", realName='" + realName + '\'' + + ", wholeSpellName='" + wholeSpellName + '\'' + + ", firstLetterName='" + firstLetterName + '\'' + + ", mobilePhone='" + mobilePhone + '\'' + + ", sex='" + sex + '\'' + + ", email='" + email + '\'' + + ", children=" + children + + '}'; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Custom.java b/app/src/main/java/xyz/fycz/myreader/entity/Custom.java new file mode 100644 index 0000000..8c4ffee --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/Custom.java @@ -0,0 +1,36 @@ +package xyz.fycz.myreader.entity; + +import java.io.Serializable; + + +public class Custom implements Serializable { + + private static final long serialVersionUID = 5088810102696918656L; + + private String id; + private String type;//类型 + + public String getId() { + return id; + } + + public String getType() { + return type; + } + + public void setId(String id) { + this.id = id; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return "Custom{" + + "id='" + id + '\'' + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Date.java b/app/src/main/java/xyz/fycz/myreader/entity/Date.java new file mode 100644 index 0000000..c75a59b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/Date.java @@ -0,0 +1,166 @@ +package xyz.fycz.myreader.entity; + + +import xyz.fycz.myreader.util.CalendarHelper; + +import java.io.Serializable; + + + +public class Date implements Serializable { + + private static final long serialVersionUID = -1251952358800941760L; + + private int year; //年 + private int month; //月 + private int date; //日 + private int day; //星期 + + + private boolean festival; + + + + public Date(){ + + + } + + public int compare(Date date){ + if(year == date.getYear() && month == date.getMonth() && this.date == date.getDate()){ + return 0; + }else if(year > date.getYear() + || (year == date.getYear() && month > date.getMonth()) + ||(year == date.getYear() && month == date.getMonth() && this.date > date.getDate())){ + return 1; + }else if(year < date.getYear() + || (year == date.getYear() && month < date.getMonth()) + ||(year == date.getYear() && month == date.getMonth() && this.date < date.getDate())){ + return -1; + }else { + return -101; + } + } + + public boolean isSameDate(Date date){ + return year == date.getYear() && month == date.getMonth() && this.date == date.getDate(); + } + + + + public Date copyDate(){ + Date resDate = new Date(); + resDate.setYear(year); + resDate.setMonth(month); + resDate.setDate(this.date); + resDate.setDay(day); + return resDate; + } + + public void lastMonth(){ + if(month == 1){ + year--; + month = 12; + }else { + month--; + } + } + + public void nextMonth(){ + if(month == 12){ + year++; + month = 1; + }else { + month++; + } + } + + public Date lastDate(){ + Date date = copyDate(); + if(date.getDate() == 1){ + if(month == 1){ + date.setMonth(12); + date.setYear(date.getYear() - 1); + }else { + date.setMonth(date.getMonth() - 1); + } + date.setDate(CalendarHelper.getMonthDays(date.getYear(),date.getMonth())); + }else { + date.setDate(date.getDate() - 1); + } + if(date.getDay() == 0){ + date.setDay(6); + }else { + date.setDay(date.getDay() - 1); + } + return date; + } + + public Date nextDate(){ + Date date = copyDate(); + if(date.getDate() == CalendarHelper.getMonthDays(date.getYear(),date.getMonth())){ + if(month == 12){ + date.setMonth(1); + date.setYear(date.getYear() + 1); + }else { + date.setMonth(date.getMonth() + 1); + } + date.setDate(1); + }else { + date.setDate(date.getDate() + 1); + } + if(date.getDay() == 6){ + date.setDay(0); + }else { + date.setDay(date.getDay() + 1); + } + return date; + } + + public long toTime(){ + java.util.Date date1 = new java.util.Date(year-1900,month-1,date); + return date1.getTime(); + } + + + + public int getDay() { + return day; + } + + public void setDay(int day) { + this.day = day; + } + + public int getDate() { + return date; + } + + public int getMonth() { + return month; + } + + public int getYear() { + return year; + } + + public void setDate(int date) { + this.date = date; + } + + public void setMonth(int month) { + this.month = month; + } + + public void setYear(int year) { + this.year = year; + } + + public boolean isFestival() { + return festival; + } + + public void setFestival(boolean festival) { + this.festival = festival; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/JsonModel.java b/app/src/main/java/xyz/fycz/myreader/entity/JsonModel.java new file mode 100644 index 0000000..16f3d4e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/JsonModel.java @@ -0,0 +1,105 @@ +package xyz.fycz.myreader.entity; + +import java.io.Serializable; + + + + +public class JsonModel implements Serializable { + + private static final long serialVersionUID = -7169864463597942730L; + + private int error;//错误码 + private boolean success;//请求是否成功 + private String result;//服务器返回的json数据存放与此 + private String token; + private int datasize; + private String publicKey; + + + private int visibleLastIndex = 0; + private int visibleItemCount; + + + public JsonModel() { + + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public int getError() { + return this.error; + } + + public void setError(int error) { + this.error = error; + } + + public String getResult() { + return this.result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + public boolean isSuccess() { + return this.success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public int getVisibleLastIndex() { + return this.visibleLastIndex; + } + + public void setVisibleLastIndex(int visibleLastIndex) { + this.visibleLastIndex = visibleLastIndex; + } + + public int getVisibleItemCount() { + return this.visibleItemCount; + } + + public void setVisibleItemCount(int visibleItemCount) { + this.visibleItemCount = visibleItemCount; + } + + public int getDatasize() { + return this.datasize; + } + + public void setDatasize(int datasize) { + this.datasize = datasize; + } + + + @Override + public String toString() { + return "JsonModel{" + + "error=" + error + + ", success=" + success + + ", result='" + result + '\'' + + ", token='" + token + '\'' + + ", datasize=" + datasize + + ", visibleLastIndex=" + visibleLastIndex + + ", visibleItemCount=" + visibleItemCount + + '}'; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/SearchBookBean.java b/app/src/main/java/xyz/fycz/myreader/entity/SearchBookBean.java new file mode 100644 index 0000000..7970fb6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/SearchBookBean.java @@ -0,0 +1,53 @@ +package xyz.fycz.myreader.entity; + +import java.util.Objects; + +/** + * @author fengyue + * @date 2020/5/19 9:19 + */ +public class SearchBookBean { + private String name;//书名 + private String author;//作者 + + public SearchBookBean() { + } + + public SearchBookBean(String name, String author) { + this.name = name; + this.author = author; + } + + 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; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SearchBookBean that = (SearchBookBean) o; + if (author == null){ + return name.equals(that.name); + } + return name.equals(that.name) && + author.equals(that.author); + } + + @Override + public int hashCode() { + return Objects.hash(name, author); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Setting.java b/app/src/main/java/xyz/fycz/myreader/entity/Setting.java new file mode 100644 index 0000000..e0382be --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/Setting.java @@ -0,0 +1,173 @@ +package xyz.fycz.myreader.entity; + +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.widget.page.PageMode; + +import java.io.Serializable; + +/** + * 用户设置 + * + */ + +public class Setting implements Serializable { + + private static final long serialVersionUID = 2295691810299441757L; + + private int readWordColor;//阅读字体颜色 + private int readBgColor;//阅读背景颜色 + private float readWordSize;//阅读字体大小 + + private ReadStyle readStyle;//阅读模式 + + private boolean dayStyle;//是否日间模式 + private int brightProgress;//亮度 1- 100 + private boolean brightFollowSystem;//亮度跟随系统 + private Language language;//简繁体 + private Font font;//字体 + + private int autoScrollSpeed = 5;//自动滑屏速度 + + private PageMode pageMode;//翻页模式 + + private boolean isVolumeTurnPage;//是否开启音量键翻页 + + private BookcaseStyle bookcaseStyle;//书架布局 + + private int newestVersionCode;//最新版本号 + + private String localFontName;//本地字体名字 + + private int settingVersion;//设置版本号 + + public int getAutoScrollSpeed() { + return autoScrollSpeed; + } + + public void setAutoScrollSpeed(int autoScrollSpeed) { + this.autoScrollSpeed = autoScrollSpeed; + } + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + this.font = font; + } + + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public boolean isBrightFollowSystem() { + return brightFollowSystem; + } + + public void setBrightFollowSystem(boolean brightFollowSystem) { + this.brightFollowSystem = brightFollowSystem; + } + + public void setBrightProgress(int brightProgress) { + this.brightProgress = brightProgress; + } + + public int getBrightProgress() { + return brightProgress; + } + + public boolean isDayStyle() { + return dayStyle; + } + + public void setDayStyle(boolean dayStyle) { + this.dayStyle = dayStyle; + } + + public int getReadWordColor() { + return readWordColor; + } + + public void setReadWordColor(int readWordColor) { + this.readWordColor = readWordColor; + } + + public int getReadBgColor() { + return readBgColor; + } + + public void setReadBgColor(int readBgColor) { + this.readBgColor = readBgColor; + } + + public float getReadWordSize() { + return readWordSize; + } + + public void setReadWordSize(float readWordSize) { + this.readWordSize = readWordSize; + } + + public ReadStyle getReadStyle() { + return readStyle; + } + + public void setReadStyle(ReadStyle readStyle) { + this.readStyle = readStyle; + } + + 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 BookcaseStyle getBookcaseStyle() { + return bookcaseStyle; + } + + public void setBookcaseStyle(BookcaseStyle bookcaseStyle) { + this.bookcaseStyle = bookcaseStyle; + } + + public int getNewestVersionCode() { + return newestVersionCode; + } + + public void setNewestVersionCode(int newestVersionCode) { + this.newestVersionCode = newestVersionCode; + } + + public String getLocalFontName() { + return localFontName; + } + + public void setLocalFontName(String localFontName) { + this.localFontName = localFontName; + } + + public int getSettingVersion() { + return settingVersion; + } + + public void setSettingVersion(int settingVersion) { + this.settingVersion = settingVersion; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Time.java b/app/src/main/java/xyz/fycz/myreader/entity/Time.java new file mode 100644 index 0000000..b3eba78 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/Time.java @@ -0,0 +1,201 @@ +package xyz.fycz.myreader.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + + + +public class Time implements Parcelable{ + private int year; + private int month; + private int date; + private int hour; + private int minute; + private int second; + + public long getLongTime(){ + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(year); + if(month < 10){ + stringBuilder.append("0"+month); + }else { + stringBuilder.append(month); + } + if(date < 10){ + stringBuilder.append("0" + date); + }else { + stringBuilder.append(date); + } + if(hour < 10){ + stringBuilder.append("0" + hour); + }else { + stringBuilder.append(hour); + } + if(minute < 10){ + stringBuilder.append("0" + minute); + }else { + stringBuilder.append(minute); + } + if(second < 10){ + stringBuilder.append("0" + second); + }else { + stringBuilder.append(second); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + java.util.Date date = null; + try { + date = sdf.parse(stringBuilder.toString()); + return date.getTime(); + } catch (ParseException e) { + e.printStackTrace(); + return 0; + } + } + + + public void init(Date date){ + year = date.getYear() + 1900; + month = date.getMonth() + 1; + this.date = date.getDate(); + hour = date.getHours(); + minute = date.getMinutes(); + second = 0; + } + + public void init(xyz.fycz.myreader.entity.Date date){ + Date date1 = new Date(); + year = date.getYear(); + month = date.getMonth(); + this.date = date.getDate(); + hour = date1.getHours(); + minute = date1.getMinutes(); + second = 0; + } + + public void setToDayZero(){ + hour = 0; + minute = 0; + second = 0; + } + + public void setToDayLast(){ + hour = 23; + minute = 59; + second = 59; + } + + public void lastMonth(){ + if(month == 1){ + month = 12; + year = year - 1; + }else { + month = month - 1; + } + } + + public void nextMonth(){ + if(month == 12){ + month = 1; + year = year + 1; + }else { + month = month + 1; + } + } + + public void init(long time){ + Date date = new Date(time); + init(date); + } + + + public int getDate() { + return date; + } + + public int getHour() { + return hour; + } + + public int getMinute() { + return minute; + } + + public int getMonth() { + return month; + } + + public int getSecond() { + return second; + } + + public int getYear() { + return year; + } + + public void setDate(int date) { + this.date = date; + } + + public void setHour(int hour) { + this.hour = hour; + } + + public void setMinute(int minute) { + this.minute = minute; + } + + public void setMonth(int month) { + this.month = month; + } + + public void setSecond(int second) { + this.second = second; + } + + public void setYear(int year) { + this.year = year; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.year); + dest.writeInt(this.month); + dest.writeInt(this.date); + dest.writeInt(this.hour); + dest.writeInt(this.minute); + dest.writeInt(this.second); + } + + public Time() { + } + + protected Time(Parcel in) { + this.year = in.readInt(); + this.month = in.readInt(); + this.date = in.readInt(); + this.hour = in.readInt(); + this.minute = in.readInt(); + this.second = in.readInt(); + } + + public static final Creator

Can save multiple the value of the map.

+ * + * @author fengyue + * @date 2020/5/19 7:33 + */ + + +public interface MultiValueMap { + + /** + * 添加Key-Value。 + * + * @param key key. + * @param value value. + */ + void add(K key, V value); + + /** + * 添加Key-List。 + * + * @param key key. + * @param values values. + */ + void add(K key, List values); + + /** + * 添加全部Key-List。 + * @param mvm + */ + void addAll(MultiValueMap mvm); + /** + * 设置一个Key-Value,如果这个Key存在就被替换,不存在则被添加。 + * + * @param key key. + * @param value values. + */ + void set(K key, V value); + + /** + * 设置Key-List,如果这个Key存在就被替换,不存在则被添加。 + * + * @param key key. + * @param values values. + * @see #set(Object, Object) + */ + void set(K key, List values); + + /** + * 替换所有的Key-List。 + * + * @param values values. + */ + void set(Map> values); + + /** + * 移除某一个Key,对应的所有值也将被移除。 + * + * @param key key. + * @return value. + */ + List remove(K key); + + /** + * 移除所有的值。 + * Remove all key-value. + */ + void clear(); + + /** + * 拿到Key的集合。 + * + * @return Set. + */ + Set keySet(); + + /** + * 拿到所有的值的集合。 + * + * @return List. + */ + List values(); + + /** + * 拿到某一个Key下的某一个值。 + * + * @param key key. + * @param index index value. + * @return The value. + */ + V getValue(K key, int index); + + /** + * 拿到某一个Key的所有值。 + * + * @param key key. + * @return values. + */ + List getValues(K key); + + /** + * 拿到MultiValueMap的大小. + * + * @return size. + */ + int size(); + + /** + * 判断MultiValueMap是否为null. + * + * @return True: empty, false: not empty. + */ + boolean isEmpty(); + + /** + * 判断MultiValueMap是否包含某个Key. + * + * @param key key. + * @return True: contain, false: none. + */ + boolean containsKey(K key); + +} + + diff --git a/app/src/main/java/xyz/fycz/myreader/mulvalmap/MultiValueSetMap.java b/app/src/main/java/xyz/fycz/myreader/mulvalmap/MultiValueSetMap.java new file mode 100644 index 0000000..f5b818c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/mulvalmap/MultiValueSetMap.java @@ -0,0 +1,132 @@ +package xyz.fycz.myreader.mulvalmap; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

Can save multiple the value of the map.

+ * value使用Hashset集合,防止重复 + * @author fengyue + * @date 2020/5/19 7:33 + */ + + +public interface MultiValueSetMap { + + /** + * 添加Key-Value。 + * + * @param key key. + * @param value value. + */ + void add(K key, V value); + + /** + * 添加Key-HashSet。 + * + * @param key key. + * @param values values. + */ + void add(K key, HashSet values); + + /** + * 添加全部Key-List。 + * @param mvm + */ + void addAll(MultiValueSetMap mvm); + /** + * 设置一个Key-Value,如果这个Key存在就被替换,不存在则被添加。 + * + * @param key key. + * @param value values. + */ + void set(K key, V value); + + /** + * 设置Key-HashSet,如果这个Key存在就被替换,不存在则被添加。 + * + * @param key key. + * @param values values. + * @see #set(Object, Object) + */ + void set(K key, HashSet values); + + /** + * 替换所有的Key-HashSet。 + * + * @param values values. + */ + void set(Map> values); + + /** + * 移除某一个Key,对应的所有值也将被移除。 + * + * @param key key. + * @return value. + */ + HashSet remove(K key); + + /** + * 移除所有的值。 + * Remove all key-value. + */ + void clear(); + + /** + * 拿到Key的集合。 + * + * @return Set. + */ + Set keySet(); + + /** + * 拿到所有的值的集合。 + * + * @return List. + */ + List values(); + + /** + * 拿到某一个Key下的某一个值。 + * + * @param key key. + * @param index index value. + * @return The value. + */ + V getValue(K key, int index); + + /** + * 拿到某一个Key的所有值。 + * + * @param key key. + * @return values. + */ + List getValues(K key); + + /** + * 拿到MultiValueMap的大小. + * + * @return size. + */ + int size(); + + /** + * 判断MultiValueMap是否为null. + * + * @return True: empty, false: not empty. + */ + boolean isEmpty(); + + /** + * 判断MultiValueMap是否包含某个Key. + * + * @param key key. + * @return True: contain, false: none. + */ + boolean containsKey(K key); + +} + + diff --git a/app/src/main/java/xyz/fycz/myreader/source/HttpDataSource.java b/app/src/main/java/xyz/fycz/myreader/source/HttpDataSource.java new file mode 100644 index 0000000..8c00efa --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/source/HttpDataSource.java @@ -0,0 +1,527 @@ +package xyz.fycz.myreader.source; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Base64; +import android.util.Log; + +import xyz.fycz.myreader.callback.JsonCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.entity.JsonModel; +import xyz.fycz.myreader.util.HttpUtil; +import xyz.fycz.myreader.util.RSAUtilV2; +import xyz.fycz.myreader.util.StringHelper; +import com.google.gson.Gson; +import xyz.fycz.myreader.callback.HttpCallback; +import xyz.fycz.myreader.callback.ResultCallback; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Map; + +/** + * Created by zhao on 2016/4/16. + */ + +public class HttpDataSource { + + /** + * http请求 (get) ps:获取html + * @param url + * @param callback + */ + public static void httpGet_html(String url, final String charsetName, final ResultCallback callback){ + Log.d("HttpGet URl", url); + HttpUtil.sendGetRequest_okHttp(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, charsetName)); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response.toString()); + callback.onFinish(response.toString(),0); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + + }); + } + + + + + /** + * http请求 (get) + * @param url + * @param callback + */ + public static void httpGet(String url, final JsonCallback callback) { + Log.d("HttpGet URl", url); + HttpUtil.sendGetRequest(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response.toString()); + // setResponse(response.toString()); + JsonModel jsonModel = new Gson().fromJson(response.toString(), JsonModel.class); +// jsonModel.setResult(jsonModel.getResult().replace("\n","")); +// test(jsonModel.getResult()); +// String str = new String(RSAUtilV2.decryptByPrivateKey(Base64.decode(jsonModel.getResult().replace("\n",""),Base64.DEFAULT),APPCONST.privateKey)); + if (URLCONST.isRSA && !StringHelper.isEmpty(jsonModel.getResult())) { + jsonModel.setResult(StringHelper.decode(new String(RSAUtilV2.decryptByPrivateKey(Base64.decode(jsonModel.getResult().replace("\n", ""), Base64.DEFAULT), APPCONST.privateKey)))); + } + callback.onFinish(jsonModel); + Log.d("Http", "RSA finish:" + new Gson().toJson(jsonModel)); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + + }); + } + public static void httpGet(String url, final ResultCallback callback) { + Log.d("HttpGet URl", url); + HttpUtil.sendGetRequest(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response.toString()); + callback.onFinish(response.toString(), 1); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + + }); + } + + /** + * http请求 (post) + * @param url + * @param output + * @param callback + */ + public static void httpPost(String url, String output, final JsonCallback callback) { + Log.d("HttpPost:", url + "&" + output); + HttpUtil.sendPostRequest(url, output, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response); + // setResponse(response.toString()); + JsonModel jsonModel = new Gson().fromJson(response.toString(), JsonModel.class); + if (URLCONST.isRSA && !StringHelper.isEmpty(jsonModel.getResult())) { + jsonModel.setResult(StringHelper.decode(new String(RSAUtilV2.decryptByPrivateKey(Base64.decode(jsonModel.getResult().replace("\n", ""), Base64.DEFAULT), APPCONST.privateKey)))); + } + callback.onFinish(jsonModel); + Log.d("Http", "RSA finish:" + new Gson().toJson(jsonModel)); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + Log.e("http", response); + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }); + } + + /** + * http请求 (post) + * @param url + * @param output + * @param callback + */ + public static void httpPost(String url, String output, final ResultCallback callback) { + Log.d("HttpPost:", url + "&" + output); + HttpUtil.sendPostRequest(url, output, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response); + callback.onFinish(response.toString(), 1); + } + } catch (Exception e) { + callback.onError(e); + e.printStackTrace(); + } + } + + @Override + public void onFinish(String response) { + Log.e("http", response); + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }); + } + /** + * http请求 (post) 获取蓝奏云直链 + * @param url + * @param output + * @param callback + */ + public static void httpPost(String url, String output, final ResultCallback callback, final String referer) { + Log.d("HttpPost:", url + "&" + output); + HttpUtil.sendPostRequest(url, output, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.d("Http", "read finish:" + response); + callback.onFinish(response.toString(), 1); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + Log.e("http", response); + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }, referer); + } + + /** + * http非加密请求(get) + * @param url + * @param callback + */ + public static void httpGetNoRSA(String url, final JsonCallback callback) { + Log.d("HttpGet URl", url); + HttpUtil.sendGetRequest(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + StringBuilder response = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + response.append(line); + line = reader.readLine(); + } + if (callback != null) { + Log.i("Http", "read finish:" + response.toString()); + // setResponse(response.toString()); + JsonModel jsonModel = new Gson().fromJson(response.toString(), JsonModel.class); + callback.onFinish(jsonModel); + Log.d("Http", "RSA finish:" + new Gson().toJson(jsonModel)); + } + } catch (Exception e) { + callback.onError(e); + } + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }); + } + + + public static void httpGetBitmap(String url, final HttpCallback callback) { + Log.d("Http", "success1"); + HttpUtil.sendBitmapGetRequest(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + if (callback != null) { + Bitmap bm = BitmapFactory.decodeStream(in); +// setBitmap(bm); + callback.onFinish(bm); + } + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }); + } + + /** + * 多文件上传 + * @param url + * @param files + * @param params + * @param callback + */ + public static void uploadFile(String url, String[] files, Map params, final JsonCallback callback) { + HttpUtil.uploadFileRequest(url, files, params, callback); + } + + /** + * 多文件上传2 + * @param url + * @param files + * @param params + * @param callback + */ + public static void uploadFile(String url, ArrayList files, Map params, final JsonCallback callback) { + String[] filePaths = new String[files.size()]; + int i = 0; + for (File file : files) { + filePaths[i++] = file.getAbsolutePath(); + } + HttpUtil.uploadFileRequest(url, filePaths, params, callback); + } + + + /** + * 多文件上传(okhttp) + * @param url + * @param files + * @param params + * @param callback + */ + public static void uploadFile_okhttp(String url, ArrayList files, Map params, final JsonCallback callback) { + HttpUtil.uploadFile(url, files, params, new HttpCallback() { + @Override + public void onFinish(String response) { + try { + if (callback != null) { + Log.d("Http", "read finish:" + response); + // setResponse(response.toString()); + JsonModel jsonModel = new Gson().fromJson(response.toString(), JsonModel.class); + if (URLCONST.isRSA && !StringHelper.isEmpty(jsonModel.getResult())) { + jsonModel.setResult(StringHelper.decode(new String(RSAUtilV2.decryptByPrivateKey(Base64.decode(jsonModel.getResult().replace("\n", ""), Base64.DEFAULT), APPCONST.privateKey)))); + } + callback.onFinish(jsonModel); + Log.d("Http", "RSA finish:" + new Gson().toJson(jsonModel)); + } + + } catch (Exception e) { + callback.onError(e); + e.printStackTrace(); + } + + } + + @Override + public void onFinish(InputStream in) { + + } + + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onError(Exception e) { + + } + }); + } + + /** + * 单文件上传 + * @param url + * @param file + * @param params + * @param callback + */ + public static void uploadFile(String url, File file, Map params, final JsonCallback callback) { + String[] filePaths = {file.getAbsolutePath()}; + HttpUtil.uploadFileRequest(url, filePaths, params, callback); + } + + /** + * 文件下载 + * @param url + * @param callback + */ + public static void httpGetFile(String url, final HttpCallback callback) { + Log.d("Http", "success1"); + HttpUtil.sendGetRequest(url, new HttpCallback() { + @Override + public void onFinish(Bitmap bm) { + + } + + @Override + public void onFinish(InputStream in) { + callback.onFinish(in); + + } + + @Override + public void onFinish(String response) { + + } + + @Override + public void onError(Exception e) { + if (callback != null) { + callback.onError(e); + } + } + }); + } + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/source/UserDatabaseHelper.java b/app/src/main/java/xyz/fycz/myreader/source/UserDatabaseHelper.java new file mode 100644 index 0000000..d7cf8a9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/source/UserDatabaseHelper.java @@ -0,0 +1,34 @@ +package xyz.fycz.myreader.source; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + + +public class UserDatabaseHelper extends SQLiteOpenHelper { + + private static final String CREATE_USER = "create table UserCache(" + + "id integer primary key autoincrement, " + + "account varchar, " + + "password varchar) "; + + private Context mContext; + + public UserDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version){ + super(context,name,factory,version); + mContext = context; + + } + + @Override + public void onCreate(SQLiteDatabase db){ + db.execSQL(CREATE_USER); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVerson){ + + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoActivity.java new file mode 100644 index 0000000..a44374e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoActivity.java @@ -0,0 +1,134 @@ +package xyz.fycz.myreader.ui.bookinfo; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import androidx.core.content.ContextCompat; +import android.widget.*; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; + +import butterknife.ButterKnife; +import butterknife.BindView; +import xyz.fycz.myreader.common.APPCONST; + +public class BookInfoActivity extends BaseActivity { + + @BindView(R.id.ll_title_back) + LinearLayout llTitleBack; + @BindView(R.id.tv_title_text) + TextView tvTitleText; + @BindView(R.id.system_title) + LinearLayout systemTitle; + @BindView(R.id.iv_book_img) + ImageView ivBookImg; + @BindView(R.id.tv_book_name) + TextView tvBookName; + @BindView(R.id.tv_book_author) + TextView tvBookAuthor; + @BindView(R.id.tv_book_type) + TextView tvBookType; + @BindView(R.id.tv_book_desc) + TextView tvBookDesc; + @BindView(R.id.btn_add_bookcase) + Button btnAddBookcase; + @BindView(R.id.btn_read_book) + Button btnReadBook; + @BindView(R.id.tv_book_newest_chapter) + TextView tvBookNewestChapter; + @BindView(R.id.tv_disclaimer) + TextView tvDisclaimer; + @BindView(R.id.tv_book_source) + TextView tvBookSource; + @BindView(R.id.btn_change_source) + Button btnChangeSource; + @BindView(R.id.pb_loading) + ProgressBar pbLoading; + + private BookInfoPresenter mBookInfoPresenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_book_info); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mBookInfoPresenter = new BookInfoPresenter(this); + mBookInfoPresenter.start(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == APPCONST.REQUEST_READ) { + if (data == null) { + return; + } + boolean isCollected = data.getBooleanExtra(APPCONST.RESULT_IS_COLLECTED, false); + if (isCollected) { + getBtnAddBookcase().setText("移除书籍"); + getBtnReadBook().setText("继续阅读"); + } + } + } + + public LinearLayout getLlTitleBack() { + return llTitleBack; + } + + public TextView getTvTitleText() { + return tvTitleText; + } + + public LinearLayout getSystemTitle() { + return systemTitle; + } + + public ImageView getIvBookImg() { + return ivBookImg; + } + + public TextView getTvBookName() { + return tvBookName; + } + + public TextView getTvBookAuthor() { + return tvBookAuthor; + } + + public TextView getTvBookType() { + return tvBookType; + } + + public TextView getTvBookDesc() { + return tvBookDesc; + } + + public Button getBtnAddBookcase() { + return btnAddBookcase; + } + + public Button getBtnReadBook() { + return btnReadBook; + } + + public TextView getTvBookNewestChapter() { + return tvBookNewestChapter; + } + + public TextView getTvDisclaimer() { + return tvDisclaimer; + } + + public TextView getTvBookSource() { + return tvBookSource; + } + + public Button getBtnChangeSource() { + return btnChangeSource; + } + + public ProgressBar getPbLoading() { + return pbLoading; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoPresenter.java new file mode 100644 index 0000000..fc7921c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookInfoPresenter.java @@ -0,0 +1,304 @@ +package xyz.fycz.myreader.ui.bookinfo; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.crawler.BookInfoCrawler; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.crawler.ReadCrawlerUtil; +import xyz.fycz.myreader.creator.ChangeSourceDialog; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.ui.read.ReadActivity; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.CommonApi; + + +public class BookInfoPresenter implements BasePresenter { + + private BookInfoActivity mBookInfoActivity; + private Book mBook; + private ArrayList aBooks; + private BookService mBookService; + private ReadCrawler mReadCrawler; + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + initBookInfo(); + break; + case 2: + createChangeSourceDia(); + break; + case 3: + mBookInfoActivity.getPbLoading().setVisibility(View.GONE); + DialogCreator.createTipDialog(mBookInfoActivity, "未搜索到该书籍,书源加载失败!"); + break; + case 4: + initOtherInfo(); + break; + } + } + }; + + public BookInfoPresenter(BookInfoActivity bookInfoActivity) { + mBookInfoActivity = bookInfoActivity; + mBookService = new BookService(); + } + + @Override + public void start() { + aBooks = (ArrayList) mBookInfoActivity.getIntent().getSerializableExtra(APPCONST.SEARCH_BOOK_BEAN); + if (aBooks != null) { + mBook = aBooks.get(0); + } else { + mBook = (Book) mBookInfoActivity.getIntent().getSerializableExtra(APPCONST.BOOK); + } + init(); + } + + private void init() { + mBookInfoActivity.getBtnChangeSource().setOnClickListener(v -> { + if (!NetworkUtils.isNetWorkAvailable()){ + TextHelper.showText("无网络连接!"); + return; + } + mBookInfoActivity.getPbLoading().setVisibility(View.VISIBLE); + if (aBooks == null) { + ChangeSourceDialog csd = new ChangeSourceDialog(mBookInfoActivity, mBook); + csd.init(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + aBooks = (ArrayList) o; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + + @Override + public void onError(Exception e) { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + }); + } else { + createChangeSourceDia(); + } + }); + mBookInfoActivity.getTvDisclaimer().setOnClickListener(v -> { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(mBookInfoActivity.getAssets().open("disclaimer.fy"))); + StringBuilder disclaimer = new StringBuilder(); + String line = null; + while ((line = br.readLine()) != null) { + disclaimer.append(line); + disclaimer.append("\r\n\n"); + } + DialogCreator.createTipDialog(mBookInfoActivity, "免责声明", disclaimer.toString()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + if (isBookCollected()) { + mBookInfoActivity.getBtnAddBookcase().setText("移除书籍"); + mBookInfoActivity.getBtnReadBook().setText("继续阅读"); + } else { + mBookInfoActivity.getBtnAddBookcase().setText("加入书架"); + mBookInfoActivity.getBtnReadBook().setText("开始阅读"); + } + mBookInfoActivity.getLlTitleBack().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mBookInfoActivity.finish(); + } + }); + mBookInfoActivity.getBtnAddBookcase().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!isBookCollected()) { + mBookService.addBook(mBook); + TextHelper.showText("成功加入书架"); + mBookInfoActivity.getBtnAddBookcase().setText("移除书籍"); + } else { + mBookService.deleteBookById(mBook.getId()); + TextHelper.showText("成功移除书籍"); + mBookInfoActivity.getBtnAddBookcase().setText("加入书架"); + mBookInfoActivity.getBtnReadBook().setText("开始阅读"); + } + + } + }); + mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource()); + mBookInfoActivity.getBtnReadBook().setOnClickListener(view -> { + final boolean isCollected; + if (isBookCollected()) { + isCollected = true; + } else { + mBookService.addBook(mBook); + isCollected = false; + CommonApi.getBookChapters(mBook.getChapterUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + mBookService.updateEntity(mBook); + } + + @Override + public void onError(Exception e) { + } + }); + } + Intent intent = new Intent(mBookInfoActivity, ReadActivity.class); + intent.putExtra(APPCONST.BOOK, mBook); + intent.putExtra("isCollected", isCollected); + mBookInfoActivity.startActivityForResult(intent, APPCONST.REQUEST_READ); + }); + initBookInfo(); + } + + @SuppressLint("SetTextI18n") + private void initBookInfo() { + mBookInfoActivity.getTvTitleText().setText(mBook.getName()); + mBookInfoActivity.getTvBookAuthor().setText(mBook.getAuthor()); + if (StringHelper.isEmpty(mBook.getImgUrl())) { + mBook.setImgUrl(""); + } + assert mBook.getNewestChapterTitle() != null; + mBookInfoActivity.getTvBookNewestChapter().setText("最新章节:" + mBook.getNewestChapterTitle().replace("最近更新 ", "")); + mBookInfoActivity.getTvBookDesc().setText(""); + mBookInfoActivity.getTvBookType().setText(""); + mBookInfoActivity.getTvBookName().setText(mBook.getName()); + if (!"null".equals(mBook.getSource())) { + mBookInfoActivity.getTvBookSource().setText("书源:" + BookSource.fromString(mBook.getSource()).text); + } + ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(mBook.getSource()); + if (rc instanceof BookInfoCrawler && StringHelper.isEmpty(mBook.getImgUrl())) { + BookInfoCrawler bic = (BookInfoCrawler) rc; + CommonApi.getBookInfo(mBook, bic, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + mHandler.sendMessage(mHandler.obtainMessage(4)); + } + + @Override + public void onError(Exception e) { + + } + }); + } else { + initOtherInfo(); + } + } + + private void initOtherInfo() { + mBookInfoActivity.getTvBookDesc().setText(mBook.getDesc()); + mBookInfoActivity.getTvBookType().setText(mBook.getType()); + Glide.with(mBookInfoActivity) + .load(mBook.getImgUrl()) + .error(R.mipmap.no_image) + .placeholder(R.mipmap.no_image) + //设置圆角 + .apply(RequestOptions.bitmapTransform(new RoundedCorners(8))) + .into(mBookInfoActivity.getIvBookImg()); + } + + private boolean isBookCollected() { + Book book = mBookService.findBookByAuthorAndName(mBook.getName(), mBook.getAuthor()); + if (book == null) { + return false; + } else { + mBook = book; + return true; + } + } + + private void createChangeSourceDia() { + if (aBooks == null){ + mHandler.sendMessage(mHandler.obtainMessage(3)); + return; + } + mBookInfoActivity.getPbLoading().setVisibility(View.GONE); + CharSequence[] sources = new CharSequence[aBooks.size()]; + int checkedItem = 0; + for (int i = 0; i < sources.length; i++) { + sources[i] = BookSource.fromString(aBooks.get(i).getSource()).text + + "\n" + aBooks.get(i).getNewestChapterTitle(); + if (sources[i].equals(BookSource.fromString(mBook.getSource()).text + + "\n" + aBooks.get(i).getNewestChapterTitle())) { + checkedItem = i; + } + } + final int finalCheckedItem = checkedItem; + AlertDialog dialog = new AlertDialog.Builder(mBookInfoActivity) + .setTitle("切换书源") + .setCancelable(true) + .setSingleChoiceItems(sources, checkedItem, (dialog1, which) -> { + boolean isBookCollected = isBookCollected(); +// mBookService.deleteBook(mBook); + if (finalCheckedItem == which) { + dialog1.dismiss(); + return; + } + Book book = aBooks.get(which); + Book bookTem = new Book(mBook); + bookTem.setChapterUrl(book.getChapterUrl()); + bookTem.setImgUrl(book.getImgUrl()); + bookTem.setType(book.getType()); + bookTem.setDesc(book.getDesc()); + bookTem.setSource(book.getSource()); + if (isBookCollected) { + mBookService.updateBook(mBook, bookTem); + } + mBook = bookTem; + mHandler.sendMessage(mHandler.obtainMessage(1)); + if (isBookCollected) { + DialogCreator.createTipDialog(mBookInfoActivity, + "换源成功,由于不同书源的章节数量不一定相同,故换源后历史章节可能出错!"); + } + dialog1.dismiss(); + }).create(); + dialog.show(); + /*try { + Field mAlert = AlertDialog.class.getDeclaredField("mAlert"); + mAlert.setAccessible(true); + Object mAlertController = mAlert.get(dialog); + Field mMessage = mAlertController.getClass().getDeclaredField("mMessageView"); + mMessage.setAccessible(true); + TextView mMessageView = (TextView) mMessage.get(mAlertController); + mMessageView.setTextSize(5); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + }*/ + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/font/FontsActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsActivity.java new file mode 100644 index 0000000..f20af13 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsActivity.java @@ -0,0 +1,84 @@ +package xyz.fycz.myreader.ui.font; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; + +import butterknife.ButterKnife; +import butterknife.BindView; +import xyz.fycz.myreader.common.APPCONST; + +import static xyz.fycz.myreader.util.UriFileUtil.getPath; + +public class FontsActivity extends BaseActivity { + + @BindView(R.id.ll_title_back) + LinearLayout llTitleBack; + @BindView(R.id.tv_title_text) + TextView tvTitleText; + @BindView(R.id.system_title) + LinearLayout systemTitle; + @BindView(R.id.lv_fonts) + ListView lvFonts; + @BindView(R.id.pb_loading) + ProgressBar pbLoading; + + private FontsPresenter mFontsPresenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_fonts); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mFontsPresenter = new FontsPresenter(this); + mFontsPresenter.start(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + String path; + if (resultCode == Activity.RESULT_OK && requestCode == APPCONST.SELECT_FILE_CODE) { + Uri uri = data.getData(); + if ("file".equalsIgnoreCase(uri.getScheme())){//使用第三方应用打开 + path = uri.getPath(); + }else { + path = getPath(this, uri); + } + mFontsPresenter.saveLocalFont(path); + } + } + @Override + protected void onResume() { + super.onResume(); + mFontsPresenter.notifyChange(); + } + + public ProgressBar getPbLoading() { + return pbLoading; + } + + public LinearLayout getLlTitleBack() { + return llTitleBack; + } + + public TextView getTvTitleText() { + return tvTitleText; + } + + public LinearLayout getSystemTitle() { + return systemTitle; + } + + public ListView getLvFonts() { + return lvFonts; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/font/FontsAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsAdapter.java new file mode 100644 index 0000000..4d3eb0f --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsAdapter.java @@ -0,0 +1,343 @@ +package xyz.fycz.myreader.ui.font; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +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.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.enums.Font; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.CommonApi; + + +public class FontsAdapter extends ArrayAdapter { + + private int mResourceId; + private Setting setting; + private ArrayList mFontList; + private FontsActivity mFontsActivity; + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + updateDownloadPro(msg.arg1, msg.arg2, (ViewHolder) msg.obj); + break; + case 2: + Font font = (Font) msg.obj; + TextHelper.showText(font.toString() + "字体下载完成"); + notifyDataSetChanged(); + break; + case 3: + notifyDataSetChanged(); + break; + case 4: + ViewHolder viewHolder = (ViewHolder) msg.obj; + viewHolder.btnFontUse.setText("连接中..."); + break; + } + } + }; + + public FontsAdapter(Context context, int resourceId, ArrayList datas, FontsActivity fontsActivity) { + super(context, resourceId, datas); + mResourceId = resourceId; + setting = SysManager.getSetting(); + mFontList = datas; + this.mFontsActivity = fontsActivity; + } + + @Override + public void notifyDataSetChanged() { + setting = SysManager.getSetting(); + super.notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null); + viewHolder.tvFontName = (TextView) convertView.findViewById(R.id.tv_font_name); + viewHolder.btnFontUse = (Button) convertView.findViewById(R.id.btn_font_use); + //viewHolder.tvExample = (TextView)convertView.findViewById(R.id.tv_font_example); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position, viewHolder); + return convertView; + } + + 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); + } + }*/ + //viewHolder.tvExample.setTypeface(typeFace); +// viewHolder.tvExample.setTypeface(mTypefaceMap.get(font)); + viewHolder.tvFontName.setText(font.toString()); + File fontFile = new File(APPCONST.FONT_BOOK_DIR + font.fileName); + if (font == Font.本地字体) { + if (setting.getFont() == Font.本地字体) { + viewHolder.tvFontName.setText(setting.getLocalFontName()); + viewHolder.btnFontUse.setText(getContext().getString(R.string.font_change)); + } else { + viewHolder.btnFontUse.setText(getContext().getString(R.string.font_select)); + } + 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; + } + } + AlertDialog dialog = new AlertDialog.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();*/ + TextHelper.showText("请选择一个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.setText(getContext().getString(R.string.font_download)); + viewHolder.btnFontUse.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + viewHolder.btnFontUse.setEnabled(false); + addDownloadFont(font, viewHolder); + } + }); + } else if (setting.getFont() == font) { + viewHolder.btnFontUse.setText(getContext().getString(R.string.font_using)); + viewHolder.btnFontUse.setEnabled(false); + } else { + viewHolder.btnFontUse.setText(getContext().getString(R.string.font_use)); + viewHolder.btnFontUse.setEnabled(true); + viewHolder.btnFontUse.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setting.setFont(font); + SysManager.saveSetting(setting); + notifyDataSetChanged(); + Intent intent = new Intent(); + intent.putExtra(APPCONST.FONT, font); + ((Activity) getContext()).setResult(Activity.RESULT_OK, intent); + } + }); + } + } + + private void addDownloadFont(final Font font, final ViewHolder viewHolder) { + if (!NetworkUtils.isNetWorkAvailable()) { + TextHelper.showText("无网络连接!"); + mHandler.sendMessage(mHandler.obtainMessage(3)); + return; + } + final String[] url = {URLCONST.FONT_DOWNLOAD_URL + font.fileName}; + viewHolder.btnFontUse.setText("获取连接..."); + CommonApi.getUrl(font.downloadPath, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + String downloadUrl = (String) o; + if (downloadUrl != null) { + url[0] = downloadUrl; + } + downloadFont(url[0], font, viewHolder); + } + + @Override + public void onError(Exception e) { + downloadFont(url[0], font, viewHolder); + } + }); + } + + private void downloadFont(final String url, final Font font, final ViewHolder viewHolder) { + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + HttpURLConnection con = null; + InputStream is = null; + FileOutputStream fos = null; + File fontFile = null; + try { + URL webUrl = new URL(url); + mHandler.sendMessage(mHandler.obtainMessage(4, viewHolder)); + con = (HttpURLConnection) webUrl.openConnection(); + is = con.getInputStream(); + int fileLength = con.getContentLength(); + String filePath = APPCONST.FONT_BOOK_DIR + font.fileName + ".temp"; + fontFile = FileUtils.getFile(filePath); + fos = new FileOutputStream(fontFile); + byte[] tem = new byte[1024]; + int len = 0; + int alreadyLen = 0; + while ((len = is.read(tem)) != -1) { + fos.write(tem, 0, len); + alreadyLen += len; + mHandler.sendMessage(mHandler.obtainMessage(1, alreadyLen, fileLength, viewHolder)); + } + fos.flush(); + if (fileLength == fontFile.length()) { + String newPath = filePath.replace(".temp", ""); + File newFile = new File(newPath); + if (fontFile.renameTo(newFile)) { + mHandler.sendMessage(mHandler.obtainMessage(2, font)); + } else { + TextHelper.showText(font.toString() + "字体下载失败!"); + fontFile.delete(); + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + } else { + TextHelper.showText(font.toString() + "字体下载失败!"); + fontFile.delete(); + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + } catch (IOException e) { + TextHelper.showText(font.toString() + "字体下载失败!"); + if (fontFile != null) { + fontFile.delete(); + } + mHandler.sendMessage(mHandler.obtainMessage(3)); + e.printStackTrace(); + } finally { + if (con != null) { + con.disconnect(); + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + }); + } + + private void updateDownloadPro(int alreadyLen, int fileLen, ViewHolder viewHolder) { + int process = alreadyLen * 100 / fileLen; + viewHolder.btnFontUse.setText(process + "%"); + viewHolder.btnFontUse.setEnabled(false); + } + + private ArrayList getLocalFontList() { + File fontDir = new File(APPCONST.FONT_BOOK_DIR); + if (!fontDir.exists()) { + return null; + } + ArrayList localFontFiles = new ArrayList<>(); + File[] fontFiles = fontDir.listFiles(); + fontFilesFor: + for (File fontFile : fontFiles) { + for (Font font : mFontList) { + if (font == Font.本地字体) { + continue; + } + if (font.fileName.equals(fontFile.getName())) { + continue fontFilesFor; + } + } + if (fontFile.getName().endsWith(".ttf")) { + localFontFiles.add(fontFile); + } + } + return localFontFiles; + } + + public void saveLocalFontName(String fontName){ + setting.setFont(Font.本地字体); + setting.setLocalFontName(fontName); + SysManager.saveSetting(setting); + notifyDataSetChanged(); + Intent intent = new Intent(); + intent.putExtra(APPCONST.FONT, Font.本地字体); + ((AppCompatActivity) getContext()).setResult(AppCompatActivity.RESULT_OK, intent); + } + + class ViewHolder { + //TextView tvExample; + TextView tvFontName; + Button btnFontUse; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/font/FontsPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsPresenter.java new file mode 100644 index 0000000..ba39b12 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/font/FontsPresenter.java @@ -0,0 +1,126 @@ +package xyz.fycz.myreader.ui.font; + +import android.os.Handler; +import android.os.Message; +import android.view.View; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.enums.Font; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.FileUtils; + +import java.io.*; +import java.util.ArrayList; + +/** + * Created by zhao on 2017/8/7. + */ + +public class FontsPresenter implements BasePresenter { + + private FontsActivity mFontsActivity; + private ArrayList mFonts; + private FontsAdapter mFontsAdapter; + + + private Handler mHandler = new Handler(){ + @Override + public void handleMessage(Message msg) { + switch (msg.what){ + case 1: + init(); + break; + } + } + }; + + public FontsPresenter(FontsActivity fontsActivity) { + mFontsActivity = fontsActivity; + } + + @Override + public void start() { + mFontsActivity.getLlTitleBack().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mFontsActivity.finish(); + } + }); + mFontsActivity.getTvTitleText().setText(mFontsActivity.getString(R.string.font)); + init(); + } + + private void init() { + initFonts(); + mFontsAdapter = new FontsAdapter(mFontsActivity, R.layout.listview_font_item, mFonts, mFontsActivity); + mFontsActivity.getLvFonts().setAdapter(mFontsAdapter); + mFontsActivity.getPbLoading().setVisibility(View.GONE); + } + + 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.本地字体); + } + + public void notifyChange(){ + mFontsAdapter.notifyDataSetChanged(); + } + + public void saveLocalFont(String path){ + File fontFile = new File(path); + if (!fontFile.exists()){ + TextHelper.showText("未找到字体文件!"); + return; + } + String fontName = fontFile.getName(); + if (!fontName.endsWith(".ttf")){ + TextHelper.showText("字体更换失败,请选择ttf格式的字体文件!"); + return; + } + String dirPath = fontFile.getParentFile().getAbsolutePath() + "/"; + if (dirPath.equals(APPCONST.FONT_BOOK_DIR)){ + mFontsAdapter.saveLocalFontName(fontName); + return; + } + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(fontFile); + fos = new FileOutputStream(FileUtils.getFile(APPCONST.FONT_BOOK_DIR + fontName)); + byte[] bytes = new byte[1021]; + int len = -1; + while ((len = fis.read(bytes)) != -1){ + fos.write(bytes, 0, len); + } + fos.flush(); + mFontsAdapter.saveLocalFontName(fontName); + } catch (IOException e) { + e.printStackTrace(); + TextHelper.showText("读取字体文件出错!\n" + e.getLocalizedMessage()); + }finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/MainActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/home/MainActivity.java new file mode 100644 index 0000000..7ece21d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/MainActivity.java @@ -0,0 +1,199 @@ +package xyz.fycz.myreader.ui.home; + + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import androidx.annotation.RequiresApi; +import androidx.core.content.FileProvider; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.viewpager.widget.ViewPager; +import com.google.android.material.tabs.TabLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.custom.CircleImageView; +import xyz.fycz.myreader.util.TextHelper; + +import butterknife.BindView; +import butterknife.ButterKnife; + +import java.io.File; + +import static xyz.fycz.myreader.util.UriFileUtil.getPath; + + +public class MainActivity extends BaseActivity { + + + @BindView(R.id.civ_avatar) + CircleImageView civAvatar; + @BindView(R.id.tl_tab_menu) + TabLayout tlTabMenu; + @BindView(R.id.iv_search) + ImageView ivSearch; + @BindView(R.id.iv_more) + ImageView ivMore; + @BindView(R.id.rl_common_title) + RelativeLayout rlCommonTitle; + @BindView(R.id.tv_edit_finish) + TextView tvEditFinish; + @BindView(R.id.rl_edit_titile) + RelativeLayout rlEditTitle; + @BindView(R.id.vp_content) + ViewPager vpContent; + private MainPresenter mMainPrensenter; + + + private File appFile; + private boolean isForceUpdate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mMainPrensenter = new MainPresenter(this); + mMainPrensenter.start(); + } + + @Override + public void onBackPressed() { + if (mMainPrensenter.ismEditState()){ + mMainPrensenter.cancelEdit(); + return; + } + if (System.currentTimeMillis() - APPCONST.exitTime > APPCONST.exitConfirmTime) { + TextHelper.showText("再按一次退出"); + APPCONST.exitTime = System.currentTimeMillis(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onResume() { + super.onResume(); +// MyApplication.checkVersionByServer(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if ((resultCode == RESULT_OK || resultCode == RESULT_CANCELED) && requestCode == APPCONST.APP_INSTALL_CODE) { + installProcess(appFile, isForceUpdate);//再次执行安装流程,包含权限判等 + }else if (resultCode == RESULT_OK && requestCode == APPCONST.SELECT_FILE_CODE){ + String path; + Uri uri = data.getData(); + if ("file".equalsIgnoreCase(uri.getScheme())){//使用第三方应用打开 + path = uri.getPath(); + }else { + path = getPath(this, uri); + } + mMainPrensenter.addLocalBook(path); + } + } + + @Override + protected void onDestroy() { + MyApplication.getApplication().shutdownThreadPool(); + super.onDestroy(); + } + + //安装应用的流程 + public void installProcess(File file, boolean isForceUpdate) { + if (appFile == null || !this.isForceUpdate) { + appFile = file; + this.isForceUpdate = isForceUpdate; + } + boolean haveInstallPermission; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //先获取是否有安装未知来源应用的权限 + haveInstallPermission = getPackageManager().canRequestPackageInstalls(); + if (!haveInstallPermission) {//没有权限 + DialogCreator.createCommonDialog(this, "安装应用", + "安装应用需要打开未知来源权限,请去设置中开启权限", true, + "确定", (dialog, which) -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startInstallPermissionSettingActivity(); + } + }); + return; + } + } + //有权限,开始安装应用程序 + installApk(file, isForceUpdate); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void startInstallPermissionSettingActivity() { + Uri packageURI = Uri.parse("package:" + getPackageName()); + //注意这个是8.0新API + Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); + startActivityForResult(intent, APPCONST.APP_INSTALL_CODE); + } + + /** + * 安装应用 + * @param file + * @param isForceUpdate + */ + public void installApk(File file, boolean isForceUpdate) { + String authority = getApplicationContext().getPackageName() + ".fileprovider"; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //判读版本是否在7.0以上 + if (Build.VERSION.SDK_INT >= 24) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri apkUri = FileProvider.getUriForFile(this, authority, file); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + } else { + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + } + startActivity(intent); + if (isForceUpdate){ + finish(); + } + } + + public CircleImageView getCivAvatar() { + return civAvatar; + } + + public TabLayout getTlTabMenu() { + return tlTabMenu; + } + + public ImageView getIvSearch() { + return ivSearch; + } + + public ViewPager getVpContent() { + return vpContent; + } + + public RelativeLayout getRlCommonTitle() { + return rlCommonTitle; + } + + public TextView getTvEditFinish() { + return tvEditFinish; + } + + public RelativeLayout getRlEditTitle() { + return rlEditTitle; + } + + public ImageView getIvMore() { + return ivMore; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/MainPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/MainPresenter.java new file mode 100644 index 0000000..6625f1e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/MainPresenter.java @@ -0,0 +1,136 @@ +package xyz.fycz.myreader.ui.home; + +import android.content.DialogInterface; +import android.content.Intent; + +import android.view.View; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentPagerAdapter; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.ui.home.bbs.BBSFragment; +import xyz.fycz.myreader.ui.home.bookcase.BookcaseFragment; +import xyz.fycz.myreader.ui.home.bookcase.BookcasePresenter; +import xyz.fycz.myreader.ui.home.bookstore.BookStoreFragment; +import xyz.fycz.myreader.ui.search.SearchBookActivity; +import xyz.fycz.myreader.ui.user.LoginActivity; +import xyz.fycz.myreader.util.AppInfoUtils; +import xyz.fycz.myreader.util.TextHelper; + +import java.io.File; +import java.util.ArrayList; + +import static androidx.fragment.app.FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT; +import static xyz.fycz.myreader.application.MyApplication.checkVersionByServer; + + +public class MainPresenter implements BasePresenter { + + private MainActivity mMainActivity; + private ArrayList mFragments = new ArrayList<>(); + + private String[] tabTitle = {"书架"}; + + private BookcaseFragment mBookcaseFragment; + + public MainPresenter(MainActivity mainActivity) { + mMainActivity = mainActivity; + } + + @Override + public void start() { + try { + Setting mSetting = SysManager.getSetting(); + int settingVersion = mSetting.getSettingVersion(); + if (settingVersion < APPCONST.SETTING_VERSION){ + SysManager.resetSetting(); + DialogCreator.createTipDialog(mMainActivity, "因组件升级,已恢复默认设置!"); + } + }catch (Exception e){ + SysManager.resetSetting(); + DialogCreator.createTipDialog(mMainActivity, "因组件升级,已恢复默认设置!"); + } + init(); + checkVersionByServer(mMainActivity, false, (BookcaseFragment) mFragments.get(0)); + mMainActivity.getIvSearch().setOnClickListener(view -> { + Intent intent = new Intent(mMainActivity, SearchBookActivity.class); + mMainActivity.startActivity(intent); + }); + mMainActivity.getCivAvatar().setOnClickListener(v -> { + final File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy"); + if (file.exists()) { + DialogCreator.createCommonDialog(mMainActivity, "退出登录", "确定要退出登录吗?" + , true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (file.delete()) { + TextHelper.showText("退出成功"); + Intent intent = new Intent(mMainActivity, LoginActivity.class); + mMainActivity.startActivity(intent); + } else { + TextHelper.showText("退出失败"); + } + } + }, (dialog, which) -> dialog.dismiss()); + } else { + Intent intent = new Intent(mMainActivity, LoginActivity.class); + mMainActivity.startActivity(intent); + } + }); + } + + /** + * 初始化 + */ + private void init() { + mBookcaseFragment = new BookcaseFragment(); + mFragments.add(mBookcaseFragment); + mMainActivity.getVpContent().setAdapter(new FragmentPagerAdapter(mMainActivity.getSupportFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + @Override + public Fragment getItem(int position) { + return mFragments.get(position); + } + + @Override + public int getCount() { + return mFragments.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return tabTitle[position]; + } + + }); + mMainActivity.getTlTabMenu().setupWithViewPager(mMainActivity.getVpContent()); + mMainActivity.getVpContent().setCurrentItem(0); + } + + /** + * 添加本地书籍 + * @param path + */ + public void addLocalBook(String path){ + mBookcaseFragment.getmBookcasePresenter().addLocalBook(path); + } + + /** + * 取消编辑状态 + */ + public void cancelEdit(){ + mBookcaseFragment.getmBookcasePresenter().cancelEdit(); + } + + /** + * 判断是否处于编辑状态 + * @return + */ + public boolean ismEditState(){ + return mBookcaseFragment.getmBookcasePresenter().ismEditState(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/Splash.java b/app/src/main/java/xyz/fycz/myreader/ui/home/Splash.java new file mode 100644 index 0000000..f3d745f --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/Splash.java @@ -0,0 +1,98 @@ +package xyz.fycz.myreader.ui.home; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import android.view.Window; +import android.view.WindowManager; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.util.PermissionHelper; +import xyz.fycz.myreader.util.PermissionsChecker; +import xyz.fycz.myreader.util.TextHelper; + +public class Splash extends AppCompatActivity { + /*************Constant**********/ + private static final int WAIT_INTERVAL = 1000; + private static final int PERMISSIONS_REQUEST_STORAGE = 1; + + static final String[] PERMISSIONS = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + + private PermissionsChecker mPermissionsChecker; + private Thread myThread = new Thread() {//创建子线程 + @Override + public void run() { + try { + sleep(WAIT_INTERVAL);//使程序休眠 + Intent it = new Intent(getApplicationContext(), MainActivity.class);//启动MainActivity + startActivity(it); + finish();//关闭当前活动 + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setFullScreen(); + setContentView(R.layout.activity_splash); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M){ + mPermissionsChecker = new PermissionsChecker(this); + requestPermission(); + }else { + myThread.start(); + } + + + } + // 全屏显示 + private void setFullScreen() { + // 如果该类是 extends Activity ,下面这句代码起作用 + // 去除ActionBar(因使用的是NoActionBar的主题,故此句有无皆可) + requestWindowFeature(Window.FEATURE_NO_TITLE); + // 如果该类是 extends AppCompatActivity, 下面这句代码起作用 + if (getSupportActionBar() != null){ getSupportActionBar().hide(); } + // 去除状态栏,如 电量、Wifi信号等 + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + private void requestPermission(){ + //获取读取和写入SD卡的权限 + if (mPermissionsChecker.lacksPermissions(PERMISSIONS)){ + ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSIONS_REQUEST_STORAGE); + } else { + myThread.start(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case PERMISSIONS_REQUEST_STORAGE: { + // 如果取消权限,则返回的值为0 + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + //申请权限成功 + myThread.start(); + } else { + //申请权限失败 + finish(); + TextHelper.showText("请给予储存权限,否则程序无法正常运行!"); + } + return; + } + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSFragment.java new file mode 100644 index 0000000..b8de42f --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSFragment.java @@ -0,0 +1,30 @@ +package xyz.fycz.myreader.ui.home.bbs; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import xyz.fycz.myreader.R; + +/** + * A simple {@link Fragment} subclass. + */ +public class BBSFragment extends Fragment { + + + public BBSFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_bbs, container, false); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSPrensenter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSPrensenter.java new file mode 100644 index 0000000..d969771 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bbs/BBSPrensenter.java @@ -0,0 +1,15 @@ +package xyz.fycz.myreader.ui.home.bbs; + +import xyz.fycz.myreader.base.BasePresenter; + +/** + * Created by zhao on 2017/7/25. + */ + +public class BBSPrensenter implements BasePresenter { + + @Override + public void start() { + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseAdapter.java new file mode 100644 index 0000000..e30b487 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseAdapter.java @@ -0,0 +1,229 @@ +package xyz.fycz.myreader.ui.home.bookcase; + +import android.content.Context; +import android.content.DialogInterface; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.custom.DragAdapter; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.widget.BadgeView; + +/** + * @author fengyue + * @date 2020/4/19 11:23 + */ +public abstract class BookcaseAdapter extends DragAdapter { + + private final Map isLoading = new HashMap<>(); + protected int mResourceId; + protected ArrayList list; + protected Context mContext; + protected boolean mEditState; + protected BookService mBookService; + protected ChapterService mChapterService; + protected BookcasePresenter mBookcasePresenter; + protected final String[] menu = { + MyApplication.getmContext().getResources().getString(R.string.menu_book_Top), + MyApplication.getmContext().getResources().getString(R.string.menu_book_download), + MyApplication.getmContext().getResources().getString(R.string.menu_book_cache), + MyApplication.getmContext().getResources().getString(R.string.menu_book_delete) + }; + + + public BookcaseAdapter(Context context, int textViewResourceId, ArrayList objects + , boolean editState, BookcasePresenter bookcasePresenter) { + mContext = context; + mResourceId = textViewResourceId; + list = objects; + mEditState = editState; + mBookService = new BookService(); + mChapterService = new ChapterService(); + mBookcasePresenter = bookcasePresenter; + } + + + @Override + public void onDataModelMove(int from, int to) { + Book b = list.remove(from); + list.add(to, b); + for (int i = 0; i < list.size(); i++) { + list.get(i).setSortCode(i); + } + mBookService.updateBooks(list); + } + + @Override + public int getCount() { + return list.size(); + } + + @Override + public Book getItem(int position) { + return list.get(position); + } + + @Override + public long getItemId(int position) { + return list.get(position).getSortCode(); + } + + public void remove(Book item) { + list.remove(item); + notifyDataSetChanged(); + mBookService.deleteBook(item); + } + + public void add(Book item) { + list.add(item); + notifyDataSetChanged(); + mBookService.addBook(item); + } + protected void showDeleteBookDialog(final Book book){ + DialogCreator.createCommonDialog(mContext, "删除书籍", "确定删除《" + book.getName() + "》及其所有缓存吗?", + true, (dialogInterface, i) -> { + remove(book); + dialogInterface.dismiss(); + }, (dialogInterface, i) -> dialogInterface.dismiss()); + } + + /** + * 设置是否处于编辑状态 + * @param mEditState + */ + public void setmEditState(boolean mEditState) { + this.mEditState = mEditState; + notifyDataSetChanged(); + } + + public boolean ismEditState() { + return mEditState; + } + /** + * getter方法 + * @return + */ + public Map getIsLoading() { + return isLoading; + } + + public boolean isBookLoading(String bookID){ + return isLoading.get(bookID); + } + + public boolean unionChapterCathe(Book book) throws IOException { + ArrayList chapters = (ArrayList) mChapterService.findBookAllChapterByBookId(book.getId()); + BufferedReader br = null; + BufferedWriter bw = null; + bw = new BufferedWriter(new FileWriter(FileUtils.getFile(APPCONST.TXT_BOOK_DIR + book.getName() + ".txt"))); + if (chapters.size() == 0){ + return false; + } + File bookFile = new File(APPCONST.BOOK_CACHE_PATH + book.getId()); + if (!bookFile.exists()){ + return false; + } + for (Chapter chapter : chapters){ + if(ChapterService.isChapterCached(chapter.getBookId(), chapter.getTitle())){ + bw.write("\t" + chapter.getTitle()); + bw.newLine(); + br = new BufferedReader(new FileReader(APPCONST.BOOK_CACHE_PATH + book.getId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY)); + String line = null; + while ((line = br.readLine()) != null){ + bw.write(line); + bw.newLine(); + } + br.close(); + } + } + bw.flush(); + bw.close(); + return true; + } + + /*******************************************缓存书籍*********************************************************/ + private int selectedIndex;//对话框选择下标 + + protected void downloadBook(final Book book) { + if (!NetworkUtils.isNetWorkAvailable()) { + TextHelper.showText("无网络连接!"); + return; + } + if ("本地书籍".equals(book.getType())){ + TextHelper.showText("《" + book.getName() + "》是本地书籍,不能缓存"); + return; + } + final int[] begin = new int[1]; + final int[] end = new int[1]; + new AlertDialog.Builder(mContext) + .setTitle("缓存书籍") + .setSingleChoiceItems(APPCONST.DIALOG_DOWNLOAD, selectedIndex, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + selectedIndex = which; + } + }).setNegativeButton("取消", (new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + })).setPositiveButton("确定", + (dialog, which) -> { + switch (selectedIndex) { + case 0: + begin[0] = book.getHisttoryChapterNum(); + end[0] = book.getHisttoryChapterNum() + 50; + break; + case 1: + begin[0] = book.getHisttoryChapterNum() - 50; + end[0] = book.getHisttoryChapterNum() + 50; + break; + case 2: + begin[0] = book.getHisttoryChapterNum(); + end[0] = 99999; + break; + case 3: + begin[0] = 0; + end[0] = 99999; + break; + } + Thread downloadThread = new Thread(() -> { + ArrayList chapters = (ArrayList) mChapterService.findBookAllChapterByBookId(book.getId()); + mBookcasePresenter.addDownload(book, chapters, begin[0], end[0]); + }); + mBookcasePresenter.getEs().submit(downloadThread); + }).show(); + } + + static class ViewHolder { + ImageView ivBookImg; + TextView tvBookName; + BadgeView tvNoReadNum; + ImageView ivDelete; + ProgressBar pbLoading; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDetailedAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDetailedAdapter.java new file mode 100644 index 0000000..0feb0d5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDetailedAdapter.java @@ -0,0 +1,213 @@ +package xyz.fycz.myreader.ui.home.bookcase; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import java.io.IOException; +import java.util.ArrayList; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity; +import xyz.fycz.myreader.ui.read.ReadActivity; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; + + +/** + * @author fengyue + * @date 2020/4/19 11:23 + */ + +public class BookcaseDetailedAdapter extends BookcaseAdapter { + ViewHolder viewHolder = null; + + public BookcaseDetailedAdapter(Context context, int textViewResourceId, ArrayList objects, + boolean editState, BookcasePresenter bookcasePresenter) { + super(context, textViewResourceId, objects, editState, bookcasePresenter); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null || convertView.getTag() instanceof BookcaseDragAdapter.ViewHolder) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(mContext).inflate(mResourceId, null); + viewHolder.ivBookImg = convertView.findViewById(R.id.iv_book_img); + viewHolder.tvBookName = convertView.findViewById(R.id.tv_book_name); + viewHolder.tvNoReadNum = convertView.findViewById(R.id.tv_no_read_num); + viewHolder.ivDelete = convertView.findViewById(R.id.iv_delete); + viewHolder.tvBookAuthor = convertView.findViewById(R.id.tv_book_author); + viewHolder.tvHistoryChapter = convertView.findViewById(R.id.tv_book_history_chapter); + viewHolder.tvNewestChapter = convertView.findViewById(R.id.tv_book_newest_chapter); + viewHolder.llBookRead = convertView.findViewById(R.id.ll_book_read); + viewHolder.pbLoading = convertView.findViewById(R.id.pb_loading); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position); + return convertView; + } + + private void initView(int position) { + final Book book = getItem(position); + if (StringHelper.isEmpty(book.getImgUrl())) { + book.setImgUrl(""); + } + Glide.with(mContext) + .load(book.getImgUrl()) + .error(R.mipmap.no_image) + .placeholder(R.mipmap.no_image) + //设置圆角 + .apply(RequestOptions.bitmapTransform(new RoundedCorners(8))) + .into(viewHolder.ivBookImg); + + viewHolder.tvBookName.setText(book.getName()); + viewHolder.tvBookAuthor.setText(book.getAuthor()); + viewHolder.ivDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDeleteBookDialog(book); + } + }); + viewHolder.tvHistoryChapter.setText(book.getHistoryChapterId()); + assert book.getNewestChapterTitle() != null; + viewHolder.tvNewestChapter.setText(book.getNewestChapterTitle().replace("最近更新 ", "")); + + if (mEditState) { + viewHolder.tvNoReadNum.setVisibility(View.GONE); + viewHolder.ivDelete.setVisibility(View.VISIBLE); + viewHolder.ivBookImg.setOnClickListener(null); + viewHolder.llBookRead.setOnClickListener(null); + viewHolder.pbLoading.setVisibility(View.GONE); + } else { + viewHolder.ivDelete.setVisibility(View.GONE); + boolean isLoading = false; + try { + isLoading = isBookLoading(book.getId()); + }catch (Exception ignored){ + } + if (isLoading){ + viewHolder.pbLoading.setVisibility(View.VISIBLE); + viewHolder.tvNoReadNum.setVisibility(View.GONE); + }else { + viewHolder.pbLoading.setVisibility(View.GONE); + int notReadNum = book.getChapterTotalNum() - book.getHisttoryChapterNum() + book.getNoReadNum() - 1; + if (notReadNum != 0) { + viewHolder.tvNoReadNum.setVisibility(View.VISIBLE); + if (book.getNoReadNum() != 0) { + viewHolder.tvNoReadNum.setHighlight(true); + if (notReadNum == -1) { + notReadNum = book.getNoReadNum() - 1; + } + } else { + viewHolder.tvNoReadNum.setHighlight(false); + } + viewHolder.tvNoReadNum.setBadgeCount(notReadNum); + } else { + viewHolder.tvNoReadNum.setVisibility(View.GONE); + } + } + viewHolder.llBookRead.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(mContext, ReadActivity.class); + intent.putExtra(APPCONST.BOOK, book); + book.setNoReadNum(0); + mBookService.updateEntity(book); + mContext.startActivity(intent); + } + }); + viewHolder.ivBookImg.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(mContext, BookInfoActivity.class); + intent.putExtra(APPCONST.BOOK, book); + mContext.startActivity(intent); + } + }); + viewHolder.llBookRead.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (!ismEditState()) { + AlertDialog bookDialog = new AlertDialog.Builder(mContext) + .setTitle(book.getName()) + .setAdapter(new ArrayAdapter<>(mContext, + android.R.layout.simple_list_item_1, menu), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + book.setSortCode(0); + mBookService.updateBook(book); + mBookcasePresenter.init(); + TextHelper.showText("书籍《" + book.getName() + "》移至顶部成功!"); + break; + case 1: + downloadBook(book); + break; + case 2: + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + try { + if (unionChapterCathe(book)) { + DialogCreator.createTipDialog(mContext, + "缓存导出成功,导出目录:" + + APPCONST.TXT_BOOK_DIR); + } else { + DialogCreator.createTipDialog(mContext, + "章节目录为空或未找到缓存文件,缓存导出失败!"); + } + } catch (IOException e) { + e.printStackTrace(); + DialogCreator.createTipDialog(mContext, + "章节目录为空或未找到缓存文件,缓存导出失败!"); + } + } + }); + break; + case 3: + showDeleteBookDialog(book); + break; + } + } + }) + .setNegativeButton(null, null) + .setPositiveButton(null, null) + .create(); + bookDialog.show(); + return true; + } + return false; + } + }); + } + } + + + static class ViewHolder extends BookcaseAdapter.ViewHolder { + TextView tvBookAuthor; + TextView tvHistoryChapter; + TextView tvNewestChapter; + LinearLayout llBookRead; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDragAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDragAdapter.java new file mode 100644 index 0000000..9b8d2c5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDragAdapter.java @@ -0,0 +1,187 @@ +package xyz.fycz.myreader.ui.home.bookcase; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import androidx.appcompat.app.AlertDialog; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; + +import java.io.IOException; +import java.util.ArrayList; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.ui.read.ReadActivity; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; + + + +public class BookcaseDragAdapter extends BookcaseAdapter { + + + public BookcaseDragAdapter(Context context, int textViewResourceId, ArrayList objects, + boolean editState, BookcasePresenter bookcasePresenter) { + super(context, textViewResourceId, objects, editState, bookcasePresenter); + } + + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null || convertView.getTag() instanceof BookcaseDetailedAdapter.ViewHolder) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(mContext).inflate(mResourceId, null); + viewHolder.ivBookImg = convertView.findViewById(R.id.iv_book_img); + viewHolder.tvBookName = convertView.findViewById(R.id.tv_book_name); + viewHolder.tvNoReadNum = convertView.findViewById(R.id.tv_no_read_num); + viewHolder.ivDelete = convertView.findViewById(R.id.iv_delete); + viewHolder.pbLoading = convertView.findViewById(R.id.pb_loading); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position, viewHolder); + return convertView; + } + + private void initView(int position, ViewHolder viewHolder) { + final Book book = getItem(position); + if (StringHelper.isEmpty(book.getImgUrl())) { + book.setImgUrl(""); + } + + Glide.with(mContext) + .load(book.getImgUrl()) + .error(R.mipmap.no_image) + .placeholder(R.mipmap.no_image) + //设置圆角 + .apply(RequestOptions.bitmapTransform(new RoundedCorners(8))) + .into(viewHolder.ivBookImg); + + viewHolder.tvBookName.setText(book.getName()); + viewHolder.ivDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDeleteBookDialog(book); + } + }); + + + if (mEditState) { + viewHolder.tvNoReadNum.setVisibility(View.GONE); + viewHolder.ivDelete.setVisibility(View.VISIBLE); + viewHolder.pbLoading.setVisibility(View.GONE); + viewHolder.ivBookImg.setOnClickListener(null); + } else { + viewHolder.ivDelete.setVisibility(View.GONE); + boolean isLoading = false; + try { + isLoading = isBookLoading(book.getId()); + }catch (Exception ignored){ + } + if (isLoading){ + viewHolder.pbLoading.setVisibility(View.VISIBLE); + viewHolder.tvNoReadNum.setVisibility(View.GONE); + }else { + viewHolder.pbLoading.setVisibility(View.GONE); + int notReadNum = book.getChapterTotalNum() - book.getHisttoryChapterNum() + book.getNoReadNum() - 1; + if (notReadNum != 0){ + viewHolder.tvNoReadNum.setVisibility(View.VISIBLE); + if(book.getNoReadNum() != 0){ + viewHolder.tvNoReadNum.setHighlight(true); + if (notReadNum == -1){ + notReadNum = book.getNoReadNum() - 1; + } + }else { + viewHolder.tvNoReadNum.setHighlight(false); + } + viewHolder.tvNoReadNum.setBadgeCount(notReadNum); + }else { + viewHolder.tvNoReadNum.setVisibility(View.GONE); + } + } + viewHolder.ivBookImg.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent( mContext, ReadActivity.class); + intent.putExtra(APPCONST.BOOK, book); + book.setNoReadNum(0); + mBookService.updateEntity(book); + mContext.startActivity(intent); + } + }); + viewHolder.ivBookImg.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (!ismEditState()){ + AlertDialog bookDialog = new AlertDialog.Builder(mContext) + .setTitle(book.getName()) + .setAdapter(new ArrayAdapter<>(mContext, + android.R.layout.simple_list_item_1, menu), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + book.setSortCode(0); + mBookService.updateBook(book); + mBookcasePresenter.init(); + TextHelper.showText("书籍《" + book.getName() + "》移至顶部成功!"); + break; + case 1: + downloadBook(book); + break; + case 2: + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + try { + if (unionChapterCathe(book)) { + DialogCreator.createTipDialog(mContext, + "缓存导出成功,导出目录:" + + APPCONST.TXT_BOOK_DIR); + }else { + DialogCreator.createTipDialog(mContext, + "章节目录为空或未找到缓存文件,缓存导出失败!"); + } + } catch (IOException e) { + e.printStackTrace(); + DialogCreator.createTipDialog(mContext, + "章节目录为空或未找到缓存文件,缓存导出失败!"); + } + } + }); + break; + case 3: + showDeleteBookDialog(book); + break; + } + } + }) + .setNegativeButton(null, null) + .setPositiveButton(null, null) + .create(); + bookDialog.show(); + return true; + } + return false; + } + }); + } + + } + class ViewHolder extends BookcaseAdapter.ViewHolder { + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseFragment.java new file mode 100644 index 0000000..dfe0d22 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseFragment.java @@ -0,0 +1,110 @@ +package xyz.fycz.myreader.ui.home.bookcase; + + +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.LinearLayout; + +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import androidx.fragment.app.Fragment; +import com.scwang.smartrefresh.layout.SmartRefreshLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.custom.DragSortGridView; + +import butterknife.ButterKnife; +import butterknife.BindView; +import butterknife.Unbinder; + +/** + * A simple {@link Fragment} subclass. + */ +public class BookcaseFragment extends Fragment { + @BindView(R.id.srl_content) + SmartRefreshLayout srlContent; + @BindView(R.id.ll_no_data_tips) + LinearLayout llNoDataTips; + @BindView(R.id.gv_book) + DragSortGridView gvBook; + @BindView(R.id.rl_download_tip) + RelativeLayout rlDownloadTip; + @BindView(R.id.tv_download_tip) + TextView tvDownloadTip; + @BindView(R.id.tv_stop_download) + TextView tvStopDownload; + @BindView(R.id.pb_download) + ProgressBar pbDownload; + + Unbinder unbinder; + + private BookcasePresenter mBookcasePresenter; + + public BookcaseFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_bookcase, container, false); + unbinder = ButterKnife.bind(this, view); + mBookcasePresenter = new BookcasePresenter(this); + mBookcasePresenter.start(); + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onResume() { + super.onResume(); + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + mBookcasePresenter.getData(); + } else { + mBookcasePresenter.init(); + } + } + + public LinearLayout getLlNoDataTips() { + return llNoDataTips; + } + + public DragSortGridView getGvBook() { + return gvBook; + } + + public SmartRefreshLayout getSrlContent() { + return srlContent; + } + + public RelativeLayout getRlDownloadTip() { + return rlDownloadTip; + } + + public TextView getTvDownloadTip() { + return tvDownloadTip; + } + + public TextView getTvStopDownload() { + return tvStopDownload; + } + + public ProgressBar getPbDownload() { + return pbDownload; + } + + public BookcasePresenter getmBookcasePresenter() { + return mBookcasePresenter; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcasePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcasePresenter.java new file mode 100644 index 0000000..25fa1e6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcasePresenter.java @@ -0,0 +1,808 @@ +package xyz.fycz.myreader.ui.home.bookcase; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.PopupMenu; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; + +import com.scwang.smartrefresh.layout.api.RefreshLayout; +import com.scwang.smartrefresh.layout.listener.OnRefreshListener; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.backup.BackupAndRestore; +import xyz.fycz.myreader.backup.UserService; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.crawler.ReadCrawlerUtil; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.custom.DragSortGridView; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.enums.BookcaseStyle; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.ui.home.MainActivity; +import xyz.fycz.myreader.ui.search.SearchBookActivity; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.CommonApi; + +import static xyz.fycz.myreader.application.MyApplication.checkVersionByServer; + + +public class BookcasePresenter implements BasePresenter { + + private final BookcaseFragment mBookcaseFragment; + private final ArrayList mBooks = new ArrayList<>();//书目数组 + private BookcaseAdapter mBookcaseAdapter; + private final BookService mBookService; + private final ChapterService mChapterService; + private final MainActivity mMainActivity; + private boolean isBookcaseStyleChange; + private final Setting mSetting; + private final List errorLoadingBooks = new ArrayList<>(); + private int finishLoadBookCount = 0; + private final BackupAndRestore mBackupAndRestore; + // private int notifyId = 11; + private ExecutorService es = Executors.newFixedThreadPool(1);//更新/下载线程池 + public ExecutorService getEs() { + return es; + } + private String downloadingBook; + private String downloadingChapter; + private boolean isDownloadFinish; + private boolean isStopDownload; + private int downloadProcess; + private PopupMenu pm; + private final String[] backupMenu = { + MyApplication.getmContext().getResources().getString(R.string.menu_backup_backup), + MyApplication.getmContext().getResources().getString(R.string.menu_backup_restore), + }; + private final String[] webBackupMenu = { + MyApplication.getmContext().getResources().getString(R.string.menu_backup_backup), + MyApplication.getmContext().getResources().getString(R.string.menu_backup_restore), + MyApplication.getmContext().getResources().getString(R.string.menu_backup_webRestore) + + }; + // private ChapterService mChapterService; + @SuppressLint("HandlerLeak") + public final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + MyApplication.runOnUiThread(() -> mBookcaseAdapter.notifyDataSetChanged()); + finishLoadBookCount++; + mBookcaseFragment.getSrlContent().finishRefresh(); + break; + case 2: + mBookcaseFragment.getSrlContent().finishRefresh(); + break; + case 3: + mBookcaseAdapter.notifyDataSetChanged(); + break; + case 4: + showErrorLoadingBooks(); + break; + case 5: + backup(); + break; + case 6: + restore(); + break; + case 7: + init(); + break; + case 8: + sendNotification(downloadingBook); + break; + case 9: + mBookcaseFragment.getRlDownloadTip().setVisibility(View.GONE); + isDownloadFinish = true; + break; + case 10: + mBookcaseFragment.getTvDownloadTip().setText("正在初始化缓存任务..."); + mBookcaseFragment.getPbDownload().setProgress(0); + mBookcaseFragment.getRlDownloadTip().setVisibility(View.VISIBLE); + break; + case 11: + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + createMenu(); + } + }); + break; + case 12: + mBookcaseFragment.getTvStopDownload().setVisibility(View.GONE); + break; + case 13: + mBookcaseFragment.getTvStopDownload().setVisibility(View.VISIBLE); + break; + } + } + }; + + public BookcasePresenter(BookcaseFragment bookcaseFragment) { + mBookcaseFragment = bookcaseFragment; + mBookService = new BookService(); + mChapterService = new ChapterService(); + mMainActivity = ((MainActivity) (mBookcaseFragment.getActivity())); +// mChapterService = new ChapterService(); + mSetting = SysManager.getSetting(); + mBackupAndRestore = new BackupAndRestore(); + } + + @Override + public void start() { + if (mSetting.getBookcaseStyle() == null) { + mSetting.setBookcaseStyle(BookcaseStyle.listMode); + } + getData(); + //是否启用下拉刷新(默认启用) + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + mBookcaseFragment.getSrlContent().setEnableRefresh(false); + } + //设置是否启用内容视图拖动效果 + mBookcaseFragment.getSrlContent().setEnableHeaderTranslationContent(false); + //设置刷新监听器 + mBookcaseFragment.getSrlContent().setOnRefreshListener(refreshlayout -> initNoReadNum()); + //搜索按钮监听器 + mBookcaseFragment.getLlNoDataTips().setOnClickListener(view -> { + Intent intent = new Intent(mBookcaseFragment.getContext(), SearchBookActivity.class); + mBookcaseFragment.startActivity(intent); + }); + + //长按事件监听 + mBookcaseFragment.getGvBook().setOnItemLongClickListener((parent, view, position, id) -> false); + + //更多按钮监听器 + mMainActivity.getIvMore().setOnClickListener(v -> mHandler.sendMessage(mHandler.obtainMessage(11))); + + //完成按钮监听器 + mMainActivity.getTvEditFinish().setOnClickListener(v -> editBookcase(false)); + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + //滑动监听器 + mBookcaseFragment.getGvBook().getmScrollView().setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + if (!mBookcaseAdapter.ismEditState()) { + mBookcaseFragment.getSrlContent().setEnableRefresh(scrollY == 0); + } + }); + } + + //停止按钮监听器 + mBookcaseFragment.getTvStopDownload().setOnClickListener(v -> { + if (downloadProcess == 99){ + TextHelper.showText("开始缓存下一本书籍!"); + isDownloadFinish = true; + }else { + isStopDownload = true; + } + }); + + } + + /** + * 编辑书架 + * + * @param isEdit + */ + private void editBookcase(boolean isEdit) { + if (isEdit) { + if (mBooks.size() > 0) { + mBookcaseFragment.getSrlContent().setEnableRefresh(false); + mBookcaseAdapter.setmEditState(true); + mBookcaseFragment.getGvBook().setDragModel(DragSortGridView.DRAG_BY_LONG_CLICK); + mBookcaseAdapter.notifyDataSetChanged(); + mMainActivity.getRlCommonTitle().setVisibility(View.GONE); + mMainActivity.getRlEditTitle().setVisibility(View.VISIBLE); + mMainActivity.getIvMore().setVisibility(View.GONE); +// VibratorUtil.Vibrate(mBookcaseFragment.getActivity(), 100); + } else { + TextHelper.showText("当前无任何书籍,无法编辑书架!"); + } + } else { + mMainActivity.getRlCommonTitle().setVisibility(View.VISIBLE); + mMainActivity.getIvMore().setVisibility(View.VISIBLE); + mMainActivity.getRlEditTitle().setVisibility(View.GONE); + if (mBookcaseFragment.getGvBook().getmScrollView().getScrollY() == 0 + && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mBookcaseFragment.getSrlContent().setEnableRefresh(true); + } + mBookcaseFragment.getGvBook().setDragModel(-1); + mBookcaseAdapter.setmEditState(false); + mBookcaseAdapter.notifyDataSetChanged(); + } + } + + + protected void init() { + initBook(); + if (mBooks.size() == 0) { + mBookcaseFragment.getGvBook().setVisibility(View.GONE); + mBookcaseFragment.getLlNoDataTips().setVisibility(View.VISIBLE); + } else { + if (mBookcaseAdapter == null || isBookcaseStyleChange) { + switch (mSetting.getBookcaseStyle()) { + case listMode: + mBookcaseAdapter = new BookcaseDetailedAdapter(mBookcaseFragment.getContext(), R.layout.gridview_book_detailed_item, mBooks, false, this); + mBookcaseFragment.getGvBook().setNumColumns(1); + break; + case threePalaceMode: + mBookcaseAdapter = new BookcaseDragAdapter(mBookcaseFragment.getContext(), R.layout.gridview_book_item, mBooks, false, this); + mBookcaseFragment.getGvBook().setNumColumns(3); + break; + } + mBookcaseFragment.getGvBook().setDragModel(-1); + mBookcaseFragment.getGvBook().setTouchClashparent(((MainActivity) (mBookcaseFragment.getActivity())).getVpContent()); + mBookcaseFragment.getGvBook().setAdapter(mBookcaseAdapter); + isBookcaseStyleChange = false; + } else { + mBookcaseAdapter.notifyDataSetChanged(); + } + mBookcaseFragment.getLlNoDataTips().setVisibility(View.GONE); + mBookcaseFragment.getGvBook().setVisibility(View.VISIBLE); + } + } + + + public void getData() { + init(); + initNoReadNum(); + } + + private void initBook() { + mBooks.clear(); + mBooks.addAll(mBookService.getAllBooks()); + for (int i = 0; i < mBooks.size(); i++) { + if (mBooks.get(i).getSortCode() != i + 1) { + mBooks.get(i).setSortCode(i + 1); + mBookService.updateEntity(mBooks.get(i)); + } + } + } + + private void initNoReadNum() { + errorLoadingBooks.clear(); + finishLoadBookCount = 0; + for (Book book : mBooks) { + mBookcaseAdapter.getIsLoading().put(book.getId(), true); + } + if (mBooks.size() > 0) { + mHandler.sendMessage(mHandler.obtainMessage(3)); + } + for (final Book book : mBooks) { + if ("本地书籍".equals(book.getType())){ + mBookcaseAdapter.getIsLoading().put(book.getId(), false); + mHandler.sendMessage(mHandler.obtainMessage(1)); + continue; + } + Thread update = new Thread(() -> { + final ArrayList mChapters = (ArrayList) mChapterService.findBookAllChapterByBookId(book.getId()); + final ReadCrawler mReadCrawler = ReadCrawlerUtil.getReadCrawler(book.getSource()); + CommonApi.getBookChapters(book.getChapterUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + final ArrayList chapters = (ArrayList) o; + int noReadNum = chapters.size() - book.getChapterTotalNum(); + book.setNoReadNum(Math.max(noReadNum, 0)); + book.setNewestChapterTitle(chapters.get(chapters.size() - 1).getTitle()); + mBookcaseAdapter.getIsLoading().put(book.getId(), false); + updateAllOldChapterData(mChapters, chapters, book.getId()); + mHandler.sendMessage(mHandler.obtainMessage(1)); + mBookService.updateEntity(book); + } + + @Override + public void onError(Exception e) { + mBookcaseAdapter.getIsLoading().put(book.getId(), false); + errorLoadingBooks.add(book); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + }); + es.submit(update); + } + MyApplication.getApplication().newThread(() -> { + while (true) { + if (finishLoadBookCount == mBooks.size()) { + mHandler.sendMessage(mHandler.obtainMessage(4)); + break; + } + } + }); + + } + + /** + * 更新所有章节 + * + * @param newChapters + */ + private void updateAllOldChapterData(ArrayList mChapters, ArrayList newChapters, String bookId) { + int i; + for (i = 0; i < mChapters.size() && i < newChapters.size(); i++) { + Chapter oldChapter = mChapters.get(i); + Chapter newChapter = newChapters.get(i); + if (!oldChapter.getTitle().equals(newChapter.getTitle())) { + oldChapter.setTitle(newChapter.getTitle()); + oldChapter.setUrl(newChapter.getUrl()); + oldChapter.setContent(null); + mChapterService.saveOrUpdateChapter(oldChapter, null); + } + } + if (mChapters.size() < newChapters.size()) { + int start = mChapters.size(); + for (int j = mChapters.size(); j < newChapters.size(); j++) { + newChapters.get(j).setId(StringHelper.getStringRandom(25)); + newChapters.get(j).setBookId(bookId); + mChapters.add(newChapters.get(j)); +// mChapterService.addChapter(newChapters.get(j)); + } + mChapterService.addChapters(mChapters.subList(start, mChapters.size())); + } else if (mChapters.size() > newChapters.size()) { + for (int j = newChapters.size(); j < mChapters.size(); j++) { + mChapterService.deleteEntity(mChapters.get(j)); + mChapterService.deleteChapterCacheFile(mChapters.get(j)); + } + mChapters.subList(0, newChapters.size()); + } + } + + private void setThemeColor(int colorPrimary, int colorPrimaryDark) { +// mToolbar.setBackgroundResource(colorPrimary); + mBookcaseFragment.getSrlContent().setPrimaryColorsId(colorPrimary, android.R.color.white); + if (Build.VERSION.SDK_INT >= 21) { + mBookcaseFragment.getActivity().getWindow().setStatusBarColor(ContextCompat.getColor(mBookcaseFragment.getContext(), colorPrimaryDark)); + } + } + + /** + * 显示更新失败的书籍信息 + */ + private void showErrorLoadingBooks() { + StringBuilder s = new StringBuilder(); + for (Book book : errorLoadingBooks) { + s.append(book.getName()); + s.append("、"); + } + if (errorLoadingBooks.size() > 0) { + s.deleteCharAt(s.lastIndexOf("、")); + s.append(" 更新失败"); + TextHelper.showText(s.toString()); + } + } + + /** + * 创建菜单栏 + */ + private void createMenu() { + //如果菜单栏已经创建直接show + if (pm != null) { + pm.show(); + return; + } + pm = new PopupMenu(mMainActivity, mMainActivity.getIvMore()); + pm.getMenuInflater().inflate(R.menu.menu_book, pm.getMenu()); + setIconEnable(pm.getMenu(), true); + if (MyApplication.isApkInDebug(mMainActivity)) { + pm.getMenu().getItem(4).setVisible(true); + } + pm.setOnMenuItemClickListener(item -> { + switch (item.getItemId()) { + case R.id.action_edit: + editBookcase(true); + return true; + case R.id.action_styleChange: + mBookcaseFragment.getGvBook().getmScrollView().setScrollY(0); + if (mSetting.getBookcaseStyle().equals(BookcaseStyle.listMode)) { + mSetting.setBookcaseStyle(BookcaseStyle.threePalaceMode); + TextHelper.showText("已切换为三列网格视图!"); + } else { + mSetting.setBookcaseStyle(BookcaseStyle.listMode); + TextHelper.showText("已切换为列表视图!"); + } + isBookcaseStyleChange = true; + SysManager.saveSetting(mSetting); + init(); + return true; + case R.id.action_addLocalBook: + TextHelper.showText("请选择一个txt格式的书籍文件"); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("text/plain"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + mMainActivity.startActivityForResult(intent, APPCONST.SELECT_FILE_CODE); + break; + case R.id.action_download_all: + DialogCreator.createCommonDialog(mMainActivity, "一键缓存(实验)", + mMainActivity.getString(R.string.all_cathe_tip), true, + (dialog, which) -> downloadAll(), null); + + return true; + case R.id.action_update: + checkVersionByServer(mMainActivity, true, mBookcaseFragment); + return true; + case R.id.action_backup: + final String[] menu; + if (UserService.isLogin()) { + menu = webBackupMenu; + } else { + menu = backupMenu; + } + AlertDialog bookDialog = new AlertDialog.Builder(mMainActivity) + .setTitle(mMainActivity.getResources().getString(R.string.menu_bookcase_backup)) + .setAdapter(new ArrayAdapter<>(mMainActivity, + android.R.layout.simple_list_item_1, menu), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + mHandler.sendMessage(mHandler.obtainMessage(5)); + break; + case 1: + mHandler.sendMessage(mHandler.obtainMessage(6)); + break; + case 2: + webRestore(); + break; + } + } + }) + .setNegativeButton(null, null) + .setPositiveButton(null, null) + .create(); + bookDialog.show(); + return true; + case R.id.action_disclaimer: + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(mMainActivity.getAssets().open("disclaimer.fy"))); + StringBuilder disclaimer = new StringBuilder(); + String line = null; + while ((line = br.readLine()) != null) { + disclaimer.append(line); + disclaimer.append("\r\n\n"); + } + DialogCreator.createTipDialog(mMainActivity, "免责声明", disclaimer.toString()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return true; + } + return false; + }); + pm.show(); + } + + /** + * 显示菜单图标 + * + * @param menu 菜单 + * @param enable 是否显示图标 + */ + private void setIconEnable(Menu menu, boolean enable) { + try { + Class clazz = Class.forName("com.android.internal.view.menu.MenuBuilder"); + Method m = clazz.getDeclaredMethod("setOptionalIconsVisible", boolean.class); + m.setAccessible(true); + //MenuBuilder实现Menu接口,创建菜单时,传进来的menu其实就是MenuBuilder对象(java的多态特征) + m.invoke(menu, enable); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 备份 + */ + private void backup() { + DialogCreator.createCommonDialog(mMainActivity, "确认备份吗?", "新备份会替换原有备份!", true, + (dialogInterface, i) -> { + dialogInterface.dismiss(); + if (UserService.isLogin()) { + new Thread(new Runnable() { + @Override + public void run() { + if (mBackupAndRestore.backup("localBackup") & UserService.webBackup()) { + DialogCreator.createTipDialog(mMainActivity, "备份(本地和网络)成功,本地备份文件路径:" + APPCONST.BACKUP_FILE_DIR); + } else { + DialogCreator.createTipDialog(mMainActivity, "未登录或未给予储存权限,备份失败!"); + } + } + }).start(); + } else { + if (mBackupAndRestore.backup("localBackup")) { + DialogCreator.createTipDialog(mMainActivity, "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR); + } else { + DialogCreator.createTipDialog(mMainActivity, "未给予储存权限,备份失败!"); + } + } + }, (dialogInterface, i) -> dialogInterface.dismiss()); + } + + /** + * 恢复 + */ + private void restore() { + DialogCreator.createCommonDialog(mMainActivity, "确认恢复吗?", "恢复书架会覆盖原有书架!", true, + (dialogInterface, i) -> { + dialogInterface.dismiss(); + if (mBackupAndRestore.restore("localBackup")) { + mHandler.sendMessage(mHandler.obtainMessage(7)); +// DialogCreator.createTipDialog(mMainActivity, +// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!"); + TextHelper.showText("书架恢复成功!"); + } else { + DialogCreator.createTipDialog(mMainActivity, "未找到备份文件或未给予储存权限,恢复失败!"); + } + }, (dialogInterface, i) -> dialogInterface.dismiss()); + } + + + /** + * 恢复 + */ + private void webRestore() { + DialogCreator.createCommonDialog(mMainActivity, "确认恢复吗?", "恢复书架会覆盖原有书架!", true, + (dialogInterface, i) -> { + dialogInterface.dismiss(); + new Thread(() -> { + if (UserService.webRestore()) { + mHandler.sendMessage(mHandler.obtainMessage(7)); +// DialogCreator.createTipDialog(mMainActivity, +// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");、 + TextHelper.showText("书架恢复成功!"); + } else { + DialogCreator.createTipDialog(mMainActivity, "未找到备份文件,恢复失败!"); + } + }).start(); + }, (dialogInterface, i) -> dialogInterface.dismiss()); + } + +/**********************************************缓存书籍***************************************************************/ + /** + * 缓存所有书籍 + */ + private void downloadAll() { + if (!NetworkUtils.isNetWorkAvailable()) { + TextHelper.showText("无网络连接!"); + return; + } + mHandler.sendMessage(mHandler.obtainMessage(13)); + isStopDownload = false; + MyApplication.getApplication().newThread(() -> { + downloadFor: + for (final Book book : mBooks) { + if (BookSource.pinshu.toString().equals(book.getSource()) || "本地书籍".equals(book.getType())){ + continue; + } + isDownloadFinish = false; + Thread downloadThread = new Thread(new Runnable() { + @Override + public void run() { + ArrayList chapters = (ArrayList) mChapterService.findBookAllChapterByBookId(book.getId()); + addDownload(book, chapters, + book.getHisttoryChapterNum(), chapters.size()); + } + }); + es.submit(downloadThread); + do { + try { + Thread.sleep(800); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (isStopDownload) { + DialogCreator.createTipDialog(mMainActivity, "请等待当前书籍缓存完成后缓存任务将全部停止!"); + break downloadFor; + } + } while (!isDownloadFinish); + } + mHandler.sendMessage(mHandler.obtainMessage(12)); + }); + } + + /** + * 添加下载 + * @param book + * @param mChapters + * @param begin + * @param end + */ + public void addDownload(final Book book, final ArrayList mChapters, int begin, int end) { + if ("本地书籍".equals(book.getType())){ + TextHelper.showText("《" + book.getName() + "》是本地书籍,不能缓存"); + return; + } + if (mChapters.size() == 0){ + TextHelper.showText("《" + book.getName() + "》章节目录为空,缓存失败,请刷新后重试"); + return; + } + mHandler.sendMessage(mHandler.obtainMessage(10)); + downloadingBook = book.getName(); + final int finalBegin = Math.max(0, begin); + final int finalEnd = Math.min(end, mChapters.size()); + final int needCacheChapterNum = finalEnd - finalBegin; + final int[] curCacheChapterNum = {0}; + //final boolean[] isDownloadFinish = new boolean[1]; + for (int i = finalBegin; i < finalEnd; i++) { + //isDownloadFinish[0] = false; + final Chapter chapter = mChapters.get(i); + if (StringHelper.isEmpty(chapter.getContent())) { + getChapterContent(book, chapter, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + curCacheChapterNum[0]++; + downloadingChapter = chapter.getTitle(); + downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum; + mChapterService.saveOrUpdateChapter(chapter, (String) o); + // isDownloadFinish[0] = true; + mHandler.sendMessage(mHandler.obtainMessage(8)); + } + + @Override + public void onError(Exception e) { + // isDownloadFinish[0] = true; + curCacheChapterNum[0]++; + downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum; + mHandler.sendMessage(mHandler.obtainMessage(8)); + } + }); + } else { + //isDownloadFinish[0] = true; + curCacheChapterNum[0]++; + downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum; + mHandler.sendMessage(mHandler.obtainMessage(8)); + } + if (curCacheChapterNum[0] == needCacheChapterNum) { + mHandler.sendMessage(mHandler.obtainMessage(9)); + } + /*while (true){ + if (isDownloadFinish[0]){ + break; + } + }*/ + if (isStopDownload) { + break; + } + } + } + + + /** + * 获取章节内容 + * + * @param + * @param + */ + private void getChapterContent(Book mBook, final Chapter chapter, final ResultCallback resultCallback) { + if (StringHelper.isEmpty(chapter.getBookId())) { + chapter.setBookId(mBook.getId()); + } + if (!StringHelper.isEmpty(chapter.getContent())) { + if (resultCallback != null) { + resultCallback.onFinish(mChapterService.getChapterCatheContent(chapter), 0); + } + } else { + ReadCrawler mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource()); + CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, resultCallback); + } + } + + private void sendNotification(String book) { + mBookcaseFragment.getPbDownload().setProgress(downloadProcess); + mBookcaseFragment.getTvDownloadTip().setText("正在缓存:" + book + "[" + downloadProcess + "%]"); + if (downloadProcess == 100) { + mHandler.sendMessage(mHandler.obtainMessage(9)); + } + } + + /** + * 添加本地书籍 + * @param path + */ + public void addLocalBook(String path) { + File file = new File(path); + if (!file.exists()){ + return; + } + Book book = new Book(); + book.setName(file.getName().replace(".txt", "")); + book.setChapterUrl(path); + book.setType("本地书籍"); + book.setHistoryChapterId("未开始阅读"); + book.setNewestChapterTitle("未拆分章节"); + book.setAuthor("本地书籍"); + book.setSource(BookSource.local.toString()); + book.setDesc(""); + + //判断书籍是否已经添加 + Book existsBook = mBookService.findBookByAuthorAndName(book.getName(), book.getAuthor()); + if (book.equals(existsBook)){ + TextHelper.showText("该书籍已存在,请勿重复添加!"); + return; + } + mBookService.addBook(book); + TextHelper.showText("本地书籍添加成功"); + init(); + } + + + /*****************************************用于返回按钮判断*************************************/ + /** + * 判断是否处于编辑状态 + * @return + */ + public boolean ismEditState(){ + return mBookcaseAdapter.ismEditState(); + } + + /** + * 取消编辑状态 + */ + public void cancelEdit(){ + editBookcase(false); + } + /*class NotificationService extends Service{ + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + public void sendNotification(Book book, String chapterTitle){ + //创建 Notification.Builder 对象 + NotificationCompat.Builder builder = new NotificationCompat.Builder(mMainActivity, "channel_download") + .setSmallIcon(R.drawable.ic_download) + //通知栏大图标 + .setLargeIcon(BitmapFactory.decodeResource(mMainActivity.getResources(), R.mipmap.ic_launcher)) + //点击通知后自动清除 + .setAutoCancel(true) + .setContentTitle("正在下载:" + book.getName()) + .setContentText(chapterTitle); + builder.addAction(R.drawable.ic_stop_black_24dp, "取消", null); + //发送通知 + startForeground(1, builder.build()); + } + }*/ + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookAdapter.java new file mode 100644 index 0000000..2ac6d5c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookAdapter.java @@ -0,0 +1,167 @@ +package xyz.fycz.myreader.ui.home.bookstore; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +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.callback.ResultCallback; +import xyz.fycz.myreader.crawler.BiQuGeReadCrawler; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.webapi.CommonApi; + + +import java.util.List; + + +public class BookStoreBookAdapter extends RecyclerView.Adapter { + + private LayoutInflater mInflater; + private View view; + private List mDatas; + + private Context mContext; + private RecyclerView rvContent; + + + private OnItemClickListener onItemClickListener; + + + private Handler mHandle = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message message) { + + switch (message.what) { + case 1: + ViewHolder holder = (ViewHolder) message.obj; + int pos = message.arg1; + BookStoreBookAdapter.this.initOtherInfo(pos, holder); + break; + } + + return false; + + } + }); + + + BookStoreBookAdapter(Context context, List datas) { + mInflater = LayoutInflater.from(context); + mDatas = datas; + mContext = context; + } + + class ViewHolder extends RecyclerView.ViewHolder { + TextView tvBookName; + TextView tvBookAuthor; + TextView tvBookDesc; + TextView tvBookNewestChapter; + TextView tvBookSource; + ImageView ivBookImg; + ViewHolder() { + super(view); + } + + + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (rvContent == null) rvContent = (RecyclerView) parent; + view = mInflater.inflate(R.layout.listview_book_store_book_item, parent, false); + ViewHolder viewHolder = new ViewHolder(); + viewHolder.tvBookName = view.findViewById(R.id.tv_book_name); + viewHolder.tvBookAuthor = view.findViewById(R.id.tv_book_author); + viewHolder.tvBookDesc = view.findViewById(R.id.tv_book_desc); + viewHolder.ivBookImg = view.findViewById(R.id.iv_book_img); + viewHolder.tvBookNewestChapter = view.findViewById(R.id.tv_book_newest_chapter); + viewHolder.tvBookSource = view.findViewById(R.id.tv_book_source); + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, final int position) { + initView(position, holder); + if (onItemClickListener != null){ + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onItemClickListener.onClick(position, view); + } + }); + + } + } + + @Override + public int getItemCount() { + return mDatas.size(); + } + + private void initView(final int position, final ViewHolder holder) { + Book book = mDatas.get(position); + holder.tvBookName.setText(book.getName()); + holder.tvBookAuthor.setText(book.getAuthor()); + holder.tvBookNewestChapter.setText(""); + holder.tvBookSource.setText("书源:" + BookSource.fromString(book.getSource()).text); + holder.tvBookDesc.setText(""); + if (StringHelper.isEmpty(book.getImgUrl())){ + //获取小说详情 + CommonApi.getBookInfo(book, new BiQuGeReadCrawler(), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + mDatas.set(position,(Book) o); + //防止列表快速滑动时出现书的信息加载混乱的问题 + if (holder.tvBookName.getTag() == null || (int) holder.tvBookName.getTag() == position) { + mHandle.sendMessage(mHandle.obtainMessage(1,position,0,holder)); + } + } + @Override + public void onError(Exception e) { + } + }); + }else{ + initOtherInfo(position,holder); + } + } + + private void initOtherInfo(final int position, final ViewHolder holder){ + Book book = mDatas.get(position); + //图片 + Glide.with(mContext) + .load(book.getImgUrl()) +// .override(DipPxUtil.dip2px(getContext(), 80), DipPxUtil.dip2px(getContext(), 150)) + .error(R.mipmap.no_image) + .placeholder(R.mipmap.no_image) + //设置圆角 + .apply(RequestOptions.bitmapTransform(new RoundedCorners(8))) + .into(holder.ivBookImg); + //简介 + holder.tvBookDesc.setText("简介:" + book.getDesc()); + holder.tvBookNewestChapter.setText("最新章节:" + book.getNewestChapterTitle()); + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public interface OnItemClickListener{ + void onClick(int pos, View view); + } + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookTypeAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookTypeAdapter.java new file mode 100644 index 0000000..1bb4998 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreBookTypeAdapter.java @@ -0,0 +1,105 @@ +package xyz.fycz.myreader.ui.home.bookstore; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.entity.bookstore.BookType; + + +import java.util.List; + +public class BookStoreBookTypeAdapter extends RecyclerView.Adapter { + + private LayoutInflater mInflater; + private View view; + private List mDatas; + + private Context mContext; + private RecyclerView rvContent; + + private OnItemClickListener onItemClickListener; + + private int selectPos = 0; + + + BookStoreBookTypeAdapter(Context context, List datas) { + mInflater = LayoutInflater.from(context); + mDatas = datas; + mContext = context; + } + + class ViewHolder extends RecyclerView.ViewHolder { + TextView tvTypeName; + + ViewHolder() { + super(view); + } + + + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (rvContent == null) rvContent = (RecyclerView) parent; + view = mInflater.inflate(R.layout.listview_book_type_item, parent, false); + ViewHolder viewHolder = new ViewHolder(); + viewHolder.tvTypeName = view.findViewById(R.id.tv_type_name); + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, final int position) { + initView(position, holder); + if (position == selectPos) { + holder.itemView.setBackgroundResource(R.color.white); + } else { + holder.itemView.setBackgroundResource(R.color.sys_book_type_bg); + } + + if (onItemClickListener != null) { + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + onItemClickListener.onClick(position, view); + selectPos = position; + BookStoreBookTypeAdapter.this.notifyDataSetChanged(); + + } + }); + + } + + + } + + @Override + public int getItemCount() { + return mDatas.size(); + } + + private void initView(final int position, final ViewHolder holder) { + BookType bookType = mDatas.get(position); + holder.tvTypeName.setText(bookType.getTypeName()); + } + + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public interface OnItemClickListener { + + void onClick(int pos, View view); + + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreFragment.java new file mode 100644 index 0000000..6dc071a --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStoreFragment.java @@ -0,0 +1,60 @@ +package xyz.fycz.myreader.ui.home.bookstore; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import com.scwang.smartrefresh.layout.SmartRefreshLayout; +import xyz.fycz.myreader.R; + +/** + * A simple {@link Fragment} subclass. + */ +public class BookStoreFragment extends Fragment { + @BindView(R.id.rv_type_list) + RecyclerView rvTypeList; + @BindView(R.id.rv_book_list) + RecyclerView rvBookList; + @BindView(R.id.srl_book_list) + SmartRefreshLayout srlBookList; + + Unbinder unbinder; + private BookStorePresenter mBookStorePresenter; + + + public BookStoreFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_book_store, container, false); + unbinder = ButterKnife.bind(this, view); + mBookStorePresenter = new BookStorePresenter(this); + mBookStorePresenter.start(); + return view; + + } + + public RecyclerView getRvTypeList() { + return rvTypeList; + } + + public RecyclerView getRvBookList() { + return rvBookList; + } + + public SmartRefreshLayout getSrlBookList() { + return srlBookList; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStorePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStorePresenter.java new file mode 100644 index 0000000..cf1bb96 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStorePresenter.java @@ -0,0 +1,189 @@ +package xyz.fycz.myreader.ui.home.bookstore; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import com.scwang.smartrefresh.layout.api.RefreshLayout; +import com.scwang.smartrefresh.layout.listener.OnRefreshListener; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity; +import xyz.fycz.myreader.ui.home.MainActivity; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.webapi.BookStoreApi; + + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by zhao on 2017/7/25. + */ + +public class BookStorePresenter implements BasePresenter { + + private BookStoreFragment mBookStoreFragment; + private MainActivity mMainActivity; + private LinearLayoutManager mLinearLayoutManager; + private BookStoreBookTypeAdapter mBookStoreBookTypeAdapter; + private List mBookTypes; + + + private BookStoreBookAdapter mBookStoreBookAdapter; + private List bookList; + + private BookType curType; + + + + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + initTypeList(); + + break; + + case 2: + initBookList(); + + break; + + + } + } + }; + + BookStorePresenter(BookStoreFragment bookStoreFragment){ + mBookStoreFragment = bookStoreFragment; + mMainActivity = ((MainActivity) (mBookStoreFragment.getActivity())); + } + + @Override + public void start() { + + //无需加载更多 + mBookStoreFragment.getSrlBookList().setEnableLoadMore(false); + + //小说列表上拉刷新事件 + mBookStoreFragment.getSrlBookList().setOnRefreshListener(new OnRefreshListener() { + @Override + public void onRefresh(@NonNull RefreshLayout refreshLayout) { + BookStorePresenter.this.getBooksData(); + } + }); + + getData(); + + + } + + + /** + * 获取页面数据 + */ + private void getData(){ + BookStoreApi.getBookTypeList(URLCONST.nameSpace_biquge, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + mBookTypes = (ArrayList)o; + curType = mBookTypes.get(0); + mHandler.sendMessage(mHandler.obtainMessage(1)); + getBooksData(); + } + + @Override + public void onError(Exception e) { + TextHelper.showText(e.getMessage()); + } + }); + + + } + + /** + * 获取小数列表数据 + */ + private void getBooksData(){ + BookStoreApi.getBookRankList(curType.getUrl(), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + bookList= (ArrayList)o; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + + @Override + public void onError(Exception e) { + TextHelper.showText(e.getMessage()); + + } + }); + } + + + + + + /** + * 初始化类别列表 + */ + private void initTypeList(){ + + //设置布局管理器 + mLinearLayoutManager = new LinearLayoutManager(mBookStoreFragment.getActivity()); + mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mBookStoreFragment.getRvTypeList().setLayoutManager(mLinearLayoutManager); + mBookStoreBookTypeAdapter = new BookStoreBookTypeAdapter(mBookStoreFragment.getActivity(), mBookTypes); + mBookStoreFragment.getRvTypeList().setAdapter(mBookStoreBookTypeAdapter); + + //点击事件 + mBookStoreBookTypeAdapter.setOnItemClickListener(new BookStoreBookTypeAdapter.OnItemClickListener() { + @Override + public void onClick(int pos, View view) { + curType = mBookTypes.get(pos); + BookStorePresenter.this.getBooksData(); + } + }); + + + } + + + + /** + * 初始化小说列表 + */ + private void initBookList(){ + //设置布局管理器 + mLinearLayoutManager = new LinearLayoutManager(mBookStoreFragment.getActivity()); + mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mBookStoreFragment.getRvBookList().setLayoutManager(mLinearLayoutManager); + mBookStoreBookAdapter = new BookStoreBookAdapter(mBookStoreFragment.getActivity(), bookList); + mBookStoreFragment.getRvBookList().setAdapter(mBookStoreBookAdapter); + //点击事件 + mBookStoreBookAdapter.setOnItemClickListener(new BookStoreBookAdapter.OnItemClickListener() { + @Override + public void onClick(int pos, View view) { + Intent intent = new Intent(mBookStoreFragment.getActivity(), BookInfoActivity.class); + intent.putExtra(APPCONST.BOOK, bookList.get(pos)); + mBookStoreFragment.getActivity().startActivity(intent); + } + }); + //刷新动作完成 + mBookStoreFragment.getSrlBookList().finishRefresh(); + + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/ReadActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/read/ReadActivity.java new file mode 100644 index 0000000..b821fe8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/ReadActivity.java @@ -0,0 +1,140 @@ +package xyz.fycz.myreader.ui.read; + + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import butterknife.BindView; +import butterknife.ButterKnife; +import com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.widget.page.PageView; + +public class ReadActivity extends BaseActivity { + + + @BindView(R.id.pb_loading) + ProgressBar pbLoading; + @BindView(R.id.read_pv_page) + PageView srlContent; + @BindView(R.id.pb_nextPage) + VerticalSeekBar pbNextPage; + + @BindView(R.id.tv_end_page_tip) + TextView tvEndPageTip; + + private ReadPresenter mReadPresenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 隐藏应用程序的标题栏,即当前activity的label + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 隐藏android系统的状态栏 + setContentView(R.layout.activity_read_new); + ButterKnife.bind(this); + mReadPresenter = new ReadPresenter(this); + mReadPresenter.start(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mReadPresenter.onActivityResult(requestCode, resultCode, data); + } + + + + public ReadPresenter getmReadPresenter() { + return mReadPresenter; + + } + + public PageView getSrlContent() { + return srlContent; + } + + + public ProgressBar getPbLoading() { + return pbLoading; + } + + + + public TextView getTvEndPageTip() { + return tvEndPageTip; + } + + + @Override + protected void onDestroy() { + mReadPresenter.onDestroy(); + super.onDestroy(); + } + /*@Override + protected void onPause() { + super.onPause(); + }*/ + + @Override + public void onBackPressed() { + if (!mReadPresenter.isCollected()){ + DialogCreator.createCommonDialog(ReadActivity.this, "加入书架", "喜欢本书就加入书架吧", true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mReadPresenter.saveLastChapterReadPosition(); + mReadPresenter.setCollected(true); + exit(); + } + } + , new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mReadPresenter.deleteBook(); + exit(); + } + }); + } else { + mReadPresenter.saveLastChapterReadPosition(); + exit(); + } + } + + private void exit(){ + // 返回给BookDetail。 + Intent result = new Intent(); + result.putExtra(APPCONST.RESULT_IS_COLLECTED, mReadPresenter.isCollected()); + setResult(AppCompatActivity.RESULT_OK, result); + super.onBackPressed(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean isVolumeTurnPage = SysManager.getSetting().isVolumeTurnPage(); + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP : + if (isVolumeTurnPage) { + return mReadPresenter.getmPageLoader().skipToPrePage(); + } + case KeyEvent.KEYCODE_VOLUME_DOWN : + if (isVolumeTurnPage) { + return mReadPresenter.getmPageLoader().skipToNextPage(); + } + } + return super.onKeyDown(keyCode, event); + } + + public VerticalSeekBar getPbNextPage() { + return pbNextPage; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/ReadPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/ReadPresenter.java new file mode 100644 index 0000000..34ee8bb --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/ReadPresenter.java @@ -0,0 +1,1130 @@ +package xyz.fycz.myreader.ui.read; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.WindowManager; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.crawler.ReadCrawlerUtil; +import xyz.fycz.myreader.creator.DialogCreator; +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; +import xyz.fycz.myreader.greendao.service.BookMarkService; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.ui.font.FontsActivity; +import xyz.fycz.myreader.ui.read.catalog.CatalogActivity; +import xyz.fycz.myreader.util.BrightUtil; +import xyz.fycz.myreader.util.ScreenHelper; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.webapi.CommonApi; +import xyz.fycz.myreader.widget.page.LocalPageLoader; +import xyz.fycz.myreader.widget.page.PageLoader; +import xyz.fycz.myreader.widget.page.PageMode; +import xyz.fycz.myreader.widget.page.PageView; + +import static androidx.appcompat.app.AppCompatActivity.RESULT_OK; +import static xyz.fycz.myreader.util.UriFileUtil.getPath; + + +public class ReadPresenter implements BasePresenter { + + + private ReadActivity mReadActivity; + private Book mBook; + private ArrayList mChapters = new ArrayList<>(); + private ChapterService mChapterService; + private BookService mBookService; + private BookMarkService mBookMarkService; + private Setting mSetting; + + private boolean settingChange;//是否是设置改变 + + private boolean chapterChange;//章节是否改变 + + private boolean isCollected = true;//是否在书架中 + + private boolean isPrev;//是否向前翻页 + + private boolean autoPage = false;//是否自动翻页 + + private Dialog mSettingDialog;//设置视图 + private Dialog mSettingDetailDialog;//详细设置视图 + private Dialog mPageModeDialog;//翻页模式实体 + + private int curCacheChapterNum = 0;//缓存章节数 + + private int needCacheChapterNum;//需要缓存的章节 + + private PageLoader mPageLoader;//页面加载器 + + private int screenTimeOut = 60 * 3;//息屏时间(单位:秒),小于零表示常亮 + + private Runnable keepScreenRunnable;//息屏线程 + private Runnable autoPageRunnable;//自动翻页 + private Runnable upHpbNextPage;//更新自动翻页进度条 + + private ReadCrawler mReadCrawler; + + private int endPageTipCount = 3; + + private int nextPageTime;//下次翻页时间 + + private int upHpbInterval = 30;//更新翻页进度速度 + + private final CharSequence[] pageMode = { + "覆盖", "仿真", "滑动", "滚动", "无动画" + }; + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + init(); + break; + case 2: + int chapterPos = msg.arg1; + int pagePos = msg.arg2; + mPageLoader.skipToChapter(chapterPos); + mPageLoader.skipToPage(pagePos); + mReadActivity.getPbLoading().setVisibility(View.GONE); + break; + case 3: + updateDownloadProgress((TextView) msg.obj); + break; + case 4: + saveLastChapterReadPosition(); + screenOffTimerStart(); + break; + case 5: + if (endPageTipCount > 0) { + endPageTipCount--; + mHandler.sendEmptyMessageDelayed(5, 1000); + } else { + mReadActivity.getTvEndPageTip().setVisibility(View.GONE); + endPageTipCount = 3; + } + break; + case 6: + mPageLoader.openChapter(); + if (isPrev) {//判断是否向前翻页打开章节,如果是则打开自己后跳转到最后一页,否则不跳转 + try {//概率性异常(空指针异常) + mPageLoader.skipToPage(mPageLoader.getAllPagePos() - 1); + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + case 7: + TextHelper.showText("无网络连接!"); + mPageLoader.chapterError(); + break; + case 8: + mReadActivity.getPbLoading().setVisibility(View.GONE); + break; + } + } + }; + + // 接收电池信息和时间更新的广播 + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + int level = intent.getIntExtra("level", 0); + try { + mPageLoader.updateBattery(level); + }catch (Exception e){ + e.printStackTrace(); + } + } + // 监听分钟的变化 + else if (Intent.ACTION_TIME_TICK.equals(intent.getAction())) { + mPageLoader.updateTime(); + } + } + }; + + public ReadPresenter(ReadActivity readActivity) { + mReadActivity = readActivity; + mBookService = new BookService(); + mChapterService = new ChapterService(); + mBookMarkService = new BookMarkService(); + mSetting = SysManager.getSetting(); + } + + + @Override + public void start() { + //保持屏幕常亮 + keepScreenRunnable = this::unKeepScreenOn; + + autoPageRunnable = this::nextPage; + + upHpbNextPage = this::upHpbNextPage; + + //注册广播 + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); + intentFilter.addAction(Intent.ACTION_TIME_TICK); + mReadActivity.registerReceiver(mReceiver, intentFilter); + + if (!mSetting.isBrightFollowSystem()) { + BrightUtil.setBrightness(mReadActivity, BrightUtil.progressToBright(mSetting.getBrightProgress())); + } + + //是否直接打开本地txt文件 + String path = null; + if (Intent.ACTION_VIEW.equals(mReadActivity.getIntent().getAction())) { + Uri uri = mReadActivity.getIntent().getData(); + path = getPath(mReadActivity, uri); + } + if (!StringHelper.isEmpty(path)) { + //本地txt文件路径不为空,添加书籍 + addLocalBook(path); + } else { + //路径为空,说明不是直接打开txt文件 + mBook = (Book) mReadActivity.getIntent().getSerializableExtra(APPCONST.BOOK); + } + + + isCollected = mReadActivity.getIntent().getBooleanExtra("isCollected", true); + + mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource()); + + mPageLoader = mReadActivity.getSrlContent().getPageLoader(mBook, mReadCrawler, mSetting); + + mReadActivity.getPbLoading().setVisibility(View.VISIBLE); + + initListener(); + + getData(); + } + + /** + * 保存最后阅读章节的进度 + */ + protected void saveLastChapterReadPosition() { + if (!StringHelper.isEmpty(mBook.getId()) && mPageLoader.getPageStatus() == PageLoader.STATUS_FINISH) { + mBook.setLastReadPosition(mPageLoader.getPagePos()); + mBook.setHisttoryChapterNum(mPageLoader.getChapterPos()); + mBookService.updateEntity(mBook); + } + } + + /** + * 创建设置视图 + */ + private void createSettingView() { + mSettingDialog = DialogCreator.createReadSetting(mReadActivity, mSetting.isDayStyle(), + mPageLoader.getPagePos(), Math.max(0, mPageLoader.getAllPagePos() - 1), + mBook, mChapters.get(mPageLoader.getChapterPos()), + view -> {//返回 + mSettingDialog.dismiss(); + mReadActivity.onBackPressed(); + }, v -> {//刷新 + isPrev = false; + if (!"本地书籍".equals(mBook.getType())) { + mChapterService.deleteChapterCacheFile(mChapters.get(mPageLoader.getChapterPos())); + } + mPageLoader.refreshChapter(mChapters.get(mPageLoader.getChapterPos())); + }, v -> {//添加书签 + Chapter curChapter = mChapters.get(mPageLoader.getChapterPos()); + BookMark bookMark = new BookMark(); + bookMark.setBookId(mBook.getId()); + bookMark.setTitle(curChapter.getTitle()); + bookMark.setBookMarkChapterNum(mPageLoader.getChapterPos()); + bookMark.setBookMarkReadPosition(mPageLoader.getPagePos()); + mBookMarkService.addOrUpdateBookMark(bookMark); + DialogCreator.createTipDialog(mReadActivity, "《" + mBook.getName() + + "》:" + bookMark.getTitle() + "[" + (bookMark.getBookMarkReadPosition() + 1) + + "]\n书签添加成功,书签列表可在目录界面查看!"); + }, (chapterTitle, chapterUrl, sbReadChapterProgress) -> {//上一章 + isPrev = false; + mPageLoader.skipPreChapter(); + String url = mChapters.get(mPageLoader.getChapterPos()).getUrl(); + if ("null".equals(mBook.getSource()) || BookSource.fynovel.equals(mBook.getSource())) { + if (!url.contains("novel.fycz.xyz")) { + url = URLCONST.nameSpace_FY + url; + } + } + chapterTitle.setText(mChapters.get(mPageLoader.getChapterPos()).getTitle()); + chapterUrl.setText(StringHelper.isEmpty(url) ? mChapters. + get(mPageLoader.getChapterPos()).getId() : url); + sbReadChapterProgress.setProgress(0); + sbReadChapterProgress.setMax(Math.max(0, mPageLoader.getAllPagePos() - 1)); + chapterChange = false; + }, (chapterTitle, chapterUrl, sbReadChapterProgress) -> {//下一章 + isPrev = false; + mPageLoader.skipNextChapter(); + String url = mChapters.get(mPageLoader.getChapterPos()).getUrl(); + if ("null".equals(mBook.getSource()) || BookSource.fynovel.equals(mBook.getSource())) { + if (!url.contains("novel.fycz.xyz")) { + url = URLCONST.nameSpace_FY + url; + } + } + chapterTitle.setText(mChapters.get(mPageLoader.getChapterPos()).getTitle()); + chapterUrl.setText(StringHelper.isEmpty(url) ? mChapters. + get(mPageLoader.getChapterPos()).getId() : url); + sbReadChapterProgress.setProgress(0); + sbReadChapterProgress.setMax(Math.max(0, mPageLoader.getAllPagePos() - 1)); + chapterChange = false; + }, view -> {//目录 + /*initChapterTitleList(); + mReadActivity.getDlReadActivity().openDrawer(GravityCompat.START);*/ + mSettingDialog.dismiss(); + Intent intent = new Intent(mReadActivity, CatalogActivity.class); + intent.putExtra(APPCONST.BOOK, mBook); + mReadActivity.startActivityForResult(intent, APPCONST.REQUEST_CHAPTER_PAGE); + }, (dialog, view, isDayStyle) -> {//日夜切换 + changeNightAndDaySetting(isDayStyle); + }, view -> {//设置 + showSettingDetailView(); + }, new SeekBar.OnSeekBarChangeListener() {//阅读进度 + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + //进行切换 + int pagePos = seekBar.getProgress(); + if (pagePos != mPageLoader.getPagePos() && pagePos < mPageLoader.getAllPagePos()) { + mPageLoader.skipToPage(pagePos); + } + } + } + , null, (dialog, view, tvDownloadProgress) -> { + if (!isCollected) { + addBookToCaseAndDownload(tvDownloadProgress); + } else { + downloadBook(tvDownloadProgress); + } + + }); + } + + /** + * 显示设置视图 + */ + private void showSettingView() { + if (mSettingDialog != null && !chapterChange) { + mSettingDialog.show(); + } else { + if (mPageLoader.getPageStatus() == PageLoader.STATUS_FINISH) { + createSettingView(); + chapterChange = false; + } + mSettingDialog.show(); + } + + } + + /** + * 添加到书架并缓存书籍 + * + * @param tvDownloadProgress + */ + private void addBookToCaseAndDownload(final TextView tvDownloadProgress) { + DialogCreator.createCommonDialog(mReadActivity, mReadActivity.getString(R.string.tip), mReadActivity.getString(R.string.download_no_add_tips), true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + downloadBook(tvDownloadProgress); + isCollected = true; + } + }, (dialog, which) -> dialog.dismiss()); + } + + /** + * 创建详细设置视图 + */ + private void createSettingDetailView() { + mSettingDetailDialog = DialogCreator.createReadDetailSetting(mReadActivity, mSetting, + this::changeStyle, v -> reduceTextSize(), v -> increaseTextSize(), v -> { + if (mSetting.isVolumeTurnPage()) { + mSetting.setVolumeTurnPage(false); + TextHelper.showText("音量键翻页已关闭!"); + } else { + mSetting.setVolumeTurnPage(true); + TextHelper.showText("音量键翻页已开启!"); + } + SysManager.saveSetting(mSetting); + }, v -> { + Intent intent = new Intent(mReadActivity, FontsActivity.class); + mReadActivity.startActivityForResult(intent, APPCONST.REQUEST_FONT); + }, this::showPageModeDialog, v -> { + if (mSetting.getPageMode() == PageMode.SCROLL){ + TextHelper.showText("滚动暂时不支持自动翻页"); + return; + } + mSettingDetailDialog.dismiss(); + autoPage = !autoPage; + autoPage(); + }); + } + + /** + * 显示详细设置视图 + */ + private void showSettingDetailView() { + mSettingDialog.dismiss(); + if (mSettingDetailDialog != null) { + mSettingDetailDialog.show(); + } else { + createSettingDetailView(); + mSettingDetailDialog.show(); + } + } + + private void showPageModeDialog(final TextView tvPageMode) { + if (mPageModeDialog != null) { + mPageModeDialog.show(); + } 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 = new AlertDialog.Builder(mReadActivity) + .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(); + } + } + + /** + * 字体结果回调 + * + * @param requestCode + * @param resultCode + * @param data + */ + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK) { + switch (requestCode) { + case APPCONST.REQUEST_FONT: + Font font = (Font) data.getSerializableExtra(APPCONST.FONT); + mSetting.setFont(font); + settingChange = true; +// init(); + MyApplication.runOnUiThread(() -> mPageLoader.setFont(font)); + break; + case APPCONST.REQUEST_CHAPTER_PAGE: + int[] chapterAndPage = data.getIntArrayExtra(APPCONST.CHAPTER_PAGE); + assert chapterAndPage != null; + skipToChapterAndPage(chapterAndPage[0], chapterAndPage[1]); + break; + } + } + } + + + /** + * 初始化 + */ + private void init() { + screenOffTimerStart(); + mPageLoader.init(); + mPageLoader.refreshChapterList(); + mHandler.sendMessage(mHandler.obtainMessage(8)); + } + + /** + * 初始化监听器 + */ + private void initListener(){ + + mReadActivity.getSrlContent().setTouchListener(new PageView.TouchListener() { + @Override + public boolean onTouch() { + screenOffTimerStart(); + return true; + } + + @Override + public void center() { + if (mPageLoader.getPageStatus() == PageLoader.STATUS_FINISH) { + showSettingView(); + } + if (autoPage){ + autoPageStop(); + } + } + + @Override + public void prePage() { + isPrev = true; + } + + @Override + public void nextPage(boolean hasNextPage) { + isPrev = false; + if (!hasNextPage && endPageTipCount == 3) { + mReadActivity.getTvEndPageTip().setVisibility(View.VISIBLE); + mHandler.sendMessage(mHandler.obtainMessage(5)); + if (autoPage){ + autoPageStop(); + } + } + } + + @Override + public void cancel() { + } + }); + + mPageLoader.setOnPageChangeListener( + new PageLoader.OnPageChangeListener() { + @Override + public void onChapterChange(int pos) { + chapterChange = true; + lastLoad(pos); + for (int i = 0; i < 5; i++) { + preLoad(pos - 1 + i); + } + mBook.setHistoryChapterId(mChapters.get(pos).getTitle()); + MyApplication.getApplication().newThread(() -> { + if (mPageLoader.getPageStatus() == PageLoader.STATUS_LOADING) { + if (!NetworkUtils.isNetWorkAvailable()) { + mHandler.sendMessage(mHandler.obtainMessage(7)); + } else { + mHandler.sendMessage(mHandler.obtainMessage(6)); + } + } + }); + + } + + @Override + public void requestChapters(List requestChapters) { + /*for (final Chapter chapter : requestChapters){ + getChapterContent(chapter, null); + }*/ + } + + @Override + public void onCategoryFinish(List chapters) { + } + + @Override + public void onPageCountChange(int count) { + + } + + @Override + public void onPageChange(int pos) { + mHandler.sendMessage(mHandler.obtainMessage(4)); + } + + @Override + public void preLoading() { + + } + } + ); + + } + + + + + /** + * 章节数据网络同步 + */ + private void getData() { + mChapters = (ArrayList) mChapterService.findBookAllChapterByBookId(mBook.getId()); + if (!isCollected || mChapters.size() == 0 || ("本地书籍".equals(mBook.getType()) && + !ChapterService.isChapterCached(mBook.getId(), mChapters.get(0).getTitle()))) { + if ("本地书籍".equals(mBook.getType())) { + if (!new File(mBook.getChapterUrl()).exists()){ + TextHelper.showText("书籍缓存为空且源文件不存在,书籍加载失败!"); + mReadActivity.finish(); + return; + } + ((LocalPageLoader) mPageLoader).loadChapters(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + ArrayList chapters = (ArrayList) o; + mBook.setChapterTotalNum(chapters.size()); + mBook.setNewestChapterTitle(chapters.get(chapters.size() - 1).getTitle()); + mBookService.updateEntity(mBook); + if (mChapters.size() == 0) { + updateAllOldChapterData(chapters); + } + initChapters(); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + + @Override + public void onError(Exception e) { + mChapters.clear(); + initChapters(); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + } else { + CommonApi.getBookChapters(mBook.getChapterUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + ArrayList chapters = (ArrayList) o; + updateAllOldChapterData(chapters); + initChapters(); + } + + @Override + public void onError(Exception e) { +// settingChange = true; + initChapters(); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + } + } else { + initChapters(); + } + } + + /** + * 初始化章节 + */ + private void initChapters() { + mBook.setChapterTotalNum(mChapters.size()); + if (!StringHelper.isEmpty(mBook.getId())) { + mBookService.updateEntity(mBook); + } + if (mChapters.size() == 0) { + TextHelper.showLongText("该书查询不到任何章节"); + mHandler.sendMessage(mHandler.obtainMessage(8)); + settingChange = false; + } else { + if (mBook.getHisttoryChapterNum() < 0) { + mBook.setHisttoryChapterNum(0); + } else if (mBook.getHisttoryChapterNum() >= mChapters.size()) { + mBook.setHisttoryChapterNum(mChapters.size() - 1); + } + if ("本地书籍".equals(mBook.getType())) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + return; + } + getChapterContent(mChapters.get(mBook.getHisttoryChapterNum()), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { +// mChapters.get(mBook.getHisttoryChapterNum()).setContent((String) o); + mChapterService.saveOrUpdateChapter(mChapters.get(mBook.getHisttoryChapterNum()), (String) o); + mHandler.sendMessage(mHandler.obtainMessage(1)); +// getAllChapterData(); + } + + @Override + public void onError(Exception e) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + } + } + + /** + * 更新所有章节 + * + * @param newChapters + */ + private void updateAllOldChapterData(ArrayList newChapters) { + for (Chapter newChapter : newChapters) { + newChapter.setId(StringHelper.getStringRandom(25)); + newChapter.setBookId(mBook.getId()); + mChapters.add(newChapter); +// mChapterService.addChapter(newChapters.get(j)); + } + mChapterService.addChapters(mChapters); + } + + + /****************************************** 缓存章节***************************************************************/ + private int selectedIndex;//对话框选择下标 + + protected void downloadBook(final TextView tvDownloadProgress) { + if ("本地书籍".equals(mBook.getType())) { + TextHelper.showText("《" + mBook.getName() + "》是本地书籍,不能缓存"); + return; + } + if (!NetworkUtils.isNetWorkAvailable()) { + TextHelper.showText("无网络连接!"); + return; + } + new AlertDialog.Builder(mReadActivity) + .setTitle("缓存书籍") + .setSingleChoiceItems(APPCONST.DIALOG_DOWNLOAD, selectedIndex, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + selectedIndex = which; + } + }).setNegativeButton("取消", (new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + })).setPositiveButton("确定", + (dialog, which) -> { + switch (selectedIndex) { + case 0: + addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mPageLoader.getChapterPos() + 50); + break; + case 1: + addDownload(tvDownloadProgress, mPageLoader.getChapterPos() - 50, mPageLoader.getChapterPos() + 50); + break; + case 2: + addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mChapters.size()); + break; + case 3: + addDownload(tvDownloadProgress, 0, mChapters.size()); + break; + } + }).show(); + } + + private void addDownload(final TextView tvDownloadProgress, int begin, int end) { + //计算断点章节 + final int finalBegin = Math.max(0, begin); + final int finalEnd = Math.min(end, mChapters.size()); + needCacheChapterNum = finalEnd - finalBegin; + curCacheChapterNum = 0; + MyApplication.getApplication().newThread(() -> { + for (int i = finalBegin; i < finalEnd; i++) { + final Chapter chapter = mChapters.get(i); + if (StringHelper.isEmpty(chapter.getContent())) { + getChapterContent(chapter, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { +// chapter.setContent((String) o); + 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)); + } + }); + } else { + curCacheChapterNum++; + mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress)); + } + } + if (curCacheChapterNum == needCacheChapterNum) { + TextHelper.showText("《" + mBook.getName() + "》" + mReadActivity.getString(R.string.download_already_all_tips)); + } + }); + } + + + private void updateDownloadProgress(TextView tvDownloadProgress) { + try { + tvDownloadProgress.setText(curCacheChapterNum * 100 / needCacheChapterNum + " %"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 获取章节内容 + * + * @param chapter + * @param resultCallback + */ + private void getChapterContent(final Chapter chapter, ResultCallback resultCallback) { + if (StringHelper.isEmpty(chapter.getBookId())) { + chapter.setId(mBook.getId()); + } + if (!StringHelper.isEmpty(chapter.getContent())) { + if (resultCallback != null) { + resultCallback.onFinish(mChapterService.getChapterCatheContent(chapter), 0); + } + } else { + if ("本地书籍".equals(mBook.getType())) { + return; + } + if (resultCallback != null) { + CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, resultCallback); + } else { + CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(final Object o, int code) { +// chapter.setContent((String) o); + mChapterService.saveOrUpdateChapter(chapter, (String) o); + } + + @Override + public void onError(Exception e) { + + } + + }); + } + } + } + + + /** + * 白天夜间改变 + * + * @param isCurDayStyle + */ + private void changeNightAndDaySetting(boolean isCurDayStyle) { + mSetting.setDayStyle(!isCurDayStyle); + SysManager.saveSetting(mSetting); + settingChange = true; + mPageLoader.setPageStyle(!isCurDayStyle); + } + + /** + * 缩小字体 + */ + private void reduceTextSize() { + if (mSetting.getReadWordSize() > 1) { + mSetting.setReadWordSize(mSetting.getReadWordSize() - 1); + SysManager.saveSetting(mSetting); + settingChange = true; + mPageLoader.setTextSize((int) mSetting.getReadWordSize()); + } + } + + /** + * 增大字体 + */ + private void increaseTextSize() { + if (mSetting.getReadWordSize() < 41) { + mSetting.setReadWordSize(mSetting.getReadWordSize() + 1); + SysManager.saveSetting(mSetting); + settingChange = true; + mPageLoader.setTextSize((int) mSetting.getReadWordSize()); + } + } + + /** + * 改变阅读风格 + * + * @param readStyle + */ + private void changeStyle(ReadStyle readStyle) { + settingChange = true; + if (!mSetting.isDayStyle()) { + mSetting.setDayStyle(true); + } + mSetting.setReadStyle(readStyle); + switch (readStyle) { + case common: + mSetting.setReadBgColor(R.color.sys_common_bg); + mSetting.setReadWordColor(R.color.sys_common_word); + break; + case leather: + mSetting.setReadBgColor(R.color.sys_leather_bg); + mSetting.setReadWordColor(R.color.sys_leather_word); + break; + case protectedEye: + mSetting.setReadBgColor(R.color.sys_protect_eye_bg); + mSetting.setReadWordColor(R.color.sys_protect_eye_word); + break; + case breen: + mSetting.setReadBgColor(R.color.sys_breen_bg); + mSetting.setReadWordColor(R.color.sys_breen_word); + break; + case blueDeep: + mSetting.setReadBgColor(R.color.sys_blue_deep_bg); + mSetting.setReadWordColor(R.color.sys_blue_deep_word); + break; + } + SysManager.saveSetting(mSetting); + MyApplication.runOnUiThread(() -> mPageLoader.setPageStyle(true)); + } + + + /** + * 取消亮屏保持 + */ + private void unKeepScreenOn() { + keepScreenOn(false); + } + + /** + * @param keepScreenOn 是否保持亮屏 + */ + public void keepScreenOn(boolean keepScreenOn) { + if (keepScreenOn) { + mReadActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + mReadActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + /** + * 重置黑屏时间 + */ + private void screenOffTimerStart() { + if (screenTimeOut < 0) { + keepScreenOn(true); + return; + } + int screenOffTime = screenTimeOut * 1000 - ScreenHelper.getScreenOffTime(mReadActivity); + if (screenOffTime > 0) { + mHandler.removeCallbacks(keepScreenRunnable); + keepScreenOn(true); + mHandler.postDelayed(keepScreenRunnable, screenOffTime); + } else { + keepScreenOn(false); + } + } + + /** + * 预加载下一章 + */ + private void preLoad(int position) { + if (position + 1 < mChapters.size()) { + Chapter chapter = mChapters.get(position + 1); + if (StringHelper.isEmpty(chapter.getContent())) { + mPageLoader.getChapterContent(chapter); + } + } + } + + /** + * 预加载上一章 + * + * @param position + */ + private void lastLoad(int position) { + if (position > 0) { + Chapter chapter = mChapters.get(position - 1); + if (StringHelper.isEmpty(chapter.getContent())) { + mPageLoader.getChapterContent(chapter); + } + } + } + + /** + * 添加本地书籍 + * @param path + */ + private void addLocalBook(String path) { + File file = new File(path); + if (!file.exists()) { + return; + } + Book book = new Book(); + book.setName(file.getName().replace(".txt", "")); + book.setChapterUrl(path); + book.setType("本地书籍"); + book.setHistoryChapterId("未开始阅读"); + book.setNewestChapterTitle("未拆分章节"); + book.setAuthor("本地书籍"); + book.setSource(BookSource.local.toString()); + book.setDesc(""); + + //判断书籍是否已经添加 + Book existsBook = mBookService.findBookByAuthorAndName(book.getName(), book.getAuthor()); + if (book.equals(existsBook)){ + mBook = existsBook; + return; + } + + mBookService.addBook(book); + mBook = book; + } + + /** + * 跳转到指定章节的指定页面 + * @param chapterPos + * @param pagePos + */ + private void skipToChapterAndPage(final int chapterPos, final int pagePos){ + isPrev = false; + if (StringHelper.isEmpty(mChapters.get(chapterPos).getContent())) { + if ("本地书籍".equals(mBook.getType())) { + TextHelper.showText("该章节无内容!"); + return; + } + mReadActivity.getPbLoading().setVisibility(View.VISIBLE); + CommonApi.getChapterContent(mChapters.get(chapterPos).getUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { +// mChapters.get(position).setContent((String) o); + mChapterService.saveOrUpdateChapter(mChapters.get(chapterPos), (String) o); + mHandler.sendMessage(mHandler.obtainMessage(2, chapterPos, pagePos)); + } + + @Override + public void onError(Exception e) { + mHandler.sendMessage(mHandler.obtainMessage(2, chapterPos, pagePos)); + mPageLoader.chapterError(); + } + }); + } else { + mHandler.sendMessage(mHandler.obtainMessage(2, chapterPos, pagePos)); + } + } + + /** + * 自动翻页 + */ + private void autoPage() { + mHandler.removeCallbacks(upHpbNextPage); + mHandler.removeCallbacks(autoPageRunnable); + if (autoPage) { + mReadActivity.getPbNextPage().setVisibility(View.VISIBLE); + //每页按字数计算一次时间 + nextPageTime = mPageLoader.curPageLength() * 60 * 1000 / mSetting.getAutoScrollSpeed(); + if (0 == nextPageTime) nextPageTime = 1000; + mReadActivity.getPbNextPage().setMax(nextPageTime); + mHandler.postDelayed(autoPageRunnable, nextPageTime); + nextPageTime = nextPageTime - upHpbInterval * 10; + mHandler.postDelayed(upHpbNextPage, upHpbInterval); + } else { + mReadActivity.getPbNextPage().setVisibility(View.INVISIBLE); + } + } + + /** + * 更新自动翻页进度条 + */ + private void upHpbNextPage() { + nextPageTime = nextPageTime - upHpbInterval; + if (nextPageTime >= 0) { + mReadActivity.getPbNextPage().setProgress(nextPageTime); + } + mHandler.postDelayed(upHpbNextPage, upHpbInterval); + } + + /** + * 停止自动翻页 + */ + private void autoPageStop() { + autoPage = false; + autoPage(); + } + + /** + * 下一页 + */ + private void nextPage() { + MyApplication.runOnUiThread(() -> { + screenOffTimerStart(); + if (mPageLoader != null) { + mPageLoader.skipToNextPage(); + } + autoPage(); + }); + } + + + /** + * setter和getter方法 + */ + public boolean isCollected() { + return isCollected; + } + + public PageLoader getmPageLoader() { + return mPageLoader; + } + + public void setCollected(boolean collected) { + isCollected = collected; + } + + /** + * ReadActivity调用 + */ + protected void deleteBook() { + mBookService.deleteBookById(mBook.getId()); + } + + /** + * ReadActivity调用 + */ + public void onDestroy() { + mReadActivity.unregisterReceiver(mReceiver); + for (int i = 0; i < 8; i++) { + mHandler.removeMessages(i + 1); + } + mPageLoader.closeBook(); + mPageLoader = null; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivity.java new file mode 100644 index 0000000..a6509dc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivity.java @@ -0,0 +1,77 @@ +package xyz.fycz.myreader.ui.read.catalog; + +import android.os.Bundle; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import androidx.viewpager.widget.ViewPager; +import butterknife.BindView; +import butterknife.ButterKnife; +import com.google.android.material.tabs.TabLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; + +/** + * @author fengyue + * @date 2020/7/22 8:04 + */ +public class CatalogActivity extends BaseActivity { + @BindView(R.id.tl_tab_menu) + TabLayout tlTabMenu; + @BindView(R.id.iv_search) + ImageView ivSearch; + @BindView(R.id.rl_common_title) + RelativeLayout rlCommonTitle; + @BindView(R.id.vp_content) + ViewPager vpContent; + @BindView(R.id.iv_back) + ImageView tvBack; + + @BindView(R.id.et_search) + EditText etSearch; + @BindView(R.id.iv_cancel) + ImageView ivCancel; + + + private CatalogActivityPresenter mCatalogPresenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_catalog); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mCatalogPresenter = new CatalogActivityPresenter(this); + mCatalogPresenter.start(); + } + + public TabLayout getTlTabMenu() { + return tlTabMenu; + } + + public ImageView getIvSearch() { + return ivSearch; + } + + public RelativeLayout getRlCommonTitle() { + return rlCommonTitle; + } + + public ViewPager getVpContent() { + return vpContent; + } + + public ImageView getTvBack() { + return tvBack; + } + + + public EditText getEtSearch() { + return etSearch; + } + + public ImageView getIvCancel() { + return ivCancel; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivityPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivityPresenter.java new file mode 100644 index 0000000..d38734b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogActivityPresenter.java @@ -0,0 +1,133 @@ +package xyz.fycz.myreader.ui.read.catalog; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentPagerAdapter; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.ui.read.catalog.bookmark.BookMarkFragment; + +import java.util.ArrayList; + +import static androidx.fragment.app.FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT; + +/** + * @author fengyue + * @date 2020/7/22 8:10 + */ +public class CatalogActivityPresenter implements BasePresenter { + + private CatalogActivity mCatalogActivity; + private ArrayList mFragments = new ArrayList<>(); + private String[] tabTitle = {"目录", "书签"}; + private Setting mSetting; + private boolean isDayStyle; + private FragmentPagerAdapter tabAdapter; + + public CatalogActivityPresenter(CatalogActivity mCatalogActivity) { + this.mCatalogActivity = mCatalogActivity; + } + + @Override + public void start() { + Setting setting = SysManager.getSetting(); + isDayStyle = setting.isDayStyle(); + //设置日夜间 + if (!isDayStyle){ + mCatalogActivity.setStatusBar(R.color.sys_dialog_setting_bg); + mCatalogActivity.getRlCommonTitle().setBackground(mCatalogActivity.getDrawable(R.color.sys_dialog_setting_bg)); + mCatalogActivity.getTvBack().setImageDrawable(mCatalogActivity.getDrawable(R.mipmap.larrow3)); + mCatalogActivity.getIvCancel().setImageDrawable(mCatalogActivity.getDrawable(R.mipmap.ic_cha_black)); + mCatalogActivity.getIvSearch().setImageDrawable(mCatalogActivity.getDrawable(R.mipmap.b9)); + mCatalogActivity.getEtSearch().setTextColor(mCatalogActivity.getResources().getColor(R.color.sys_night_word)); + mCatalogActivity.getEtSearch().setHintTextColor(mCatalogActivity.getResources().getColor(R.color.sys_night_word)); + }else { + mCatalogActivity.getEtSearch().setTextColor(mCatalogActivity.getResources().getColor(setting.getReadWordColor())); + } + init(); + + InputMethodManager manager = ((InputMethodManager) mCatalogActivity.getSystemService(Context.INPUT_METHOD_SERVICE)); + + mCatalogActivity.getTvBack().setOnClickListener(v -> mCatalogActivity.finish()); + mCatalogActivity.getIvSearch().setOnClickListener(v -> { + mCatalogActivity.getTlTabMenu().setVisibility(View.GONE); + mCatalogActivity.getIvSearch().setVisibility(View.GONE); + mCatalogActivity.getEtSearch().setVisibility(View.VISIBLE); + mCatalogActivity.getIvCancel().setVisibility(View.VISIBLE); + mCatalogActivity.getEtSearch().setFocusable(true); + mCatalogActivity.getEtSearch().setFocusableInTouchMode(true); + mCatalogActivity.getEtSearch().requestFocus(); + }); + + mCatalogActivity.getEtSearch().setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus){ + if (manager != null) manager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); + }else { + if (manager != null) manager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + } + }); + + mCatalogActivity.getIvCancel().setOnClickListener(v -> { + mCatalogActivity.getTlTabMenu().setVisibility(View.VISIBLE); + mCatalogActivity.getIvSearch().setVisibility(View.VISIBLE); + mCatalogActivity.getEtSearch().setVisibility(View.GONE); + mCatalogActivity.getIvCancel().setVisibility(View.GONE); + mCatalogActivity.getEtSearch().setText(""); + }); + + mCatalogActivity.getEtSearch().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) { + switch (mCatalogActivity.getVpContent().getCurrentItem()){ + case 0: + ((CatalogFragment) tabAdapter.getItem(0)).getmCatalogPresent().startSearch(s.toString()); + break; + case 1: + ((BookMarkFragment) tabAdapter.getItem(1)).getmBookMarkPresenter().startSearch(s.toString()); + break; + } + } + }); + } + + private void init(){ + mFragments.add(new CatalogFragment()); + mFragments.add(new BookMarkFragment()); + tabAdapter = new FragmentPagerAdapter(mCatalogActivity.getSupportFragmentManager(),BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + @Override + public Fragment getItem(int position) { + return mFragments.get(position); + } + + @Override + public int getCount() { + return mFragments.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return tabTitle[position]; + } + + }; + mCatalogActivity.getVpContent().setAdapter(tabAdapter); + mCatalogActivity.getTlTabMenu().setupWithViewPager(mCatalogActivity.getVpContent()); + mCatalogActivity.getVpContent().setCurrentItem(0); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogFragment.java new file mode 100644 index 0000000..03cf10d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogFragment.java @@ -0,0 +1,67 @@ +package xyz.fycz.myreader.ui.read.catalog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.RelativeLayout; +import androidx.fragment.app.Fragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import xyz.fycz.myreader.R; + +/** + * A simple {@link Fragment} subclass. + */ +public class CatalogFragment extends Fragment { + @BindView(R.id.rl_catalog) + RelativeLayout rlCatalog; + @BindView(R.id.lv_chapter_list) + ListView lvChapterList; + @BindView(R.id.change_sort) + FloatingActionButton fcChangeSort; + + Unbinder unbinder; + + private CatalogPresenter mCatalogPresent; + + public CatalogFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_catalog, container, false); + unbinder = ButterKnife.bind(this, view); + mCatalogPresent = new CatalogPresenter(this); + mCatalogPresent.start(); + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + public RelativeLayout getRlCatalog() { + return rlCatalog; + } + + public ListView getLvChapterList() { + return lvChapterList; + } + + public FloatingActionButton getFcChangeSort() { + return fcChangeSort; + } + + public CatalogPresenter getmCatalogPresent() { + return mCatalogPresent; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogPresenter.java new file mode 100644 index 0000000..a5fc8ef --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/CatalogPresenter.java @@ -0,0 +1,118 @@ +package xyz.fycz.myreader.ui.read.catalog; + +import android.app.Activity; +import android.content.Intent; +import android.view.View; +import android.widget.AdapterView; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BasePresenter; +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 java.util.ArrayList; +import java.util.Collections; + +/** + * @author fengyue + * @date 2020/7/22 9:14 + */ +public class CatalogPresenter implements BasePresenter { + 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; + + public CatalogPresenter(CatalogFragment mCatalogFragment) { + this.mCatalogFragment = mCatalogFragment; + mChapterService = new ChapterService(); + } + + @Override + public void start() { + mBook = (Book) mCatalogFragment.getActivity().getIntent().getSerializableExtra(APPCONST.BOOK); + isDayStyle = SysManager.getSetting().isDayStyle(); + if (!isDayStyle) { + mCatalogFragment.getLvChapterList().setBackground(mCatalogFragment.getActivity().getDrawable(R.color.sys_dialog_setting_bg)); + } + mCatalogFragment.getFcChangeSort().setOnClickListener(view -> { + if (curSortflag == 0) {//当前正序 + curSortflag = 1; + changeChapterSort(); + } else {//当前倒序 + curSortflag = 0; + changeChapterSort(); + } + }); + initChapterTitleList(); + mCatalogFragment.getLvChapterList().setOnItemClickListener((adapterView, view, i, l) -> { + Chapter chapter = mChapterTitleAdapter.getItem(i); + final int position; + assert chapter != null; + if (chapter.getNumber() == 0) { + if (curSortflag == 0) { + position = i; + } else { + position = mChapters.size() - 1 - i; + } + } else { + position = chapter.getNumber(); + } + Intent intent = new Intent(); + intent.putExtra(APPCONST.CHAPTER_PAGE, new int[]{position, 0}); + mCatalogFragment.getActivity().setResult(Activity.RESULT_OK, intent); + mCatalogFragment.getActivity().finish(); + }); + } + + + /** + * 初始化章节目录 + */ + private void initChapterTitleList() { + mChapters = (ArrayList) mChapterService.findBookAllChapterByBookId(mBook.getId()); + //初始化倒序章节 + mConvertChapters.addAll(mChapters); + Collections.reverse(mConvertChapters); + //设置布局管理器 + int curChapterPosition; + curChapterPosition = mBook.getHisttoryChapterNum(); + mChapterTitleAdapter = new ChapterTitleAdapter(mCatalogFragment.getContext(), R.layout.listview_chapter_title_item, mChapters, mBook); + mCatalogFragment.getLvChapterList().setAdapter(mChapterTitleAdapter); + mCatalogFragment.getLvChapterList().setSelection(curChapterPosition); + } + + /** + * 改变章节列表排序(正倒序) + */ + private void changeChapterSort() { + if (curSortflag == 0) { + mChapterTitleAdapter.clear(); + mChapterTitleAdapter.addAll(mChapterTitleAdapter.getmList()); + } else { + mChapterTitleAdapter.clear(); + mConvertChapters.clear(); + mConvertChapters.addAll(mChapterTitleAdapter.getmList()); + Collections.reverse(mConvertChapters); + mChapterTitleAdapter.addAll(mConvertChapters); + } + mChapterTitleAdapter.notifyDataSetChanged(); + mCatalogFragment.getLvChapterList().setAdapter(mChapterTitleAdapter); + } + + /** + * 搜索过滤 + * + * @param query + */ + public void startSearch(String query) { + mChapterTitleAdapter.getFilter().filter(query); + mCatalogFragment.getLvChapterList().setSelection(0); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/ChapterTitleAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/ChapterTitleAdapter.java new file mode 100644 index 0000000..7664568 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/ChapterTitleAdapter.java @@ -0,0 +1,139 @@ +package xyz.fycz.myreader.ui.read.catalog; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Filter; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class ChapterTitleAdapter extends ArrayAdapter { + + private int mResourceId; + private Setting setting; + private int curChapterPosition = -1; + private List mChapters; + private List mList; + private Book mBook; + + public ChapterTitleAdapter(Context context, int resourceId, ArrayList datas, Book book) { + super(context, resourceId, new ArrayList<>(datas)); + mResourceId = resourceId; + setting = SysManager.getSetting(); + mChapters = datas; + mList = datas; + mBook = book; + } + + @Override + public void notifyDataSetChanged() { + setting = SysManager.getSetting(); + super.notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null); + viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_chapter_title); + viewHolder.vLine = (View) convertView.findViewById(R.id.v_line); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position, viewHolder); + return convertView; + } + + private void initView(int postion, final ViewHolder viewHolder) { + final Chapter chapter = getItem(postion); +// viewHolder.tvTitle.setText("【" + chapter.getTitle() + "】"); + viewHolder.tvTitle.setText(chapter.getTitle()); + if (ChapterService.isChapterCached(chapter.getBookId(), chapter.getTitle())) { + viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(getContext().getResources().getDrawable(R.drawable.selector_category_load), null, null, null); + } else { + viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(getContext().getResources().getDrawable(R.drawable.selector_category_unload), null, null, null); + } + if (setting.isDayStyle()) { + viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(setting.getReadWordColor())); + } else { + viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word)); + viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line)); + } + + if (chapter.getNumber() == mBook.getHisttoryChapterNum()) { + viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.colorAccent)); + } + + } + + /** + * 过滤器,实现搜索 + * + * @return + */ + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + String charString = constraint.toString(); + List mFilterList = new ArrayList<>(); + if (charString.isEmpty()) { + //没有过滤的内容,则使用源数据 + mFilterList = mChapters; + } else { + for (Chapter chapter : mChapters) { + //这里根据需求,添加匹配规则 + if (chapter.getTitle().contains(charString)) { + mFilterList.add(chapter); + } + } + } + + FilterResults filterResults = new FilterResults(); + filterResults.values = mFilterList; + return filterResults; + } + + //把过滤后的值返回出来 + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mList = (ArrayList) results.values; + clear(); + addAll(mList); + notifyDataSetChanged(); + } + }; + } + + + public List getmList() { + return mList; + } + + class ViewHolder { + + TextView tvTitle; + View vLine; + + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkAdapter.java new file mode 100644 index 0000000..b29931c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkAdapter.java @@ -0,0 +1,120 @@ +package xyz.fycz.myreader.ui.read.catalog.bookmark; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Filter; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.BookMark; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.ChapterService; + +import java.util.ArrayList; +import java.util.List; + + +public class BookMarkAdapter extends ArrayAdapter { + + private int mResourceId; + private Setting setting; + private List mBookMarks; + + public BookMarkAdapter(Context context, int resourceId, ArrayList datas){ + super(context, resourceId, datas); + mResourceId = resourceId; + setting = SysManager.getSetting(); + mBookMarks = new ArrayList<>(datas); + } + + @Override + public void notifyDataSetChanged() { + setting = SysManager.getSetting(); + super.notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null){ + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId,null); + viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_chapter_title); + viewHolder.vLine = (View) convertView.findViewById(R.id.v_line); + convertView.setTag(viewHolder); + }else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position,viewHolder); + return convertView; + } + + private void initView(int postion,final ViewHolder viewHolder){ + final BookMark bookMark = getItem(postion); +// viewHolder.tvTitle.setText("【" + chapter.getTitle() + "】"); + viewHolder.tvTitle.setText(bookMark.getTitle() + "[" + (bookMark.getBookMarkReadPosition() + 1) + "]"); + if (ChapterService.isChapterCached(bookMark.getBookId(), bookMark.getTitle())){ + viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(getContext().getResources().getDrawable(R.drawable.selector_category_load),null,null,null); + } else { + viewHolder.tvTitle.setCompoundDrawablesWithIntrinsicBounds(getContext().getResources().getDrawable(R.drawable.selector_category_unload),null,null,null); + } + if (setting.isDayStyle()) { + viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(setting.getReadWordColor())); + }else { + viewHolder.tvTitle.setTextColor(getContext().getResources().getColor(R.color.sys_night_word)); + viewHolder.vLine.setBackground(getContext().getDrawable(R.color.sys_dialog_setting_line)); + } + + } + /** + * 过滤器,实现搜索 + * + * @return + */ + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + String charString = constraint.toString(); + List mFilterList = new ArrayList<>(); + if (charString.isEmpty()) { + //没有过滤的内容,则使用源数据 + mFilterList = mBookMarks; + } else { + for (BookMark bookMark : mBookMarks) { + //这里根据需求,添加匹配规则 + if (bookMark.getTitle().contains(charString)) { + mFilterList.add(bookMark); + } + } + } + + FilterResults filterResults = new FilterResults(); + filterResults.values = mFilterList; + return filterResults; + } + + //把过滤后的值返回出来 + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + clear(); + addAll((ArrayList) results.values); + notifyDataSetChanged(); + } + }; + } + class ViewHolder{ + + TextView tvTitle; + View vLine; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkFragment.java new file mode 100644 index 0000000..b788366 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkFragment.java @@ -0,0 +1,54 @@ +package xyz.fycz.myreader.ui.read.catalog.bookmark; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.RelativeLayout; +import androidx.fragment.app.Fragment; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import xyz.fycz.myreader.R; + +/** + * A simple {@link Fragment} subclass. + */ +public class BookMarkFragment extends Fragment { + @BindView(R.id.lv_bookmark_list) + ListView lvBookmarkList; + + private BookMarkPresenter mBookMarkPresenter; + Unbinder unbinder; + + + public BookMarkFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_bookmark, container, false); + unbinder = ButterKnife.bind(this, view); + mBookMarkPresenter = new BookMarkPresenter(this); + mBookMarkPresenter.start(); + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + public ListView getLvBookmarkList() { + return lvBookmarkList; + } + + public BookMarkPresenter getmBookMarkPresenter() { + return mBookMarkPresenter; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkPresenter.java new file mode 100644 index 0000000..2ccb98d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/read/catalog/bookmark/BookMarkPresenter.java @@ -0,0 +1,74 @@ +package xyz.fycz.myreader.ui.read.catalog.bookmark; + +import android.app.Activity; +import android.content.Intent; +import androidx.core.view.GravityCompat; +import android.view.View; +import android.widget.AdapterView; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.BookMark; +import xyz.fycz.myreader.greendao.service.BookMarkService; + +import java.util.ArrayList; + +/** + * @author fengyue + * @date 2020/7/22 11:11 + */ +public class BookMarkPresenter implements BasePresenter { + private BookMarkFragment mBookMarkFragment; + private BookMarkService mBookMarkService; + private ArrayList mBookMarks = new ArrayList<>(); + private BookMarkAdapter mBookMarkAdapter; + private Book mBook; + + public BookMarkPresenter(BookMarkFragment mBookMarkFragment) { + this.mBookMarkFragment = mBookMarkFragment; + mBookMarkService = new BookMarkService(); + } + + @Override + public void start() { + mBook = (Book) mBookMarkFragment.getActivity().getIntent().getSerializableExtra(APPCONST.BOOK); + if (!SysManager.getSetting().isDayStyle()) { + mBookMarkFragment.getLvBookmarkList().setBackground(mBookMarkFragment.getActivity().getDrawable(R.color.sys_dialog_setting_bg)); + } + initBookMarkList(); + mBookMarkFragment.getLvBookmarkList().setOnItemClickListener((parent, view, position, id) -> { + BookMark bookMark = mBookMarks.get(position); + int chapterPos = bookMark.getBookMarkChapterNum(); + int pagePos = bookMark.getBookMarkReadPosition(); + Intent intent = new Intent(); + intent.putExtra(APPCONST.CHAPTER_PAGE, new int[]{chapterPos, pagePos}); + mBookMarkFragment.getActivity().setResult(Activity.RESULT_OK, intent); + mBookMarkFragment.getActivity().finish(); + }); + + mBookMarkFragment.getLvBookmarkList().setOnItemLongClickListener((parent, view, position, id) -> { + if (mBookMarks.get(position) != null) { + mBookMarkService.deleteBookMark(mBookMarks.get(position)); + initBookMarkList(); + } + return true; + }); + } + + private void initBookMarkList() { + mBookMarks = (ArrayList) mBookMarkService.findBookAllBookMarkByBookId(mBook.getId()); + mBookMarkAdapter = new BookMarkAdapter(mBookMarkFragment.getActivity(), R.layout.listview_chapter_title_item, mBookMarks); + mBookMarkFragment.getLvBookmarkList().setAdapter(mBookMarkAdapter); + } + + /** + * 搜索过滤 + * @param query + */ + public void startSearch(String query) { + mBookMarkAdapter.getFilter().filter(query); + mBookMarkFragment.getLvBookmarkList().setSelection(0); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookActivity.java new file mode 100644 index 0000000..b711c64 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookActivity.java @@ -0,0 +1,136 @@ +package xyz.fycz.myreader.ui.search; + + +import android.content.Context; +import android.os.Bundle; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; + +import com.scwang.smartrefresh.layout.SmartRefreshLayout; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; + +import butterknife.ButterKnife; +import butterknife.BindView; +import me.gujun.android.taggroup.TagGroup; + +public class SearchBookActivity extends BaseActivity { + + @BindView(R.id.ll_title_back) + LinearLayout llTitleBack; + @BindView(R.id.tv_title_text) + TextView tvTitleText; + @BindView(R.id.system_title) + LinearLayout systemTitle; + @BindView(R.id.et_search_key) + EditText etSearchKey; + @BindView(R.id.tv_search_conform) + TextView tvSearchConform; + + @BindView(R.id.ll_refresh_suggest_books) + LinearLayout llRefreshSuggestBooks; + @BindView(R.id.lv_search_books_list) + ListView lvSearchBooksList; + @BindView(R.id.ll_suggest_books_view) + LinearLayout llSuggestBooksView; + @BindView(R.id.pb_loading) + ProgressBar pbLoading; + @BindView(R.id.lv_history_list) + ListView lvHistoryList; + @BindView(R.id.ll_clear_history) + LinearLayout llClearHistory; + @BindView(R.id.ll_history_view) + LinearLayout llHistoryView; + @BindView(R.id.tg_suggest_book) + TagGroup tgSuggestBook; + @BindView(R.id.renew_image) + ImageView renewByImage; + @BindView(R.id.renew_text) + TextView renewByText; + @BindView(R.id.srl_search_book_list) + SmartRefreshLayout srlSearchBookList; + + + private SearchBookPrensenter mSearchBookPrensenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search_book); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mSearchBookPrensenter = new SearchBookPrensenter(this); + mSearchBookPrensenter.start(); + } + + @Override + public void onBackPressed() { + if (!mSearchBookPrensenter.onBackPressed()){ + super.onBackPressed(); + } + } + + public LinearLayout getLlTitleBack() { + return llTitleBack; + } + + public TextView getTvTitleText() { + return tvTitleText; + } + + public LinearLayout getSystemTitle() { + return systemTitle; + } + + public EditText getEtSearchKey() { + return etSearchKey; + } + + public TextView getTvSearchConform() { + return tvSearchConform; + } + + public TagGroup getTgSuggestBook() { + return tgSuggestBook; + } + + public LinearLayout getLlRefreshSuggestBooks() { + return llRefreshSuggestBooks; + } + + public ListView getLvSearchBooksList() { + return lvSearchBooksList; + } + + public LinearLayout getLlSuggestBooksView() { + return llSuggestBooksView; + } + + public ProgressBar getPbLoading() { + return pbLoading; + } + + public ListView getLvHistoryList() { + return lvHistoryList; + } + + public LinearLayout getLlClearHistory() { + return llClearHistory; + } + + public LinearLayout getLlHistoryView() { + return llHistoryView; + } + + public ImageView getRenewByImage() { + return renewByImage; + } + + public TextView getRenewByText() { + return renewByText; + } + + public SmartRefreshLayout getSrlSearchBookList() { + return srlSearchBookList; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookAdapter.java new file mode 100644 index 0000000..750fabe --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookAdapter.java @@ -0,0 +1,165 @@ +package xyz.fycz.myreader.ui.search; + +import android.content.Context; +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; +import androidx.annotation.Nullable; +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.callback.ResultCallback; +import xyz.fycz.myreader.crawler.BookInfoCrawler; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.crawler.ReadCrawlerUtil; +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.enums.BookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Created by zhao on 2017/7/26. + */ + +public class SearchBookAdapter extends ArrayAdapter { + + private int mResourceId; + + private ConcurrentMultiValueMap mBooks; + + private ArrayList mTempBooks = new ArrayList<>(); + + private Handler mHandle = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message message) { + + switch (message.what) { + case 1: + ViewHolder holder = (ViewHolder) message.obj; + int pos = message.arg1; + SearchBookAdapter.this.initOtherInfo(pos, holder); + break; + } + return false; + } + }); + + public SearchBookAdapter(Context context, int resourceId, ArrayList datas, + ConcurrentMultiValueMap mBooks){ + super(context, resourceId, datas); + mResourceId = resourceId; + this.mBooks = mBooks; + } + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null){ + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId,null); + viewHolder.ivBookImg = convertView.findViewById(R.id.iv_book_img); + viewHolder.tvBookName = convertView.findViewById(R.id.tv_book_name); + viewHolder.tvAuthor = convertView.findViewById(R.id.tv_book_author); + viewHolder.tvDesc = convertView.findViewById(R.id.tv_book_desc); + viewHolder.tvType = convertView.findViewById(R.id.tv_book_type); + viewHolder.tvSource = convertView.findViewById(R.id.tv_book_source); + viewHolder.tvNewestChapter = convertView.findViewById(R.id.tv_book_newest_chapter); + //viewHolder.tvNewestChapter = convertView.findViewById(R.id.tv_book_newest_chapter); + convertView.setTag(viewHolder); + }else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position,viewHolder); + return convertView; + } + + private void initView(final int position, final ViewHolder viewHolder){ + List aBooks = mBooks.getValues(getItem(position)); + int bookCount = aBooks.size(); + final Book book = aBooks.get(0); + SearchBookBean ssb = new SearchBookBean(book.getName(), book.getAuthor()); + + //判断是否已经加载,防止多次加载 + for (SearchBookBean temp : mTempBooks){//已加载 + if (ssb.equals(temp)){ + viewHolder.tvSource.setText("书源:" + BookSource.fromString(book.getSource()).text + + " 共" + bookCount + "个源"); + return; + } + } + + if (StringHelper.isEmpty(book.getImgUrl())){ + book.setImgUrl(""); + } + viewHolder.tvBookName.setText(book.getName()); + viewHolder.tvNewestChapter.setText("最新章节:" + book.getNewestChapterTitle()); + viewHolder.tvAuthor.setText(book.getAuthor()); + viewHolder.tvSource.setText("书源:" + BookSource.fromString(book.getSource()).text + + " 共" + bookCount + "个源"); + viewHolder.tvDesc.setText(""); + viewHolder.tvType.setText(""); + ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(book.getSource()); + if (rc instanceof BookInfoCrawler && StringHelper.isEmpty(book.getImgUrl())){ + BookInfoCrawler bic = (BookInfoCrawler) rc; + CommonApi.getBookInfo(book, bic, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + mHandle.sendMessage(mHandle.obtainMessage(1,position,0,viewHolder)); + } + + @Override + public void onError(Exception e) { + + } + }); + }else { + initOtherInfo(position, viewHolder); + } + //viewHolder.tvNewestChapter.setText(book.getNewestChapterTitle()); + //添加已经加载的书籍 + mTempBooks.add(ssb); + } + private void initOtherInfo(final int position, final ViewHolder holder){ + Book book = mBooks.getValue(getItem(position), 0); + //图片 + Glide.with(getContext()) + .load(book.getImgUrl()) +// .override(DipPxUtil.dip2px(getContext(), 80), DipPxUtil.dip2px(getContext(), 150)) + .error(R.mipmap.no_image) + .placeholder(R.mipmap.no_image) + //设置圆角 + .apply(RequestOptions.bitmapTransform(new RoundedCorners(8))) + .into(holder.ivBookImg); + //简介 + holder.tvDesc.setText("简介:" + book.getDesc()); + holder.tvType.setText(book.getType()); + } + + class ViewHolder{ + ImageView ivBookImg; + TextView tvBookName; + TextView tvDesc; + TextView tvAuthor; + TextView tvType; + TextView tvSource; + TextView tvNewestChapter; + //TextView tvNewestChapter; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookPrensenter.java b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookPrensenter.java new file mode 100644 index 0000000..4cb84ba --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookPrensenter.java @@ -0,0 +1,384 @@ +package xyz.fycz.myreader.ui.search; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; + +import android.widget.TextView; +import androidx.annotation.NonNull; +import com.scwang.smartrefresh.layout.api.RefreshLayout; +import com.scwang.smartrefresh.layout.listener.OnRefreshListener; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.crawler.*; +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap; +import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.SearchHistory; +import xyz.fycz.myreader.greendao.service.SearchHistoryService; +import xyz.fycz.myreader.util.StringHelper; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +import me.gujun.android.taggroup.TagGroup; + + +public class SearchBookPrensenter implements BasePresenter { + + private SearchBookActivity mSearchBookActivity; + private SearchBookAdapter mSearchBookAdapter; + private String searchKey;//搜索关键字 + private ArrayList mBooksBean = new ArrayList<>(); + private ConcurrentMultiValueMap mBooks = new ConcurrentMultiValueMap<>(); + private ArrayList mSearchHistories = new ArrayList<>(); + private ArrayList mSuggestions = new ArrayList<>(); + + private SearchHistoryService mSearchHistoryService; + + private SearchHistoryAdapter mSearchHistoryAdapter; + + + private int curThreadCount; + + private int allThreadCount; + + private int inputConfirm = 0;//搜索输入确认 + private int confirmTime = 1000;//搜索输入确认时间(毫秒) + + private static String[] suggestion = {"第一序列", "大道朝天", "伏天氏", "终极斗罗", "我师兄实在太稳健了", "烂柯棋缘", "诡秘之主"}; + private static String[] suggestion2 = {"不朽凡人", "圣墟", "我是至尊", "龙王传说", "太古神王", "一念永恒", "雪鹰领主", "大主宰"}; + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + search(); + break; + case 2: + if (curThreadCount == 0) { + initSearchList(); + mSearchBookActivity.getSrlSearchBookList().finishRefresh(); + mSearchBookActivity.getPbLoading().setVisibility(View.GONE); + }/*else { + notifyDataSetChanged(); + mSearchBookActivity.getSrlSearchBookList().finishRefresh(); + }*/ + break; + } + } + }; + + public SearchBookPrensenter(SearchBookActivity searchBookActivity) { + mSearchBookActivity = searchBookActivity; + mSearchHistoryService = new SearchHistoryService(); + for (int i = 0; i < suggestion.length; i++) { + mSuggestions.add(suggestion[i]); + } + } + + @Override + public void start() { + mSearchBookActivity.getTvTitleText().setText("搜索"); + mSearchBookActivity.etSearchKey.requestFocus();//get the focus + //enter事件 + mSearchBookActivity.getEtSearchKey().setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_UNSPECIFIED) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + return (keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER); + } + return false; + } + }); + //换一批点击事件 + mSearchBookActivity.getRenewByImage().setOnClickListener(new RenewSuggestionBook()); + //换一批点击事件 + mSearchBookActivity.getRenewByText().setOnClickListener(new RenewSuggestionBook()); + //返回 + mSearchBookActivity.getLlTitleBack().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSearchBookActivity.finish(); + } + }); + //搜索框改变事件 + mSearchBookActivity.getEtSearchKey().addTextChangedListener(new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(final Editable editable) { + searchKey = editable.toString(); + if (StringHelper.isEmpty(searchKey)) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + + } + + }); + //进入书籍详情页 + mSearchBookActivity.getLvSearchBooksList().setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent = new Intent(mSearchBookActivity, BookInfoActivity.class); + intent.putExtra(APPCONST.SEARCH_BOOK_BEAN, new ArrayList<>(mBooks.getValues(mBooksBean.get(i)))); + mSearchBookActivity.startActivity(intent); + } + }); + //搜索按钮点击事件 + mSearchBookActivity.getTvSearchConform().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + //suggestion搜索事件 + mSearchBookActivity.getTgSuggestBook().setOnTagClickListener(new TagGroup.OnTagClickListener() { + @Override + public void onTagClick(String tag) { + mSearchBookActivity.getEtSearchKey().setText(tag); + mSearchBookActivity.getEtSearchKey().setSelection(tag.length()); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + //历史记录搜索事件 + mSearchBookActivity.getLvHistoryList().setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mSearchBookActivity.getEtSearchKey().setText(mSearchHistories.get(position).getContent()); + mSearchBookActivity.getEtSearchKey().setSelection(mSearchHistories.get(position).getContent().length()); + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + //清空历史记录 + mSearchBookActivity.getLlClearHistory().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mSearchHistoryService.clearHistory(); + initHistoryList(); + } + }); + //清除单个历史记录 + mSearchBookActivity.getLvHistoryList().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (mSearchHistories.get(position) != null) { + mSearchHistoryService.deleteHistory(mSearchHistories.get(position)); + initHistoryList(); + } + return true; + } + }); + //上拉刷新 + mSearchBookActivity.getSrlSearchBookList().setOnRefreshListener(new OnRefreshListener() { + @Override + public void onRefresh(@NonNull RefreshLayout refreshLayout) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + mSearchBookActivity.getSrlSearchBookList().setEnableRefresh(false); + initSuggestionBook(); + initHistoryList(); + } + + + /** + * 初始化建议书目 + */ + private void initSuggestionBook() { + mSearchBookActivity.getTgSuggestBook().setTags(suggestion); + } + + private class RenewSuggestionBook implements View.OnClickListener { + @Override + public void onClick(View v) { + String[] s = mSearchBookActivity.getTgSuggestBook().getTags(); + if (Arrays.equals(s, suggestion)) { + mSearchBookActivity.getTgSuggestBook().setTags(suggestion2); + } else { + mSearchBookActivity.getTgSuggestBook().setTags(suggestion); + } + } + } + + /** + * 初始化历史列表 + */ + private void initHistoryList() { + mSearchHistories = mSearchHistoryService.findAllSearchHistory(); + if (mSearchHistories == null || mSearchHistories.size() == 0) { + mSearchBookActivity.getLlHistoryView().setVisibility(View.GONE); + } else { + mSearchHistoryAdapter = new SearchHistoryAdapter(mSearchBookActivity, R.layout.listview_search_history_item, mSearchHistories); + mSearchBookActivity.getLvHistoryList().setAdapter(mSearchHistoryAdapter); + mSearchBookActivity.getLlHistoryView().setVisibility(View.VISIBLE); + } + } + + /** + * 初始化搜索列表 + */ + private void initSearchList() { + initmBooksBean(); + mSearchBookAdapter = new SearchBookAdapter(mSearchBookActivity, + R.layout.listview_search_book_item, mBooksBean, mBooks); + mSearchBookActivity.getLvSearchBooksList().setAdapter(mSearchBookAdapter); + mSearchBookActivity.getLvSearchBooksList().setVisibility(View.VISIBLE); + mSearchBookActivity.getLlSuggestBooksView().setVisibility(View.GONE); + mSearchBookActivity.getLlHistoryView().setVisibility(View.GONE); + } + + /** + * 更新搜索列表 + */ + private void notifyDataSetChanged() { + if (curThreadCount == 0) { + mSearchBookActivity.getPbLoading().setVisibility(View.GONE); + } + initmBooksBean(); + } + + /** + * 初始化mBooksBean + */ + private void initmBooksBean() { + synchronized (this) { + mBooksBean.clear(); + mBooksBean.addAll(mBooks.keySet()); + //排序,基于最符合关键字的搜书结果应该是最短的 + //TODO ;这里只做了简单的比较排序,还需要继续完善 + Collections.sort(mBooksBean, new Comparator() { + @Override + public int compare(SearchBookBean o1, SearchBookBean o2) { + if (searchKey.equals(o1.getName())) + return -1; + if (searchKey.equals(o2.getName())) + return 1; + if (searchKey.equals(o1.getAuthor())) + return -1; + if (searchKey.equals(o2.getAuthor())) + return 1; + if (o1.getName().length() < o2.getName().length()) + return -1; + if (o1.getName().length() == o2.getName().length()) + return 0; + return 1; + } + }); + /*MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + mSearchBookAdapter.notifyDataSetChanged(); + } + });*/ + } + } + + /** + * 获取搜索数据 + */ + private void getData() { + mBooksBean.clear(); + mBooks.clear(); + allThreadCount = 4; + curThreadCount = allThreadCount; + searchBookByCrawler(new BiQuGe44ReadCrawler(), ""); + searchBookByCrawler(new TianLaiReadCrawler(), ""); + searchBookByCrawler(new BiQuGeReadCrawler(), "gbk"); + searchBookByCrawler(new PinShuReadCrawler(), "gbk"); + } + + /** + * 搜索 + */ + private void search() { + mSearchBookActivity.getPbLoading().setVisibility(View.VISIBLE); + if (StringHelper.isEmpty(searchKey)) { + mSearchBookActivity.getPbLoading().setVisibility(View.GONE); + mSearchBookActivity.getLvSearchBooksList().setVisibility(View.GONE); + mSearchBookActivity.getLlSuggestBooksView().setVisibility(View.VISIBLE); + initHistoryList(); + mSearchBookActivity.getLvSearchBooksList().setAdapter(null); + mSearchBookActivity.getSrlSearchBookList().setEnableRefresh(false); + } else { + mSearchBookActivity.getLvSearchBooksList().setVisibility(View.VISIBLE); + mSearchBookActivity.getLlSuggestBooksView().setVisibility(View.GONE); + mSearchBookActivity.getLlHistoryView().setVisibility(View.GONE); + getData(); + mSearchBookActivity.getSrlSearchBookList().setEnableRefresh(true); + mSearchHistoryService.addOrUpadteHistory(searchKey); + //收起软键盘 + InputMethodManager imm = (InputMethodManager) MyApplication.getmContext().getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(mSearchBookActivity.getEtSearchKey().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + public boolean onBackPressed() { + if (StringHelper.isEmpty(searchKey)) { + return false; + } else { + mSearchBookActivity.getEtSearchKey().setText(""); + return true; + } + } + + private void searchBookByCrawler(ReadCrawler rc, String charset) { + String searchKey = this.searchKey; + if (charset.toLowerCase().equals("gbk")) { + try { + searchKey = URLEncoder.encode(this.searchKey, charset); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + CommonApi.search(searchKey, rc, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + ConcurrentMultiValueMap sbb = + (ConcurrentMultiValueMap) o; + mBooks.addAll(sbb); + curThreadCount--; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + + @Override + public void onError(Exception e) { + curThreadCount--; + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + }); + } + +} + diff --git a/app/src/main/java/xyz/fycz/myreader/ui/search/SearchHistoryAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchHistoryAdapter.java new file mode 100644 index 0000000..1169cd5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/search/SearchHistoryAdapter.java @@ -0,0 +1,59 @@ +package xyz.fycz.myreader.ui.search; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.greendao.entity.SearchHistory; + +import java.util.ArrayList; + +/** + * Created by zhao on 2017/7/26. + */ + +public class SearchHistoryAdapter extends ArrayAdapter { + + private int mResourceId; + + public SearchHistoryAdapter(Context context, int resourceId, ArrayList datas){ + super(context,resourceId,datas); + mResourceId = resourceId; + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null){ + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId,null); + viewHolder.tvContent = (TextView)convertView.findViewById(R.id.tv_history_content); + convertView.setTag(viewHolder); + }else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position,viewHolder); + return convertView; + } + + private void initView(int postion,ViewHolder viewHolder){ + SearchHistory searchHistory = getItem(postion); + viewHolder.tvContent.setText(searchHistory.getContent()); + + } + + class ViewHolder{ + + TextView tvContent; + + + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/search/SuggestBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/search/SuggestBookAdapter.java new file mode 100644 index 0000000..392582c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/search/SuggestBookAdapter.java @@ -0,0 +1,56 @@ +package xyz.fycz.myreader.ui.search; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import xyz.fycz.myreader.R; + +import java.util.ArrayList; + +/** + * Created by zhao on 2017/7/26. + */ + +public class SuggestBookAdapter extends ArrayAdapter { + + private int mResourceId; + + public SuggestBookAdapter(Context context, int resourceId, ArrayList datas) { + super(context, resourceId, datas); + mResourceId = resourceId; + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + ViewHolder viewHolder = null; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(getContext()).inflate(mResourceId, null); + viewHolder.tvBookName = (TextView) convertView.findViewById(R.id.tv_suggestion_book); + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + initView(position, viewHolder); + return convertView; + } + + private void initView(int postion, ViewHolder viewHolder) { + String bookName = getItem(postion); + viewHolder.tvBookName.setText(bookName); + + } + + class ViewHolder { + + TextView tvBookName; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/user/LoginActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/user/LoginActivity.java new file mode 100644 index 0000000..3031ead --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/user/LoginActivity.java @@ -0,0 +1,144 @@ +package xyz.fycz.myreader.ui.user; + +/** + * @author fengyue + * @date 2020/4/26 18:49 + */ + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.util.utils.StringUtils; + + +@SuppressLint("Registered") +public class LoginActivity extends BaseActivity implements TextWatcher { + @BindView(R.id.ll_title_back) + LinearLayout llTitleBack; + @BindView(R.id.tv_title_text) + TextView tvTitleText; + @BindView(R.id.et_user) + EditText user; + @BindView(R.id.et_password) + EditText password; + @BindView(R.id.bt_login) + Button loginBtn; + @BindView(R.id.tv_register) + TextView tvRegister; + @BindView(R.id.et_captcha) + EditText etCaptcha; + @BindView(R.id.iv_captcha) + ImageView ivCaptcha; + private LoginPresenter mLoginPresenter; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mLoginPresenter = new LoginPresenter(this); + mLoginPresenter.start(); + } + + + /** + * 当有控件获得焦点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.getText().toString().length() > 3 && + password.getText().toString().length() > 5 && + etCaptcha.getText().toString().length() > 0){ + //按钮可以点击 + loginBtn.setEnabled(true); + }else{ + //按钮不能点击 + loginBtn.setEnabled(false); + } + } + + + public EditText getUser() { + return user; + } + + public EditText getPassword() { + return password; + } + + public Button getLoginBtn() { + return loginBtn; + } + + public TextView getTvTitleText() { + return tvTitleText; + } + + public LinearLayout getLlTitleBack() { + return llTitleBack; + } + + public TextView getTvRegister() { + return tvRegister; + } + + public EditText getEtCaptcha() { + return etCaptcha; + } + + public ImageView getIvCaptcha() { + return ivCaptcha; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/user/LoginPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/user/LoginPresenter.java new file mode 100644 index 0000000..6435f4e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/user/LoginPresenter.java @@ -0,0 +1,137 @@ +package xyz.fycz.myreader.ui.user; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import xyz.fycz.myreader.backup.UserService; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.ui.font.FontsActivity; +import xyz.fycz.myreader.util.CodeUtil; +import xyz.fycz.myreader.util.CyptoUtils; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.NetworkUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author fengyue + * @date 2020/4/26 20:31 + */ + +public class LoginPresenter implements BasePresenter { + private LoginActivity mLoginActivity; + private String code; + + public LoginPresenter(LoginActivity mLoginActivity) { + this.mLoginActivity = mLoginActivity; + } + + @SuppressLint("HandlerLeak") + private Handler mHandler = new Handler() { + @SuppressLint("HandlerLeak") + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 1: + mLoginActivity.getLoginBtn().setEnabled(true); + break; + case 2: + createCaptcha(); + break; + } + } + }; + + @Override + public void start() { + mHandler.sendMessage(mHandler.obtainMessage(2)); + mLoginActivity.getTvTitleText().setText("登录"); + mLoginActivity.getLlTitleBack().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mLoginActivity.finish(); + } + }); + String username = UserService.readUsername(); + mLoginActivity.getUser().setText(username); + mLoginActivity.getUser().requestFocus(username.length()); + //监听内容改变 -> 控制按钮的点击状态 + mLoginActivity.getUser().addTextChangedListener(mLoginActivity); + mLoginActivity.getPassword().addTextChangedListener(mLoginActivity); + mLoginActivity.getEtCaptcha().addTextChangedListener(mLoginActivity); + + mLoginActivity.getIvCaptcha().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mHandler.sendMessage(mHandler.obtainMessage(2)); + } + }); + + mLoginActivity.getLoginBtn().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mHandler.sendMessage(mHandler.obtainMessage(2)); + if (!code.toLowerCase().equals(mLoginActivity.getEtCaptcha().getText().toString().toLowerCase())){ + DialogCreator.createTipDialog(mLoginActivity, "验证码错误!"); + return; + } + if (!NetworkUtils.isNetWorkAvailable()) { + TextHelper.showText("无网络连接!"); + return; + } + mLoginActivity.getLoginBtn().setEnabled(false); + final String loginName = mLoginActivity.getUser().getText().toString().trim(); + String loginPwd = mLoginActivity.getPassword().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); + mLoginActivity.finish(); + } else { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + TextHelper.showText(resultName); + } + + @Override + public void onError(Exception e) { + TextHelper.showText("登录失败\n" + e.getLocalizedMessage()); + } + }); + + } + }); + + mLoginActivity.getTvRegister().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(mLoginActivity, RegisterActivity.class); + mLoginActivity.startActivity(intent); + } + }); + } + + public void createCaptcha() { + code = CodeUtil.getInstance().createCode(); + Bitmap codeBitmap = CodeUtil.getInstance().createBitmap(code); + mLoginActivity.getIvCaptcha().setImageBitmap(codeBitmap); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterActivity.java new file mode 100644 index 0000000..4153897 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterActivity.java @@ -0,0 +1,100 @@ +package xyz.fycz.myreader.ui.user; + +/** + * @author fengyue + * @date 2020/4/26 18:49 + */ + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.*; +import butterknife.BindView; +import butterknife.ButterKnife; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; + + +@SuppressLint("Registered") +public class RegisterActivity extends BaseActivity{ + @BindView(R.id.ll_title_back) + LinearLayout llTitleBack; + @BindView(R.id.tv_title_text) + TextView tvTitleText; + @BindView(R.id.et_username) + EditText etUsername; + @BindView(R.id.et_password) + EditText etPassword; + @BindView(R.id.et_rp_password) + EditText etRpPassword; + @BindView(R.id.et_captcha) + EditText 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 RegisterPresenter mRegisterPresenter; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_register); + ButterKnife.bind(this); + setStatusBar(R.color.sys_line); + mRegisterPresenter = new RegisterPresenter(this); + mRegisterPresenter.start(); + } + + public LinearLayout getLlTitleBack() { + return llTitleBack; + } + + public TextView getTvTitleText() { + return tvTitleText; + } + + public EditText getEtUsername() { + return etUsername; + } + + public EditText getEtPassword() { + return etPassword; + } + + public EditText getEtRpPassword() { + return etRpPassword; + } + + public EditText getEtCaptcha() { + return etCaptcha; + } + + public ImageView getIvCaptcha() { + return ivCaptcha; + } + + public Button getBtRegister() { + return btRegister; + } + + public TextView getTvRegisterTip() { + return tvRegisterTip; + } + + public CheckBox getCbAgreement() { + return cbAgreement; + } + + public TextView getTvAgreement() { + return tvAgreement; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterPresenter.java new file mode 100644 index 0000000..3829899 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/user/RegisterPresenter.java @@ -0,0 +1,249 @@ +package xyz.fycz.myreader.ui.user; + +import android.annotation.SuppressLint; +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 xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.backup.UserService; +import xyz.fycz.myreader.base.BasePresenter; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.creator.DialogCreator; +import xyz.fycz.myreader.util.AppInfoUtils; +import xyz.fycz.myreader.util.CodeUtil; +import xyz.fycz.myreader.util.CyptoUtils; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * @author fengyue + * @date 2020/4/26 20:31 + */ + +public class RegisterPresenter implements BasePresenter { + private RegisterActivity mRegisterActivity; + private String code; + private String username = ""; + private String password = ""; + private String rpPassword = ""; + private String inputCode = ""; + + public RegisterPresenter(RegisterActivity mRegisterActivity) { + this.mRegisterActivity = mRegisterActivity; + } + + @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: + mRegisterActivity.getTvRegisterTip().setVisibility(View.GONE); + break; + } + } + }; + + @Override + public void start() { + mHandler.sendMessage(mHandler.obtainMessage(1)); + mRegisterActivity.getTvTitleText().setText("注册"); + mRegisterActivity.getEtUsername().requestFocus(); + mRegisterActivity.getLlTitleBack().setOnClickListener(v -> mRegisterActivity.finish()); + + mRegisterActivity.getEtUsername().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(); + } + }); + + mRegisterActivity.getEtPassword().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(); + } + }); + + mRegisterActivity.getEtRpPassword().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(); + } + }); + + mRegisterActivity.getEtCaptcha().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(); + } + }); + + mRegisterActivity.getIvCaptcha().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + + mRegisterActivity.getBtRegister().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!username.matches("^[A-Za-z][A-Za-z0-9]{5,13}$")){ + DialogCreator.createTipDialog(mRegisterActivity, "用户名格式错误", + "用户名必须在6-14位之间\n用户名只能以字母开头\n用户名只能由数字、字母、下划线、减号组成"); + }else if(password.matches("^\\d+$") || !password.matches("^.{8,16}$")){ + DialogCreator.createTipDialog(mRegisterActivity, "密码格式错误", + "密码必须在8-16位之间\n密码不能是纯数字"); + }else if(!password.equals(rpPassword)){ + DialogCreator.createTipDialog(mRegisterActivity, "重复密码错误", + "两次输入的密码不一致"); + }else if(!inputCode.trim().toLowerCase().equals(code.toLowerCase())){ + DialogCreator.createTipDialog(mRegisterActivity, "验证码错误"); + }else if(!mRegisterActivity.getCbAgreement().isChecked()){ + DialogCreator.createTipDialog(mRegisterActivity, "请勾选同意《用户服务协议》"); + }else { + 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); + mRegisterActivity.finish(); + } + TextHelper.showText(info[1]); + } + @Override + public void onError(Exception e) { + TextHelper.showText("注册失败:\n" + e.getLocalizedMessage()); + } + }); + } + mHandler.sendMessage(mHandler.obtainMessage(1)); + } + }); + + mRegisterActivity.getTvAgreement().setMovementMethod(LinkMovementMethod.getInstance()); + } + + + public void createCaptcha() { + code = CodeUtil.getInstance().createCode(); + Bitmap codeBitmap = CodeUtil.getInstance().createBitmap(code); + mRegisterActivity.getIvCaptcha().setImageBitmap(codeBitmap); + } + + public void showTip(String tip) { + mRegisterActivity.getTvRegisterTip().setVisibility(View.VISIBLE); + mRegisterActivity.getTvRegisterTip().setText(tip); + } + + public void checkNotNone(){ + if ("".equals(username) || "".equals(password) || "".equals(rpPassword) || "".equals(inputCode)){ + mRegisterActivity.getBtRegister().setEnabled(false); + }else { + mRegisterActivity.getBtRegister().setEnabled(true); + } + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/AlarmHelper.java b/app/src/main/java/xyz/fycz/myreader/util/AlarmHelper.java new file mode 100644 index 0000000..7dfb5f5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/AlarmHelper.java @@ -0,0 +1,72 @@ +package xyz.fycz.myreader.util; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import xyz.fycz.myreader.common.APPCONST; + +import java.util.Date; + +import static android.content.Context.ALARM_SERVICE; +import static android.content.Intent.FLAG_INCLUDE_STOPPED_PACKAGES; + + + +public class AlarmHelper { + + private static String alarmActicon = "xyz.fycz.kl.gxdw"; + private static String AntiHijackingActicon = "xyz.fycz.kl.gxdw.AntiHijacking"; + + public static void addOneShotAlarm(Context context, long time, String msg, int id){ + Date date = new Date(); + if(time <= date.getTime()){ + return; + } + Intent intent = new Intent(alarmActicon); + intent.putExtra(APPCONST.ALARM_SCHEDULE_MSG,msg); + intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); + PendingIntent pi = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmManager = (AlarmManager)context.getSystemService(ALARM_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarmManager.setExact(AlarmManager.RTC_WAKEUP,time,pi); + }else { + alarmManager.set(AlarmManager.RTC_WAKEUP,time,pi); + } + } + + public static void removeOneShotAlarm(Context context, int id){ + Intent intent = new Intent(alarmActicon); + intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); + PendingIntent pi = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmManager = (AlarmManager)context.getSystemService(ALARM_SERVICE); + alarmManager.cancel(pi); + } + + public static void addAlarm(Context context, long time, int id){ +// Date date = new Date(); + /* if(time <= date.getTime()){ + return; + }*/ + Intent intent = new Intent(AntiHijackingActicon); + + intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); + PendingIntent pi = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmManager = (AlarmManager)context.getSystemService(ALARM_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + alarmManager.setExact(AlarmManager.RTC_WAKEUP,new Date().getTime() + time,pi); + }else { + alarmManager.set(AlarmManager.RTC_WAKEUP,new Date().getTime() + time,pi); + } + } + + public static void removeAlarm(Context context, int id){ + Intent intent = new Intent(AntiHijackingActicon); + intent.setFlags(FLAG_INCLUDE_STOPPED_PACKAGES); + PendingIntent pi = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmManager = (AlarmManager)context.getSystemService(ALARM_SERVICE); + alarmManager.cancel(pi); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/Anti_hijackingUtils.java b/app/src/main/java/xyz/fycz/myreader/util/Anti_hijackingUtils.java new file mode 100644 index 0000000..06ebdc3 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/Anti_hijackingUtils.java @@ -0,0 +1,125 @@ +package xyz.fycz.myreader.util; + +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import xyz.fycz.myreader.R; + +/** + * 防界面劫持提示 + */ +public class Anti_hijackingUtils { + +// private int id = 12345678;//记录定时ID + + private static boolean home; + private static boolean back; + + + /** + * 用于执行定时任务 + */ + private Timer timer = null; + + /** + * 用于保存当前任务 + */ + private List tasks = null; + + /** + * 唯一实例 + */ + private static Anti_hijackingUtils anti_hijackingUtils; + + private Anti_hijackingUtils() { + // 初始化 + tasks = new ArrayList(); + timer = new Timer(); + } + + /** + * 获取唯一实例 + * + * @return 唯一实例 + */ + public static Anti_hijackingUtils getinstance() { + if (anti_hijackingUtils == null) { + anti_hijackingUtils = new Anti_hijackingUtils(); + } + return anti_hijackingUtils; + } + + /** + * 在activity的onPause()方法中调用 + * + * @param activity + */ + public void onPause(final AppCompatActivity activity) { + MyTimerTask task = new MyTimerTask(activity); + tasks.add(task); + timer.schedule(task, 2000); +// AlarmHelper.addAlarm(activity,2000,id); + } + + /** + * 在activity的onResume()方法中调用 + */ + public void onResume(final AppCompatActivity activity) { + if (tasks.size() > 0) { + tasks.get(tasks.size() - 1).setCanRun(false); + tasks.remove(tasks.size() - 1); + } +// AlarmHelper.removeAlarm(activity,id); + } + + /** + * 自定义TimerTask类 + */ + class MyTimerTask extends TimerTask { + /** + * 任务是否有效 + */ + private boolean canRun = true; + private AppCompatActivity activity; + + private void setCanRun(boolean canRun) { + this.canRun = canRun; + } + + public MyTimerTask(AppCompatActivity activity) { + this.activity = activity; + } + + @Override + public void run() { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (canRun) { + // 程序退到后台,进行风险警告 + if (home || back){ + Toast.makeText(activity, activity.getString(R.string.anti_hijacking_tips_home), Toast.LENGTH_LONG).show(); + + tasks.remove(MyTimerTask.this); + home = false; + }else { + Toast.makeText(activity, activity.getString(R.string.anti_hijacking_tips), Toast.LENGTH_LONG).show(); +// TextHelper.showLongText(MyApplication.getApplication().getString(R.string.anti_hijacking_tips)); + tasks.remove(MyTimerTask.this); + } + } + } + }); + } + + } + + +} + diff --git a/app/src/main/java/xyz/fycz/myreader/util/AppInfoUtils.java b/app/src/main/java/xyz/fycz/myreader/util/AppInfoUtils.java new file mode 100644 index 0000000..23436bf --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/AppInfoUtils.java @@ -0,0 +1,84 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author fengyue + * @date 2020/7/13 15:59 + */ +public class AppInfoUtils { + public final static String SHA1 = "SHA1"; + + /** + * 返回一个签名的对应类型的字符串 + * + * @param context + * @param packageName + * @param type + * + * @return + */ + public static String getSingInfo(Context context, String packageName, String type) { + String tmp = null; + Signature[] signs = getSignatures(context, packageName); + for (Signature sig : signs) { + if (SHA1.equals(type)) { + tmp = getSignatureString(sig, SHA1); + break; + } + } + return tmp; + } + + /** + * 返回对应包的签名信息 + * + * @param context + * @param packageName + * + * @return + */ + public static Signature[] getSignatures(Context context, String packageName) { + PackageInfo packageInfo = null; + try { + packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + return packageInfo.signatures; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取相应的类型的字符串(把签名的byte[]信息转换成16进制) + * + * @param sig + * @param type + * + * @return + */ + public static String getSignatureString(Signature sig, String type) { + byte[] hexBytes = sig.toByteArray(); + String fingerprint = "error!"; + try { + MessageDigest digest = MessageDigest.getInstance(type); + if (digest != null) { + byte[] digestBytes = digest.digest(hexBytes); + StringBuilder sb = new StringBuilder(); + for (byte digestByte : digestBytes) { + sb.append((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)); + } + fingerprint = sb.toString(); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return fingerprint; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/BadgeUtil.java b/app/src/main/java/xyz/fycz/myreader/util/BadgeUtil.java new file mode 100644 index 0000000..2a046bc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/BadgeUtil.java @@ -0,0 +1,187 @@ +package xyz.fycz.myreader.util; + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + + + +public class BadgeUtil { + + /** + * Set badge count
+ * 针对 Samsung / xiaomi / sony 手机有效 + * @param context The context of the application package. + * @param count Badge count to be set + */ + @TargetApi(16) + public static void setBadgeCount(Context context, int count) { + if (count <= 0) { + count = 0; + } else { + count = Math.max(0, Math.min(count, 99)); + } + + if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { + NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + Notification.Builder builder = new Notification.Builder(context).setContentTitle("title").setContentText("text"); + + Notification notification = builder.build(); + + try { + + Field field = notification.getClass().getDeclaredField("extraNotification"); + + Object extraNotification = field.get(notification); + + Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class); + + method.invoke(extraNotification, count); + + mNotificationManager.notify(0,notification); + sendToXiaoMi(notification, context, count); + + } catch (Exception e) { + + e.printStackTrace(); + + } + + + } else if (Build.MANUFACTURER.equalsIgnoreCase("sony")) { + sendToSony(context, count); + } else if (Build.MANUFACTURER.toLowerCase().contains("samsung")) { + sendToSamsumg(context, count); + } else { +// Toast.makeText(context, "Not Support", Toast.LENGTH_LONG).show(); + } + } + + + + + /** + * 向小米手机发送未读消息数广播 + * @param count + */ + private static void sendToXiaoMi(Notification notification, Context context, int count) { + try { +// Class miuiNotificationClass = Class.forName("android.app.MiuiNotification"); +// Object miuiNotification = miuiNotificationClass.newInstance(); +// Field field = miuiNotification.getClass().getDeclaredField("messageCount"); +// field.setAccessible(true); +// field.set(miuiNotification, String.valueOf(count == 0 ? "" : count)); // 设置信息数-->这种发送必须是miui 6才行 + + Field field = notification.getClass().getDeclaredField("extraNotification"); + + Object extraNotification = field.get(notification); + + Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class); + + method.invoke(extraNotification, count); + + } catch (Exception e) { + e.printStackTrace(); + // miui 6之前的版本 + Intent localIntent = new Intent( + "android.intent.action.APPLICATION_MESSAGE_UPDATE"); + localIntent.putExtra( + "android.intent.extra.update_application_component_name", + context.getPackageName() + "/" + getLauncherClassName(context)); + localIntent.putExtra( + "android.intent.extra.update_application_message_text", String.valueOf(count == 0 ? "" : count)); + context.sendBroadcast(localIntent); + } + } + + + /** + * 向索尼手机发送未读消息数广播
+ * 据说:需添加权限: [未验证] + * @param count + */ + private static void sendToSony(Context context, int count){ + String launcherClassName = getLauncherClassName(context); + if (launcherClassName == null) { + return; + } + + boolean isShow = true; + if (count == 0) { + isShow = false; + } + Intent localIntent = new Intent(); + localIntent.setAction("com.sonyericsson.home.action.UPDATE_BADGE"); + localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE",isShow);//是否显示 + localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME",launcherClassName );//启动页 + localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", String.valueOf(count));//数字 + localIntent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.getPackageName());//包名 + context.sendBroadcast(localIntent); + } + + + /** + * 向三星手机发送未读消息数广播 + * @param count + */ + private static void sendToSamsumg(Context context, int count){ + String launcherClassName = getLauncherClassName(context); + if (launcherClassName == null) { + return; + } + Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE"); + intent.putExtra("badge_count", count); + intent.putExtra("badge_count_package_name", context.getPackageName()); + intent.putExtra("badge_count_class_name", launcherClassName); + context.sendBroadcast(intent); + } + + + /** + * 重置、清除Badge未读显示数
+ * @param context + */ + /* public static void resetBadgeCount(Notification notification,Context context) { + setBadgeCount(notification, context, 0); + }*/ + + + /** + * Retrieve launcher activity name of the application from the context + * + * @param context The context of the application package. + * @return launcher activity name of this application. From the + * "android:name" attribute. + */ + private static String getLauncherClassName(Context context) { + PackageManager packageManager = context.getPackageManager(); + + Intent intent = new Intent(Intent.ACTION_MAIN); + // To limit the components this Intent will resolve to, by setting an + // explicit package name. + intent.setPackage(context.getPackageName()); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + + // All Application must have 1 Activity at least. + // Launcher activity must be found! + ResolveInfo info = packageManager + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + + // get a ResolveInfo containing ACTION_MAIN, CATEGORY_LAUNCHER + // if there is no Activity which has filtered by CATEGORY_DEFAULT + if (info == null) { + info = packageManager.resolveActivity(intent, 0); + } + + return info.activityInfo.name; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/BeanPropertiesUtil.java b/app/src/main/java/xyz/fycz/myreader/util/BeanPropertiesUtil.java new file mode 100644 index 0000000..7fc2bae --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/BeanPropertiesUtil.java @@ -0,0 +1,125 @@ +package xyz.fycz.myreader.util; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Created by Kodulf + */ +public class BeanPropertiesUtil { + /** + * 利用反射实现对象之间属性复制 + * @param from + * @param to + */ + public static void copyProperties(Object from, Object to) throws Exception { + copyPropertiesExclude(from, to, null); + } + + /** + * 复制对象属性 + * @param from + * @param to + * @param excludsArray 排除属性列表 + * @throws Exception + */ + @SuppressWarnings("unchecked") + public static void copyPropertiesExclude(Object from, Object to, String[] excludsArray) throws Exception { + List excludesList = null; + if(excludsArray != null && excludsArray.length > 0) { + excludesList = Arrays.asList(excludsArray); //构造列表对象 + } + Method[] fromMethods = from.getClass().getDeclaredMethods(); + Method[] toMethods = to.getClass().getDeclaredMethods(); + Method fromMethod = null, toMethod = null; + String fromMethodName = null, toMethodName = null; + for (int i = 0; i < fromMethods.length; i++) { + fromMethod = fromMethods[i]; + fromMethodName = fromMethod.getName(); + if (!fromMethodName.contains("get") || fromMethodName.contains("getId")) + continue; + //排除列表检测 + if(excludesList != null && excludesList.contains(fromMethodName.substring(3).toLowerCase())) { + continue; + } + toMethodName = "set" + fromMethodName.substring(3); + toMethod = findMethodByName(toMethods, toMethodName); + if (toMethod == null) + continue; + Object value = fromMethod.invoke(from); + if(value == null) + continue; + //集合类判空处理 + if(value instanceof Collection) { + Collection newValue = (Collection)value; + if(newValue.size() <= 0) + continue; + } + toMethod.invoke(to, value); + } + } + + /** + * 对象属性值复制,仅复制指定名称的属性值 + * @param from + * @param to + * @param includsArray + * @throws Exception + */ + @SuppressWarnings("unchecked") + public static void copyPropertiesInclude(Object from, Object to, String[] includsArray) throws Exception { + List includesList = null; + if(includsArray != null && includsArray.length > 0) { + includesList = Arrays.asList(includsArray); //构造列表对象 + } else { + return; + } + Method[] fromMethods = from.getClass().getDeclaredMethods(); + Method[] toMethods = to.getClass().getDeclaredMethods(); + Method fromMethod = null, toMethod = null; + String fromMethodName = null, toMethodName = null; + for (int i = 0; i < fromMethods.length; i++) { + fromMethod = fromMethods[i]; + fromMethodName = fromMethod.getName(); + if (!fromMethodName.contains("get")) + continue; + //排除列表检测 + String str = fromMethodName.substring(3); + if(!includesList.contains(str.substring(0,1).toLowerCase() + str.substring(1))) { + continue; + } + toMethodName = "set" + fromMethodName.substring(3); + toMethod = findMethodByName(toMethods, toMethodName); + if (toMethod == null) + continue; + Object value = fromMethod.invoke(from); + if(value == null) + continue; + //集合类判空处理 + if(value instanceof Collection) { + Collection newValue = (Collection)value; + if(newValue.size() <= 0) + continue; + } + toMethod.invoke(to, value); + } + } + + + + /** + * 从方法数组中获取指定名称的方法 + * + * @param methods + * @param name + * @return + */ + public static Method findMethodByName(Method[] methods, String name) { + for (int j = 0; j < methods.length; j++) { + if (methods[j].getName().equals(name)) + return methods[j]; + } + return null; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/BrightUtil.java b/app/src/main/java/xyz/fycz/myreader/util/BrightUtil.java new file mode 100644 index 0000000..dfad6e9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/BrightUtil.java @@ -0,0 +1,58 @@ +package xyz.fycz.myreader.util; + +import android.content.ContentResolver; +import android.provider.Settings; +import android.view.WindowManager; + +import androidx.appcompat.app.AppCompatActivity; + + +public class BrightUtil { + /** + * 获取屏幕的亮度 + * + * @param activity + * @return + */ + public static int getScreenBrightness(AppCompatActivity activity) { + int nowBrightnessValue = 0; + ContentResolver resolver = activity.getContentResolver(); + try { + nowBrightnessValue = android.provider.Settings.System.getInt( + resolver, Settings.System.SCREEN_BRIGHTNESS); + } catch (Exception e) { + e.printStackTrace(); + } + return nowBrightnessValue; + } + /** + * 设置亮度 + * + * @param activity + * @param brightness + */ + public static void setBrightness(AppCompatActivity activity, int brightness) { + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); + lp.screenBrightness = Float.valueOf(brightness) * (1f / 255f); + activity.getWindow().setAttributes(lp); + } + + public static int brightToProgress(int brightness){ + return (int)(Float.valueOf(brightness) * (1f / 255f) * 100); + } + + public static int progressToBright(int progress){ + return progress * 255 / 100; + } + + /** + * 亮度跟随系统 + * @param activity + */ + public static void followSystemBright(AppCompatActivity activity){ + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); + lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; + activity.getWindow().setAttributes(lp); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/CacheFileNameHelper.java b/app/src/main/java/xyz/fycz/myreader/util/CacheFileNameHelper.java new file mode 100644 index 0000000..3816727 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/CacheFileNameHelper.java @@ -0,0 +1,8 @@ +package xyz.fycz.myreader.util; + + + + +public class CacheFileNameHelper { + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/CacheHelper.java b/app/src/main/java/xyz/fycz/myreader/util/CacheHelper.java new file mode 100644 index 0000000..2746491 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/CacheHelper.java @@ -0,0 +1,140 @@ +package xyz.fycz.myreader.util; + + + +import xyz.fycz.myreader.application.MyApplication; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Hashtable; + + + +public class CacheHelper { + + private static Hashtable memCacheRegion = new Hashtable(); + private static final int CACHE_TIME = 60*60000; + public static String WRITING_OR_READING_FILE_NAME = ""; + + /** + * 读取对象(Serializable) + * @param file + * @return + * @throws IOException + */ + public static Serializable readObject(String file){ + if(!isExistDataCache(file)) return null; + while(WRITING_OR_READING_FILE_NAME.equals(file)){ + try { + Thread.sleep(100); + }catch (Exception e){ + e.printStackTrace(); + } + } + WRITING_OR_READING_FILE_NAME = file; + FileInputStream fis = null; + ObjectInputStream ois = null; + try{ + fis = MyApplication.getApplication().openFileInput(file); + ois = new ObjectInputStream(fis); + return (Serializable)ois.readObject(); + }catch(FileNotFoundException e){ + e.printStackTrace(); + // return null; + }catch(Exception e){ + e.printStackTrace(); + //反序列化失败 - 删除缓存文件 + if(e instanceof InvalidClassException){ + File data = MyApplication.getApplication().getFileStreamPath(file); + data.delete(); + } + // return null; + }finally{ + try { + ois.close(); + fis.close(); + WRITING_OR_READING_FILE_NAME = ""; + } catch (Exception e) { + e.printStackTrace(); + WRITING_OR_READING_FILE_NAME = ""; + } + } + return null; + } + + + public static boolean deleteFile(String file){ + while(WRITING_OR_READING_FILE_NAME.equals(file)){ + try { + Thread.sleep(100); + }catch (Exception e){ + e.printStackTrace(); + } + } + WRITING_OR_READING_FILE_NAME = file; + boolean flag = MyApplication.getApplication().deleteFile(file); + WRITING_OR_READING_FILE_NAME = ""; + return flag; + } + + /** + * 保存对象 + * @param ser + * @param file + * @throws IOException + */ + public static boolean saveObject(Serializable ser, String file) { + while(WRITING_OR_READING_FILE_NAME.equals(file)){ + try { + Thread.sleep(100); + }catch (Exception e){ + e.printStackTrace(); + } + } + WRITING_OR_READING_FILE_NAME = file; + FileOutputStream fos = null; + ObjectOutputStream oos = null; + try{ + fos = MyApplication.getApplication().openFileOutput(file, MyApplication.getApplication().MODE_PRIVATE); + oos = new ObjectOutputStream(fos); + oos.writeObject(ser); + oos.flush(); + fos.flush(); + return true; + }catch(Exception e){ + e.printStackTrace(); + return false; + }finally{ + try { + oos.close(); + fos.close(); + WRITING_OR_READING_FILE_NAME = ""; + } catch (Exception e) { + e.printStackTrace(); + WRITING_OR_READING_FILE_NAME = ""; + + } + + } + } + + /** + * 判断缓存是否存在 + * @param cachefile + * @return + */ + private static boolean isExistDataCache(String cachefile){ + boolean exist = false; + File data = MyApplication.getApplication().getFileStreamPath(cachefile); + if(data.exists()) + exist = true; + return exist; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/CalendarHelper.java b/app/src/main/java/xyz/fycz/myreader/util/CalendarHelper.java new file mode 100644 index 0000000..8af5865 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/CalendarHelper.java @@ -0,0 +1,242 @@ +package xyz.fycz.myreader.util; + + + +import xyz.fycz.myreader.entity.Date; + +import java.util.ArrayList; +import java.util.Calendar; + + + +public class CalendarHelper { + + public static ArrayList getMonthDate(int year, int month) { + int totalDays = 0, curMonthDays = 0, firstDayOfMonth = 0, lastMonthDays; + ArrayList dates = new ArrayList<>(); + curMonthDays = getMonthDays(year, month); + for (int i = 1900; i < year; i++) { + if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) { + totalDays = totalDays + 366; + } else { + totalDays = totalDays + 365; + } + } + for (int i = 1; i < month; i++) { + totalDays += getMonthDays(year, i); + } + + int temp = 1 + totalDays % 7; + if (temp == 7) { + firstDayOfMonth = 0; //周日 + } else { + firstDayOfMonth = temp; + } + if (month != 1) { + lastMonthDays = getMonthDays(year, month - 1); + } else { + lastMonthDays = getMonthDays(year - 1, 12); + } + for (int i = 0; i < firstDayOfMonth; i++) { + Date date = new Date(); + if(month == 1){ + date.setYear(year - 1); + date.setMonth(12); + }else { + date.setYear(year); + date.setMonth(month -1); + } + date.setDate(lastMonthDays - firstDayOfMonth + i + 1); + date.setDay(i); + dates.add(date); + } + for(int i = 0; i < curMonthDays; i++ ){ + Date date = new Date(); + date.setYear(year); + date.setMonth(month); + date.setDate(i + 1); + date.setDay(firstDayOfMonth ++); + if(firstDayOfMonth > 6){ + firstDayOfMonth = 0; + } + dates.add(date); + } + if(firstDayOfMonth != 0){ + int tem = 1; + for(int i = firstDayOfMonth; i < 7; i++){ + Date date = new Date(); + if(month == 12){ + date.setYear(year + 1); + date.setMonth(1); + }else { + date.setYear(year); + date.setMonth(month +1); + } + date.setDate(tem++); + date.setDay(i); + dates.add(date); + } + } + return dates; + } + + public static int getMonthDays(int year,int month){ + boolean isRun = false; + if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){ + isRun = true; + } + switch(month){ + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + case 4: + case 6: + case 9: + case 11: + return 30; + + case 2: + if(isRun){ + return 29; + }else{ + return 28; + } + default: + return 0; + } + } + + private static Calendar dateToCalendar(Date date){ + java.util.Date date1 = new java.util.Date(date.getYear()-1900,date.getMonth()-1,date.getDate()); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date1); + return calendar; + } + + public static Date getDate(java.util.Date date){ + Date dateRes = new Date(); + dateRes.setDate(date.getDate()); + dateRes.setYear(date.getYear() + 1900); + dateRes.setMonth(date.getMonth() + 1); + dateRes.setDay(date.getDay()); + return dateRes; + } + + public static boolean isTomorrow(Date date){ + Date today = getDate(new java.util.Date()); + if(today.nextDate().isSameDate(date)){ + return true; + }else return false; + } + + + + public static String getDay(Date date) { + String[] weekDaysName = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" }; + String[] weekDaysCode = { "0", "1", "2", "3", "4", "5", "6" }; + Calendar calendar = dateToCalendar(date); + int intWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; + return weekDaysCode[intWeek]; + } + + public static String getDate(Date date) { + StringBuilder stringBuilder = new StringBuilder(); + if (date.getMonth() < 10) { + stringBuilder.append("0"); + } + stringBuilder.append(String.valueOf(date.getMonth())); + stringBuilder.append("."); + if (date.getDate() < 10) { + stringBuilder.append("0"); + } + stringBuilder.append(String.valueOf(date.getDate())); + return stringBuilder.toString(); + } + + public static String getDay2(Date date) { + Date today = CalendarHelper.getDate(new java.util.Date()); + if (date.isSameDate(today)) { + return "今天"; + } else if (CalendarHelper.isTomorrow(date)) { + return "明天"; + } else if (CalendarHelper.isTomorrow(date.lastDate())) { + return "后天"; + } else { + switch (date.getDay()) { + case 0: + return "周日"; + + case 1: + return "周一"; + + case 2: + return "周二"; + + case 3: + return "周三"; + + case 4: + return "周四"; + + case 5: + return "周五"; + + case 6: + return "周六"; + + } + } + return ""; + } + + + + /** + * 获取未来 任意天内的日期数组 + * @param intervals intervals天内 + * @return 日期数组 + */ + public static ArrayList getFetureDates(int intervals) { +// ArrayList pastDaysList = new ArrayList<>(); + ArrayList fetureDaysList = new ArrayList<>(); + Date date = CalendarHelper.getDate(new java.util.Date()); + for (int i = 0; i + * HmacMD5 + * HmacSHA1 + * HmacSHA256 + * HmacSHA384 + * HmacSHA512 + * + */ + public static final String KEY_MAC = "HmacMD5"; + + /** + * BASE64解密 + * @param key + * @return + * @throws Exception + */ + public static byte[] decryptBASE64( String key ) throws Exception { + return (new BASE64Decoder()).decodeBuffer(key); + } + + /** + * BASE64加密 + * @param key + * @return + * @throws Exception + */ + public static String encryptBASE64(byte[] key) throws Exception { + return (new BASE64Encoder()).encodeBuffer(key); + } + + /** + * MD5 加密 + * @param data + * @return + * @throws Exception + */ + public static byte[] encryptMD5( byte[] data) throws Exception { + MessageDigest md5 = MessageDigest.getInstance(KEY_MD5); + md5.update(data); + + return md5.digest(); + } + + /** + * SHA 加密 + * @param data + * @return + * @throws Exception + */ + public static byte[] encryptSHA( byte[] data) throws Exception { + MessageDigest sha = MessageDigest.getInstance(KEY_SHA); + sha.update(data); + + return sha.digest(); + } + + /** + * 初始化HMAC密钥 + * + * @return + * @throws Exception + */ + public static String initMacKey() throws Exception { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC); + + SecretKey secretKey = keyGenerator.generateKey(); + return encryptBASE64(secretKey.getEncoded()); + } + + /** + * HMAC 加密 + * @param data + * @param key + * @return + * @throws Exception + */ + public static byte[] encryptHMAC( byte[] data, String key) throws Exception { + SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC); + Mac mac = Mac.getInstance(secretKey.getAlgorithm()); + mac.init(secretKey); + return mac.doFinal(data); + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/CyptoUtils.java b/app/src/main/java/xyz/fycz/myreader/util/CyptoUtils.java new file mode 100644 index 0000000..0553125 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/CyptoUtils.java @@ -0,0 +1,155 @@ +package xyz.fycz.myreader.util; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; + +/** + * 加密解密工具包 + * @author lxp + */ +public class CyptoUtils { + + public static final String ALGORITHM_DES = "DES/CBC/PKCS5Padding"; + + /** + * DES算法,加密 + * + * @param data 待加密字符串 + * @param key 加密私钥,长度不能够小于8位 + * @return 加密后的字节数组,一般结合Base64编码使用 + * @throws Exception + */ + public static String encode(String key, String data) { + if(data == null) + return null; + try{ + DESKeySpec dks = new DESKeySpec(key.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + //key的长度不能够小于8位字节 + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance(ALGORITHM_DES); + IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); + AlgorithmParameterSpec paramSpec = iv; + cipher.init(Cipher.ENCRYPT_MODE, secretKey,paramSpec); + byte[] bytes = cipher.doFinal(data.getBytes()); + return byte2hex(bytes); + }catch(Exception e){ + e.printStackTrace(); + return data; + } + } + /** + * DES算法,加密 + * + * @param data 待加密字符串 + * @param key 加密私钥,长度不能够小于8位 + * @return 加密后的字节数组,一般结合Base64编码使用 + * @throws Exception + */ + public static byte[] encode(String key, byte[] data) { + if(data == null) + return null; + try{ + DESKeySpec dks = new DESKeySpec(key.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + //key的长度不能够小于8位字节 + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance(ALGORITHM_DES); + IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); + AlgorithmParameterSpec paramSpec = iv; + cipher.init(Cipher.ENCRYPT_MODE, secretKey,paramSpec); + byte[] bytes = cipher.doFinal(data); + return bytes; + }catch(Exception e){ + e.printStackTrace(); + return null; + } + } + + /** + * DES算法,解密 + * + * @param data 待解密字符串 + * @param key 解密私钥,长度不能够小于8位 + * @return 解密后的字节数组 + * @throws Exception 异常 + */ + public static String decode(String key, String data) { + if(data == null) + return null; + try { + DESKeySpec dks = new DESKeySpec(key.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + //key的长度不能够小于8位字节 + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance(ALGORITHM_DES); + IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); + AlgorithmParameterSpec paramSpec = iv; + cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + return new String(cipher.doFinal(hex2byte(data.getBytes()))); + } catch (Exception e){ + e.printStackTrace(); + return data; + } + } + /** + * DES算法,解密 + * + * @param data 待解密字符串 + * @param key 解密私钥,长度不能够小于8位 + * @return 解密后的字节数组 + * @throws Exception 异常 + */ + public static byte[] decodeToByte(String key, byte[] data) { + if(data == null) + return null; + try { + DESKeySpec dks = new DESKeySpec(key.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + //key的长度不能够小于8位字节 + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance(ALGORITHM_DES); + IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); + AlgorithmParameterSpec paramSpec = iv; + cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + return cipher.doFinal(hex2byte(data)); + } catch (Exception e){ + e.printStackTrace(); + return null; + } + } + + /** + * 二行制转字符串 + * @param b + * @return + */ + private static String byte2hex(byte[] b) { + StringBuilder hs = new StringBuilder(); + String stmp; + for (int n = 0; b!=null && n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0XFF); + if (stmp.length() == 1) + hs.append('0'); + hs.append(stmp); + } + return hs.toString().toUpperCase(); + } + + private static byte[] hex2byte(byte[] b) { + if((b.length%2)!=0) + throw new IllegalArgumentException(); + byte[] b2 = new byte[b.length/2]; + for (int n = 0; n < b.length; n+=2) { + String item = new String(b,n,2); + b2[n/2] = (byte) Integer.parseInt(item,16); + } + return b2; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java b/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java new file mode 100644 index 0000000..573d1d3 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/DateHelper.java @@ -0,0 +1,304 @@ +package xyz.fycz.myreader.util; + +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + + +public class DateHelper { + + /** + * @param + * @return + * ==>1471941967364 + * 2147483647 + */ + public static long getLongDate(){ + long res = System.currentTimeMillis(); + return res; + + } + + /** + * + * @param date==>2016/05/02 09:10:46 + * @return ==>2016/05/02 09:10 + */ + public static String formatDate_3(String date){ + String res = date; + res = res.substring(0,res.length()-3); + return res; + } + + /** + *截断字符串末尾num个字符 + */ + public static String formatDateByTailNum(String date, int num){ + String res = date; + res = res.substring(0,res.length() - num); + return res; + } + /** + * + * @param date==>2016/05/02 09:10:46 + * @return ==>2016/05/02 + */ + public static String formatDate3(String date){ + String res = date; + res = res.substring(0,res.length() - 9); + return res; + } + + + public static String getStrLongDate(){ + return String.valueOf(getLongDate()); + } + + public static Date longToDate(long longDate){ + + Date date = new Date(longDate); + return date; + + } + + public static Date strLongToDate(String strLongDate){ + Date date = null; + try{ + date = new Date(Long.parseLong(strLongDate)); + }catch (Exception e){ + e.printStackTrace(); + return null; + } + return date; + } + + /** + * + * @param longDate ==>1471941967364 + * @return ==>2016/05/02 09:10:46 + */ + public static String longToTime(long longDate){ + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String strTime = df.format(longToDate(longDate)); + return strTime; + } + + /** + * + * @param longDate ==>1471941967364 + * @return ==>2016/05/02 09:10 + */ + public static String longToTime2(long longDate){ + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + String strTime = df.format(longToDate(longDate)); + return strTime; + } + + /** + * + * @param longDate ==>1471941967364 + * @return ==>2016/05/02 + */ + public static String longToTime3(long longDate){ + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + String strTime = df.format(longToDate(longDate)); + return strTime; + } + + /** + * + * @param longDate ==>1471941967364 + * @return ==>09:10 + */ + public static String longToDayTime(long longDate){ + DateFormat df = new SimpleDateFormat("HH:mm"); + String strTime = df.format(longToDate(longDate)); + return strTime; + } + + /** + * + * @param strLongDate ==>1471941967364 + * @return ==>2016/05/02 09:10:46 + */ + public static String strLongToTime(String strLongDate){ + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String strTime = df.format(strLongToDate(strLongDate)); + return strTime; + } + + /** + * + * @param longDate ==>1471941967364 + * @return ==>2016年05月02日 09:10 + */ + public static String strLongToScheduleTime(long longDate){ + DateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm"); + String strTime = df.format(longToDate(longDate)); + return strTime; + } + + + /** + * @param + * @return + * ==>Wed May 02 09:10:46 CST 2012 + */ + public static String getTime1(){ + + Date date1 = new Date(); + return date1.toString(); + } + /** + * @param + * @return + * ==>2012-05-02 + */ + public static String getYearMonthDay1(){ + + java.sql.Date date2 = new java.sql.Date(System.currentTimeMillis()); + return date2.toString(); + } + /** + * @param + * @return + * ==>2012-05-02 + */ + public static String getYearMonthDay2(){ + Date date1 = new Date(); + java.sql.Date date3 = new java.sql.Date(date1.getTime()); + return date3.toString(); + } + /** + * @param + * @return + * ==>2012-05-02 09:10:46.436 + */ + public static String getSeconds1(){ + Timestamp stamp1 = new Timestamp(System.currentTimeMillis()); + return stamp1.toString(); + } + + /** + * @param + * @return + * ==>2012-05-02 09:10:46.436 + */ + public static String getSeconds2(){ + + Date date1 = new Date(); + Timestamp stamp2 = new Timestamp(date1.getTime()); + return stamp2.toString(); + } + /** + * @param + * @return + * ==>2012/05/02 09:10:46 + */ + public static String getYear_Second1(){ + + Timestamp stamp1 = new Timestamp(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String timeStr = sdf.format(stamp1); + return timeStr; + } + /** + * @param + * @return + * ==>2012/05/02 09:10:46 + */ + public static String getYear_Second2(){ + + Date date1 = new Date(); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String timeStr2 = df.format(date1); + return timeStr2; + } + /** + * � + * @param str + * ==>yyyy-MM-dd HH:mm:ss + * @param + * @return + * ==>Thu Dec 10 05:12:02 CST 2009 + */ + public static Date changeStringToDate1(String str){ + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date = null; + try { + date = sdf.parse(str); + return date; + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + } + /** + * + * + * @param str + * ==>yyyy-MM-dd HH:mm:ss + * @param + * @return + * ==>2009-12-10 + */ + public static String changeStringToDate2(String str){ + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date6 = null; + try { + date6 = sdf.parse(str); + java.sql.Date date7 = new java.sql.Date(date6.getTime()); + return date7.toString(); + } catch (ParseException e) { + e.printStackTrace(); + return ""; + } + } + /** + * + * + * @param str + * ==>yyyy-MM-dd HH:mm:ss + * @param + * @return + * ==>2009-12-10 05:12:02.0 + */ + public static String changeStringToDate3(String str){ + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date6 = null; + try { + date6 = sdf.parse(str); + java.sql.Date date7 = new java.sql.Date(date6.getTime()); + Timestamp stamp9 = new Timestamp(date7.getTime()); + return stamp9.toString(); + } catch (ParseException e) { + e.printStackTrace(); + return ""; + } + } + + /** + * + * @param str ==>2016/05/02 09:10:46 + * @return 1471941967364 + */ + + public static long strDateToLong(String str){ + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date6 = null; + try { + date6 = sdf.parse(str); + return date6.getTime(); + }catch (ParseException e) { + e.printStackTrace(); + return 0; + } + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/DipPxUtil.java b/app/src/main/java/xyz/fycz/myreader/util/DipPxUtil.java new file mode 100644 index 0000000..5737f01 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/DipPxUtil.java @@ -0,0 +1,24 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; + + + +public class DipPxUtil { + + /** + * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + */ + public static int dp2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale+0.5f); + } + + /** + * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + */ + public static int px2dip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/DownloadMangerUtils.java b/app/src/main/java/xyz/fycz/myreader/util/DownloadMangerUtils.java new file mode 100644 index 0000000..d2da7e7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/DownloadMangerUtils.java @@ -0,0 +1,431 @@ +package xyz.fycz.myreader.util; + +import android.Manifest; +import android.app.DownloadManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.util.Log; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.util.HashMap; +import java.util.Map; + +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.creator.DialogCreator; + +import static android.app.DownloadManager.Request.VISIBILITY_HIDDEN; + + + +public class DownloadMangerUtils { + + public static final String FILE_DIR = "gxdw"; + private static Map mBroadcastReceiverMap = new HashMap<>(); + + /** + * 文件下载 + * + * @param context + * @param fileDir + * @param url + * @param fileName + */ + public static void downloadFile(Context context, String fileDir, String url, String fileName) { + + try { + Log.d("http download:", url); + //String Url = "10.10.123.16:8080/gxqdw_ubap/mEmailController.thumb?getAttachmentStream&fileId=1&fileName=自我探索——我是谁.ppt&emailId=36&token=d1828248-cc71-4719-8218-adc31ffc9cca&type=inbox&fileSize=14696446"; + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + // DownloadManager.Request.setDestinationInExternalPublicDir(); + + request.setDescription(fileName); + request.setTitle("附件"); + // in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + // request.setNotificationVisibility(VISIBILITY_HIDDEN); + } + // int i = Build.VERSION.SDK_INT; + if (Build.VERSION.SDK_INT > 17) { + request.setDestinationInExternalPublicDir(fileDir, fileName); + } else { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(fileDir, fileName); + } else { + Log.d("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + DialogCreator.createCommonDialog(context, "文件下载", "android版本过低或系统兼容性问题,不存在外部存储,无法指定下载路径,文件下载到系统默认路径,请到文件管理搜索文件名", true, + "关闭", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + } + } + // get download service and enqueue file + DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } catch (Exception e) { + e.printStackTrace(); + lowVersionNoSDDownload(context, url, fileName); + } + } + + /** + * 低版本无外置存储下载 + * + * @param context + * @param url + * @param fileName + */ + private static void lowVersionNoSDDownload(Context context, String url, String fileName) { + try { + Log.d("http download:", url); + //String Url = "10.10.123.16:8080/gxqdw_ubap/mEmailController.thumb?getAttachmentStream&fileId=1&fileName=自我探索——我是谁.ppt&emailId=36&token=d1828248-cc71-4719-8218-adc31ffc9cca&type=inbox&fileSize=14696446"; + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + // DownloadManager.Request.setDestinationInExternalPublicDir(); + + request.setDescription(fileName); + request.setTitle("附件"); + // in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + // request.setNotificationVisibility(VISIBILITY_HIDDEN); + } + // int i = Build.VERSION.SDK_INT; + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(APPCONST.FILE_DIR, fileName); + } else { + Log.d("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + DialogCreator.createCommonDialog(context, "文件下载", "android版本过低或系统兼容性问题,不存在外部存储,无法指定下载路径,文件下载到系统默认路径,请到文件管理搜索文件名", true, + "关闭", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + } + // get download service and enqueue file + DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } catch (Exception e) { + e.printStackTrace(); + TextHelper.showText("下载错误:" + e.toString()); + } + + } + + + /** + * 文件下载(有回调,无通知) + * + * @param context + * @param fileDir + * @param fileName + * @param url + * @param listener + */ + public static void downloadFileByFinishListener(Context context, String fileDir, String fileName, String url, + final DownloadCompleteListener listener) { + try { + if (isPermission(context)) { + Log.d("http download:", url); +// String Url = "10.10.123.16:8080/gxqdw_ubap/mEmailController.thumb?getAttachmentStream&fileId=1&fileName=自我探索——我是谁.ppt&emailId=36&token=d1828248-cc71-4719-8218-adc31ffc9cca&type=inbox&fileSize=14696446"; + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); +// DownloadManager.Request.setDestinationInExternalPublicDir(); + + /* request.setDescription(fileName); + request.setTitle("附件");*/ +// in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); +// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + request.setNotificationVisibility(VISIBILITY_HIDDEN); + } +// int i = Build.VERSION.SDK_INT; + if (Build.VERSION.SDK_INT > 17) { + request.setDestinationInExternalPublicDir(fileDir, fileName); + } else { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(fileDir, fileName); + } else { + Log.i("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + } + } +// get download service and enqueue file + final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + + final long id = manager.enqueue(request); + // 注册广播监听系统的下载完成事件。 + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + + final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + long ID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + if (ID == id) { + listener.onFinish(manager.getUriForDownloadedFile(id)); + context.unregisterReceiver(mBroadcastReceiverMap.get(id)); + mBroadcastReceiverMap.remove(id); + /* Toast.makeText(getApplicationContext(), "任务:" + Id + " 下载完成!", Toast.LENGTH_LONG).show();*/ + } + } + }; + context.registerReceiver(broadcastReceiver, intentFilter); + mBroadcastReceiverMap.put(id, broadcastReceiver); + } + } catch (Exception e) { + lowVersionNoSDDownloadFileByFinishListener(context, fileDir, fileName, url, listener); +// listener.onError(e.toString()); + } + + } + + /** + * 低版本无外置存储文件下载(有回调,无通知) + * + * @param context + * @param fileDir + * @param fileName + * @param url + * @param listener + */ + private static void lowVersionNoSDDownloadFileByFinishListener(Context context, String fileDir, String fileName, String url, + final DownloadCompleteListener listener) { + try { + + Log.d("http download:", url); +// String Url = "10.10.123.16:8080/gxqdw_ubap/mEmailController.thumb?getAttachmentStream&fileId=1&fileName=自我探索——我是谁.ppt&emailId=36&token=d1828248-cc71-4719-8218-adc31ffc9cca&type=inbox&fileSize=14696446"; + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); +// DownloadManager.Request.setDestinationInExternalPublicDir(); + + /* request.setDescription(fileName); + request.setTitle("附件");*/ +// in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); +// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + request.setNotificationVisibility(VISIBILITY_HIDDEN); + } +// int i = Build.VERSION.SDK_INT; + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(APPCONST.FILE_DIR, fileName); + } else { + Log.d("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + } + +// get download service and enqueue file + final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + + final long id = manager.enqueue(request); + // 注册广播监听系统的下载完成事件。 + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + + final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + long ID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + if (ID == id) { + listener.onFinish(manager.getUriForDownloadedFile(id)); + context.unregisterReceiver(mBroadcastReceiverMap.get(id)); + mBroadcastReceiverMap.remove(id); + /* Toast.makeText(getApplicationContext(), "任务:" + Id + " 下载完成!", Toast.LENGTH_LONG).show();*/ + } + } + }; + context.registerReceiver(broadcastReceiver, intentFilter); + mBroadcastReceiverMap.put(id, broadcastReceiver); + + } catch (Exception e) { + listener.onError(e.toString()); + } + + } + + /** + * 文件下载(有回调,有通知) + * + * @param context + * @param fileDir + * @param fileName + * @param url + * @param title + * @param listener + */ + public static void downloadFileOnNotificationByFinishListener(final Context context, final String fileDir, final String fileName, final String url, + final String title, final DownloadCompleteListener listener) { + try { + if (isPermission(context)) { + Log.d("http download:", url); + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + request.setTitle(title); + + // in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); + } + + if (Build.VERSION.SDK_INT > 17) { + /* File file = new File(Environment.getExternalStorageDirectory() + "/gxdw/apk/app_gxdw_186.apk"); + if (!file.exists()){ + boolean flag = file.createNewFile(); + }*/ + request.setDestinationInExternalPublicDir(fileDir, fileName); + + } else { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(fileDir, fileName); + } else { + Log.d("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + } + } + +// get download service and enqueue file + final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + + final long id = manager.enqueue(request); + // 注册广播监听系统的下载完成事件。 + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + + final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + long ID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + if (ID == id) { + listener.onFinish(manager.getUriForDownloadedFile(id)); + context.unregisterReceiver(mBroadcastReceiverMap.get(id)); + mBroadcastReceiverMap.remove(id); + /* Toast.makeText(getApplicationContext(), "任务:" + Id + " 下载完成!", Toast.LENGTH_LONG).show();*/ + } + } + }; + context.registerReceiver(broadcastReceiver, intentFilter); + mBroadcastReceiverMap.put(id, broadcastReceiver); + } + } catch (Exception e) { + e.printStackTrace(); + lowVersionDownloadFileOnNotificationByFinishListener(context, fileDir, fileName, url, title, listener); + } + } + + /** + * 低版本文件下载(有回调,有通知) + * + * @param context + * @param fileDir + * @param fileName + * @param url + * @param title + * @param listener + */ + private static void lowVersionDownloadFileOnNotificationByFinishListener(final Context context, final String fileDir, final String fileName, final String url, + final String title, final DownloadCompleteListener listener) { + + + try { + + Log.d("http download:", url); + + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + request.setTitle(title); + +// in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); + + } + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + request.setDestinationInExternalPublicDir(APPCONST.FILE_DIR, fileName); + } else { + Log.d("download", "android版本过低,不存在外部存储,下载路径无法指定,默认路径:/data/data/com.android.providers.downloads/cache/"); + } + + + // get download service and enqueue file + final DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + + final long id = manager.enqueue(request); + // 注册广播监听系统的下载完成事件。 + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + + final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + long ID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + if (ID == id) { + listener.onFinish(manager.getUriForDownloadedFile(id)); + context.unregisterReceiver(mBroadcastReceiverMap.get(id)); + mBroadcastReceiverMap.remove(id); + } + } + }; + context.registerReceiver(broadcastReceiver, intentFilter); + mBroadcastReceiverMap.put(id, broadcastReceiver); + + } catch (Exception e) { + e.printStackTrace(); + listener.onError(e.toString()); + } + + + } + + + /** + * 读写权限判断 + * + * @param context + * @return + */ + public static boolean isPermission(Context context) { + boolean permission = false; + if (Build.VERSION.SDK_INT >= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + TextHelper.showText("当前应用未拥有存储设备读写权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + TextHelper.showText("当前应用未拥有存储设备读写权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + public interface DownloadCompleteListener { + void onFinish(Uri uri); + + void onError(String s); + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/EmailUtils.java b/app/src/main/java/xyz/fycz/myreader/util/EmailUtils.java new file mode 100644 index 0000000..826a8a7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/EmailUtils.java @@ -0,0 +1,20 @@ +package xyz.fycz.myreader.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + + +public class EmailUtils { + /** + * 检验邮箱格式 + * @param email + * @return + */ + public static boolean isMatched(String email){ + String check = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + Pattern regex = Pattern.compile(check); + Matcher matcher = regex.matcher(email); + return matcher.matches(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java new file mode 100644 index 0000000..52b93e9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/FileUtils.java @@ -0,0 +1,734 @@ +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/FormatDateUtils.java b/app/src/main/java/xyz/fycz/myreader/util/FormatDateUtils.java new file mode 100644 index 0000000..9f6eaff --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/FormatDateUtils.java @@ -0,0 +1,58 @@ +package xyz.fycz.myreader.util; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; + + + +public class FormatDateUtils { + private static Date date; + private static SimpleDateFormat format; + + private static SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + public static String formatDateTime(long time) { + if (0 == time) { + return ""; + } + return mDateFormat.format(new Date(time)); + } + + public static String long2date(String str, String formater){ + return formateLongTime(long2String(str),formater); + } + + public static String long2date(long times, String formater){ + return formateLongTime(times,formater); + } + + + private static String formateLongTime(long times, String formater){ + date=new Date(times); + GregorianCalendar gc = new GregorianCalendar(); + gc.setTime(date); + format = new SimpleDateFormat(formater); + String sb=format.format(gc.getTime()); + return sb; + } + + + + public static long long2String(String str){ + long time = 0; + try { + time = mDateFormat.parse(str).getTime(); + } catch (ParseException e) { + e.printStackTrace(); + } + return time; + } + + public static long day2long(long times){ + long day = times / (1000*60*60*24); + return day; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/GreenDaoUpgrade.java b/app/src/main/java/xyz/fycz/myreader/util/GreenDaoUpgrade.java new file mode 100644 index 0000000..45dc6cc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/GreenDaoUpgrade.java @@ -0,0 +1,150 @@ +package xyz.fycz.myreader.util; + + +public class GreenDaoUpgrade { + /*private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS"; + private static GreenDaoUpgrade instance; + + public static GreenDaoUpgrade getInstance() { + if (instance == null) { + instance = new GreenDaoUpgrade(); + } + return instance; + } + + private static List getColumns(Database db, String tableName) { + List columns = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null); + if (cursor != null) { + columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames())); + } + } catch (Exception e) { + Log.v(tableName, e.getMessage(), e); + e.printStackTrace(); + } finally { + if (cursor != null) + cursor.close(); + } + return columns; + } + + public void migrate(Database db, Class>... daoClasses) { + generateTempTables(db, daoClasses); + DaoMaster.dropAllTables(db, true); + DaoMaster.createAllTables(db, false); + restoreData(db, daoClasses); + } + + private void generateTempTables(Database db, Class>... daoClasses) { + for (int i = 0; i < daoClasses.length; i++) { + DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); + + String divider = ""; + String tableName = daoConfig.tablename; + String tempTableName = daoConfig.tablename.concat("_TEMP"); + ArrayList properties = new ArrayList<>(); + + StringBuilder createTableStringBuilder = new StringBuilder(); + + createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" ("); + + for (int j = 0; j < daoConfig.properties.length; j++) { + String columnName = daoConfig.properties[j].columnName; + + if (getColumns(db, tableName).contains(columnName)) { + properties.add(columnName); + + String type = null; + + try { + type = getTypeByClass(daoConfig.properties[j].type); + } catch (Exception exception) { + exception.printStackTrace(); + } + + createTableStringBuilder.append(divider).append(columnName).append(" ").append(type); + + if (daoConfig.properties[j].primaryKey) { + createTableStringBuilder.append(" PRIMARY KEY"); + } + + divider = ","; + } + } + createTableStringBuilder.append(");"); + + db.execSQL(createTableStringBuilder.toString()); + + StringBuilder insertTableStringBuilder = new StringBuilder(); + + insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" ("); + insertTableStringBuilder.append(TextUtils.join(",", properties)); + insertTableStringBuilder.append(") SELECT "); + insertTableStringBuilder.append(TextUtils.join(",", properties)); + insertTableStringBuilder.append(" FROM ").append(tableName).append(";"); + + db.execSQL(insertTableStringBuilder.toString()); + } + } + + private void restoreData(Database db, Class>... daoClasses) { + for (int i = 0; i < daoClasses.length; i++) { + DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); + + String tableName = daoConfig.tablename; + String tempTableName = daoConfig.tablename.concat("_TEMP"); + ArrayList properties = new ArrayList(); + ArrayList propertiesQuery = new ArrayList(); + for (int j = 0; j < daoConfig.properties.length; j++) { + String columnName = daoConfig.properties[j].columnName; + + if (getColumns(db, tempTableName).contains(columnName)) { + properties.add(columnName); + propertiesQuery.add(columnName); + } else { + try { + if (getTypeByClass(daoConfig.properties[j].type).equals("INTEGER")) { + propertiesQuery.add("0 as " + columnName); + properties.add(columnName); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + StringBuilder insertTableStringBuilder = new StringBuilder(); + + insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" ("); + insertTableStringBuilder.append(TextUtils.join(",", properties)); + insertTableStringBuilder.append(") SELECT "); + insertTableStringBuilder.append(TextUtils.join(",", propertiesQuery)); + insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); + + StringBuilder dropTableStringBuilder = new StringBuilder(); + + dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); + + db.execSQL(insertTableStringBuilder.toString()); + db.execSQL(dropTableStringBuilder.toString()); + } + } + + private String getTypeByClass(Class type) throws Exception { + if (type.equals(String.class)) { + return "TEXT"; + } + if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class) || type.equals(int.class)) { + return "INTEGER"; + } + if (type.equals(Boolean.class) || type.equals(boolean.class)) { + return "BOOLEAN"; + } + + Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString())); + exception.printStackTrace(); + throw exception; + }*/ +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java new file mode 100644 index 0000000..4f9d7b6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java @@ -0,0 +1,776 @@ +package xyz.fycz.myreader.util; + +import android.graphics.Bitmap; +import android.util.Base64; +import android.util.Log; + +import com.google.gson.Gson; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.application.TrustAllCerts; +import xyz.fycz.myreader.callback.HttpCallback; +import xyz.fycz.myreader.callback.JsonCallback; +import xyz.fycz.myreader.callback.URLConnectionCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.entity.JsonModel; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import static java.lang.String.valueOf; + + + +public class HttpUtil { + + private static String sessionid; + //最好只使用一个共享的OkHttpClient 实例,将所有的网络请求都通过这个实例处理。 + //因为每个OkHttpClient 实例都有自己的连接池和线程池,重用这个实例能降低延时,减少内存消耗,而重复创建新实例则会浪费资源。 + private static OkHttpClient mClient; + + private static SSLSocketFactory createSSLSocketFactory() { + SSLSocketFactory ssfFactory = null; + + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom()); + ssfFactory = sc.getSocketFactory(); + } catch (Exception e) { + } + + return ssfFactory; + } + + private static synchronized OkHttpClient getOkHttpClient(){ + if (mClient == null){ + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.connectTimeout(30000, TimeUnit.SECONDS); + builder.sslSocketFactory(createSSLSocketFactory()); + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + mClient = builder + .build(); + } + return mClient; + + } + + /** + * 图片发送 + * @param address + * @param callback + */ + public static void sendBitmapGetRequest(final String address, final HttpCallback callback) { + new Thread(new Runnable() { + HttpURLConnection connection = null; + @Override + public void run() { + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-type", "application/octet-stream"); + connection.setRequestProperty("Accept-Charset", "utf-8"); + connection.setRequestProperty("contentType", "utf-8"); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + connection.setDoInput(true); + connection.setDoOutput(true); + InputStream in = connection.getInputStream(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + Log.e("Http", "网络错误异常!!!!"); + } + Log.d("Http", "connection success"); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + Log.e("Http", e.toString()); + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + }).start(); + } + + /** + * get请求 + * @param address + * @param callback + */ + public static void sendGetRequest(final String address, final HttpCallback callback) { + new Thread(new Runnable() { + HttpURLConnection connection = null; + @Override + public void run() { + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-type", "text/html"); + connection.setRequestProperty("Accept-Charset", "utf-8"); + connection.setRequestProperty("contentType", "utf-8"); + connection.setConnectTimeout(60 * 1000); + connection.setReadTimeout(60 * 1000); + connection.setDoInput(true); + connection.setDoOutput(true); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + Log.e("Http", "网络错误异常!!!!"); + } + InputStream in = connection.getInputStream(); + Log.d("Http", "connection success"); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e("Http", e.toString()); + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + }).start(); + } + + /** + * 网络通信测试请求 + * @param address + * @param callback + */ + public static void sendTestGetRequest(final String address, final HttpCallback callback) { + new Thread(new Runnable() { + HttpURLConnection connection = null; + + @Override + public void run() { + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-type", "text/html"); + connection.setRequestProperty("Accept-Charset", "utf-8"); + connection.setRequestProperty("contentType", "utf-8"); + connection.setConnectTimeout(3 * 1000); + connection.setReadTimeout(3 * 1000); + connection.setDoInput(true); + connection.setDoOutput(true); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + Log.e("Http", "网络错误异常!!!!"); + } + InputStream in = connection.getInputStream(); + Log.d("Http", "connection success"); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e("Http", e.toString()); + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + }).start(); + } + + public static void sendGetRequest_okHttp(final String address, final HttpCallback callback) { + MyApplication.getApplication().newThread(new Runnable() { + @Override + public void run() { + /* HttpURLConnection connection = null; + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-type", "text/html"); + connection.setRequestProperty("Accept-Charset", "gbk"); + connection.setRequestProperty("contentType", "gbk"); + connection.setConnectTimeout(5 * 1000); + connection.setReadTimeout(5 * 1000); + connection.setDoInput(true); + connection.setDoOutput(true); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + Log.e("Http", "网络错误异常!!!!"); + } + InputStream in = connection.getInputStream(); + Log.d("Http", "connection success"); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e("Http", e.toString()); + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + }*/ + try{ + OkHttpClient client = getOkHttpClient(); + Request request = new Request.Builder() + .addHeader("User-Agent","Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)") + .url(address) + .build(); + Response response = client.newCall(request).execute(); + callback.onFinish(response.body().byteStream()); + }catch(Exception e){ + e.printStackTrace(); + callback.onError(e); + } + } + + }); + } + + /** + * post请求 + * @param address + * @param output + * @param callback + */ + public static void sendPostRequest(final String address, final String output, final HttpCallback callback) { + MyApplication.getApplication().newThread(new Runnable() { + HttpURLConnection connection = null; + + @Override + public void run() { + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setConnectTimeout(60 * 1000); + connection.setReadTimeout(60 * 1000); + connection.setRequestProperty("user-agent", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36"); + connection.setDoInput(true); + connection.setDoOutput(true); + if (output != null) { + // 获取URLConnection对象对应的输出流 + PrintWriter out = new PrintWriter(connection.getOutputStream()); + // 发送请求参数 + out.print(output); + // flush输出流的缓冲 + out.flush(); + } + InputStream in = connection.getInputStream(); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + }); + } + + /** + * post请求 获取蓝奏云直链 + * @param address + * @param output + * @param callback + */ + public static void sendPostRequest(final String address, final String output, final HttpCallback callback, final String referer) { + new Thread(new Runnable() { + HttpURLConnection connection = null; + + @Override + public void run() { + try { + URL url = new URL(address); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setConnectTimeout(60 * 1000); + connection.setReadTimeout(60 * 1000); + connection.setRequestProperty("Referer", referer); + connection.setRequestProperty("user-agent", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36"); + connection.setDoInput(true); + connection.setDoOutput(true); + if (output != null) { + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + out.writeBytes(output); + } + InputStream in = connection.getInputStream(); + if (callback != null) { + callback.onFinish(in); + } + } catch (Exception e) { + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + }).start(); + } + + public static void sendPostRequest_okHttp(final String address, final String output, final HttpCallback callback) { + new Thread(new Runnable() { + HttpURLConnection connection = null; + @Override + public void run() { + try { + MediaType contentType = MediaType.parse("charset=utf-8"); + OkHttpClient client = new OkHttpClient(); + RequestBody body = RequestBody.create(contentType, output); + Request request = new Request.Builder() + .url(address) + .post(body) + .build(); + Response response = client.newCall(request).execute(); + callback.onFinish(response.body().byteStream()); + } catch (Exception e) { + if (callback != null) { + callback.onError(e); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + }).start(); + } + + /** + * 多文件上传请求 + * @param url + * @param files + * @param params + * @param callback + */ + public static void uploadFile(String url, ArrayList files, Map params, final HttpCallback callback){ + OkHttpClient client = new OkHttpClient(); + // form 表单形式上传 + MultipartBody.Builder requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM); + for(File file : files){ + if(file != null){ + // MediaType.parse() 里面是上传的文件类型。 + RequestBody body = RequestBody.create(MediaType.parse("*/*"), file); + String filename = file.getName(); + // 参数分别为, 请求key ,文件名称 , RequestBody + requestBody.addFormDataPart(file.getName(), file.getName(), body); + } + } + + if (params != null) { + // map 里面是请求中所需要的 key 和 value + for (Map.Entry entry : params.entrySet()) { + requestBody.addFormDataPart(valueOf(entry.getKey()), valueOf(entry.getValue())); + } + } + Request request = new Request.Builder().url(url).post(requestBody.build()).tag(MyApplication.getApplication()).build(); + // readTimeout("请求超时时间" , 时间单位); + client.newBuilder().readTimeout(5000, TimeUnit.MILLISECONDS).build().newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + callback.onError(e); + Log.i("Http" ,"onFailure"); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + String str = response.body().string(); + callback.onFinish(str); + Log.i("Http", response.message() + " , body " + str); + } else { + JsonModel jsonModel = new JsonModel(); + jsonModel.setSuccess(false); +// jsonModel.setResult(response.body().string()); + callback.onFinish(new Gson().toJson(jsonModel)); + Log.i("Http" ,response.message() + " error : body " + response.body().string()); + } + } + + }); + + } + + + /** + * 生成URL + * @param p_url + * @param params + * @return + */ + public static String makeURL(String p_url, Map params) { + if (params == null) return p_url; + StringBuilder url = new StringBuilder(p_url); + Log.d("http", p_url); + if (url.indexOf("?") < 0) + url.append('?'); + for (String name : params.keySet()) { + Log.d("http", name + "=" + params.get(name)); + url.append('&'); + url.append(name); + url.append('='); + try { + if (URLCONST.isRSA) { + if (name.equals("token")) { + url.append(valueOf(params.get(name))); + } else { + url.append(StringHelper.encode(Base64.encodeToString(RSAUtilV2.encryptByPublicKey(valueOf(params.get(name)).getBytes(), APPCONST.publicKey), Base64.DEFAULT).replace("\n", ""))); + } + } else { + url.append(valueOf(params.get(name))); + } + } catch (Exception e) { + e.printStackTrace(); + } + //不做URLEncoder处理 +// try { +// url.append(URLEncoder.encode(String.valueOf(params.get(name)), UTF_8)); +// } catch (UnsupportedEncodingException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + } + return url.toString().replace("?&", "?"); + } + + /** + * 生成URL(不加密) + * @param p_url + * @param params + * @return + */ + public static String makeURLNoRSA(String p_url, Map params) { + if (params == null) return ""; + StringBuilder url = new StringBuilder(p_url); + Log.d("http", p_url); + if (url.indexOf("?") < 0) + url.append('?'); + for (String name : params.keySet()) { + Log.d("http", name + "=" + params.get(name)); + url.append('&'); + url.append(name); + url.append('='); + url.append(valueOf(params.get(name))); + //不做URLEncoder处理 +// try { +// url.append(URLEncoder.encode(String.valueOf(params.get(name)), UTF_8)); +// } catch (UnsupportedEncodingException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + } + return url.toString().replace("?&", "?"); + } + + /** + * 生成post输出参数串 + * @param params + * @return + */ + public static String makePostOutput(Map params) { + StringBuilder output = new StringBuilder(); + Iterator it = params.keySet().iterator(); + while (true) { + String name = it.next(); + Log.d("http", name + "=" + params.get(name)); + output.append(name); + output.append('='); + try { + if (URLCONST.isRSA) { + if (name.equals("token")) { + output.append(valueOf(params.get(name))); + } else { + output.append(StringHelper.encode(Base64.encodeToString(RSAUtilV2.encryptByPublicKey(valueOf(params.get(name)).getBytes(), APPCONST.publicKey), Base64.DEFAULT).replace("\n", ""))); + } + } else { + output.append(valueOf(params.get(name))); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (!it.hasNext()) { + break; + } + output.append('&'); + //不做URLEncoder处理 +// try { +// url.append(URLEncoder.encode(String.valueOf(params.get(name)), UTF_8)); +// } catch (UnsupportedEncodingException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + } + return output.toString(); + } + + public static void uploadFileRequest(final String actionUrl, + final String[] filePaths, + final Map params, + final JsonCallback callback) { + + new Thread(new Runnable() { + @Override + public void run() { + uploadFile(actionUrl, params, filePaths, callback); + } + + }).start(); + + } + + + /** + * 测试URL可连接性 + * @param url + * @param connectionCallback + */ + public static void isURLConnection(String url, final URLConnectionCallback connectionCallback){ + sendTestGetRequest(url, new HttpCallback() { + @Override + public void onFinish(String response) { + connectionCallback.onFinish(true); + } + + @Override + public void onFinish(InputStream in) { + connectionCallback.onFinish(true); + } + + @Override + public void onFinish(Bitmap bm) { + connectionCallback.onFinish(true); + } + + @Override + public void onError(Exception e) { + connectionCallback.onFinish(false); + + } + }); + } + + + /** + * 多文件上传的方法 + * + * @param actionUrl:上传的路径 + * @param uploadFilePaths:需要上传的文件路径,数组 + * @return + */ + private static void uploadFile(String actionUrl, Map params, String[] uploadFilePaths, JsonCallback callback) { + String end = "\r\n"; + String twoHyphens = "--"; + String boundary = "*****"; + String CHARSET = "utf-8"; //设置编码 + DataOutputStream ds = null; + InputStream inputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader reader = null; + StringBuffer resultBuffer = new StringBuffer(); + String tempLine = null; + try { + Log.i("http", "开始上传文件"); + // 统一资源 + URL url = new URL(makeURL(actionUrl, params)); + // 连接类的父类,抽象类 + URLConnection urlConnection = url.openConnection(); + urlConnection.setConnectTimeout(60000); + urlConnection.setReadTimeout(60000); + // http的连接类 + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + // 设置是否从httpUrlConnection读入,默认情况下是true; + httpURLConnection.setDoInput(true); + // 设置是否向httpUrlConnection输出 + httpURLConnection.setDoOutput(true); + // Post 请求不能使用缓存 + httpURLConnection.setUseCaches(false); + // 设定请求的方法,默认是GET + httpURLConnection.setRequestMethod("POST"); + // 设置字符编码连接参数 + httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); + // 设置字符编码 + httpURLConnection.setRequestProperty("Charset", "UTF-8"); + // 设置请求内容类型 + httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + // 设置DataOutputStream + ds = new DataOutputStream(httpURLConnection.getOutputStream()); + for (int i = 0; i < uploadFilePaths.length; i++) { + String uploadFile = uploadFilePaths[i]; + String filename = uploadFile.substring(uploadFile.lastIndexOf("/") + 1); + //设置参数 + StringBuffer sb = new StringBuffer(); + sb.append(end); + sb.append(twoHyphens + boundary + end); + sb.append("Content-Disposition: form-data; " + "name=\"file" + i + "\";filename=\"" + filename + "\"" + end); + sb.append("Content-Type: application/octet-stream; charset=" + CHARSET + end); + sb.append(end); + Log.i("http", "参数:" + sb.toString()); + //写入文件数据 + ds.write(sb.toString().getBytes()); + FileInputStream fStream = new FileInputStream(uploadFile); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + int length = -1; + int total = 0; + while ((length = fStream.read(buffer)) != -1) { + ds.write(buffer, 0, length); + total += length; + } + Log.i("http", "文件的大小:" + total); + ds.writeBytes(end); + /* close streams */ + fStream.close(); + } + ds.writeBytes(twoHyphens + boundary + twoHyphens + end); + /* close streams */ + ds.flush(); + if (httpURLConnection.getResponseCode() >= 300) { + callback.onError(new Exception( + "HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode())); +// throw new Exception( +// "HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode()); + } + if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { + inputStream = httpURLConnection.getInputStream(); + inputStreamReader = new InputStreamReader(inputStream); + reader = new BufferedReader(inputStreamReader); + tempLine = null; + resultBuffer = new StringBuffer(); + while ((tempLine = reader.readLine()) != null) { + resultBuffer.append(tempLine); + resultBuffer.append("\n"); + } + Log.i("http", resultBuffer.toString()); + if (callback != null) { + Gson gson = new Gson(); + JsonModel jsonModel = gson.fromJson(resultBuffer.toString(), JsonModel.class); + if (URLCONST.isRSA && !StringHelper.isEmpty(jsonModel.getResult())) { + jsonModel.setResult(StringHelper.decode(new String(RSAUtilV2.decryptByPrivateKey(Base64.decode(jsonModel.getResult().replace("\n", ""), Base64.DEFAULT), APPCONST.privateKey)))); + } + callback.onFinish(jsonModel); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (ds != null) { + try { + ds.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (inputStreamReader != null) { + try { + inputStreamReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + + + /** + * Trust every server - dont check for any certificate + */ + public static void trustAllHosts() { + final String TAG = "trustAllHosts"; + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[] {}; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + Log.i(TAG, "checkClientTrusted"); + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + Log.i(TAG, "checkServerTrusted"); + } + } }; + + // Install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/IOUtils.java b/app/src/main/java/xyz/fycz/myreader/util/IOUtils.java new file mode 100644 index 0000000..cf998f4 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/IOUtils.java @@ -0,0 +1,23 @@ +package xyz.fycz.myreader.util; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Created by newbiechen on 17-5-11. + */ + +public class IOUtils { + + public static void close(Closeable... closeables){ + for (Closeable closeable : closeables){ + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/IdHelper.java b/app/src/main/java/xyz/fycz/myreader/util/IdHelper.java new file mode 100644 index 0000000..9999d19 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/IdHelper.java @@ -0,0 +1,14 @@ +package xyz.fycz.myreader.util; + + + +public class IdHelper { + + public static String getId(){ + java.util.Date date = new java.util.Date(); + int rand = (int)(1+ Math.random()*(25-0+1)); +// char c = (char) ('a' + rand); + return String.valueOf(date.getTime()%100000000); + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/JsonArrayToObjectArray.java b/app/src/main/java/xyz/fycz/myreader/util/JsonArrayToObjectArray.java new file mode 100644 index 0000000..7a9cda6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/JsonArrayToObjectArray.java @@ -0,0 +1,24 @@ +package xyz.fycz.myreader.util; + +import com.google.gson.Gson; + +import org.json.JSONArray; + +import java.util.ArrayList; + + + +public class JsonArrayToObjectArray { + + public static ArrayList getArray(String json, Class c) throws Exception { + ArrayList arrayList = new ArrayList<>(); + + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + arrayList.add(new Gson().fromJson(jsonArray.getString(i), c)); + } + + return arrayList; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/ListViewHeight.java b/app/src/main/java/xyz/fycz/myreader/util/ListViewHeight.java new file mode 100644 index 0000000..b2ac730 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/ListViewHeight.java @@ -0,0 +1,157 @@ +package xyz.fycz.myreader.util; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.view.View; +import android.view.ViewGroup; +import android.widget.GridView; +import android.widget.ListAdapter; +import android.widget.ListView; + +import xyz.fycz.myreader.base.BaseActivity; + +import java.lang.reflect.Field; + + + +public class ListViewHeight { + private static HandlerThread thread = new HandlerThread("listview"); + private static int totalHeight = 0; + static { + thread.start(); + } + private static Handler handler = new Handler(thread.getLooper()){ + @Override + public void handleMessage(Message msg) { + switch (msg.what){ + case 1: + + break; + } + } + }; + + /** + * 计算listview的高度 + * @param listView + * @return + */ + public static int setListViewHeightBasedOnChildren(final ListView listView) { + ListAdapter listAdapter = listView.getAdapter(); + if (listAdapter == null) { + // pre-condition + return 0; + } + int totalHeight = 0; + for (int i = 0; i < listAdapter.getCount(); i++) { + View listItem = listAdapter.getView(i, null, listView); + listItem.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); +// listItem.measure(0, 0); + totalHeight += listItem.getMeasuredHeight(); + } + final ViewGroup.LayoutParams params = listView.getLayoutParams(); + params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); +// MyApplication.getApplication().runOnUiThread(new Runnable() { +// @Override +// public void run() { + listView.setLayoutParams(params); +// } +// }); + + return totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); + } + + + + /** + * 日期gridview的高度 + * @param gridView + * @return + */ + public static int setDateGridViewHeightBasedOnChildren(GridView gridView) { + ListAdapter listAdapter = gridView.getAdapter(); + if (listAdapter == null) { + // pre-condition + return 0; + } + int totalHeight = 0; + for (int i = 0; i < (listAdapter.getCount()/7); i++) { + View listItem = listAdapter.getView(i, null, gridView); + listItem.measure(0, 0); + totalHeight += listItem.getMeasuredHeight(); + } + ViewGroup.LayoutParams params = gridView.getLayoutParams(); + params.height = totalHeight + (1 * (listAdapter.getCount()/7 - 1)); + gridView.setLayoutParams(params); + return totalHeight + (1 * (listAdapter.getCount()/7 - 1)); + } + + /** + * 计算gridview的高度 + * @param gridView + */ + public static void setGridViewHeightBasedOnChildren(GridView gridView) { + // 获取GridView对应的Adapter + ListAdapter listAdapter = gridView.getAdapter(); + if (listAdapter == null) { + return; + } + int rows; + int columnWidths; + int columns=0; + int horizontalBorderHeight = 0; + int verticalBorderHeight = 0; + + Class clazz=gridView.getClass(); + try { + //利用反射,取得横向分割线高度 + Field vertical=clazz.getDeclaredField("mVerticalSpacing"); + vertical.setAccessible(true); + verticalBorderHeight =(Integer)vertical.get(gridView); + + //利用反射,取得纵向分割线高度 + Field horizontalSpacing=clazz.getDeclaredField("mRequestedHorizontalSpacing"); + horizontalSpacing.setAccessible(true); + horizontalBorderHeight=(Integer)horizontalSpacing.get(gridView); + + //利用反射,取得每行显示的个数 + Field column=clazz.getDeclaredField("mRequestedNumColumns"); + column.setAccessible(true); + columns=(Integer)column.get(gridView); + + if(columns == -1 || columns == 0){ + Field columnWidth = clazz.getDeclaredField("mRequestedColumnWidth"); + columnWidth.setAccessible(true); + columnWidths = (Integer)columnWidth.get(gridView); + int pad = gridView.getPaddingLeft() * 2; + int width = BaseActivity.width - pad * 2; + columns = width / (columnWidths /*+ horizontalBorderHeight*/); + if(horizontalBorderHeight * (columns - 1) + columnWidths * columns > width){ + columns --; + } + } + + } catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + } + //判断数据总数除以每行个数是否整除。不能整除代表有多余,需要加一行 + if(listAdapter.getCount()%columns > 0){ + rows=listAdapter.getCount()/columns+1; + }else { + rows=listAdapter.getCount()/columns; + } + int totalHeight = 0; + for (int i = 0; i < rows; i++) { //只计算每项高度*行数 + View listItem = listAdapter.getView(i, null, gridView); + listItem.measure(0, 0); // 计算子项View 的宽高 + totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度 + } + ViewGroup.LayoutParams params = gridView.getLayoutParams(); + params.height = totalHeight + gridView.getPaddingTop() + + gridView.getPaddingBottom() + verticalBorderHeight * (rows-1);//最后加上分割线总高度 + gridView.setLayoutParams(params); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/Lunar.java b/app/src/main/java/xyz/fycz/myreader/util/Lunar.java new file mode 100644 index 0000000..138b6c8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/Lunar.java @@ -0,0 +1,266 @@ +package xyz.fycz.myreader.util; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + + + +public class Lunar +{ + private int year; + private int month; + private int day; + private boolean leap; + final static String chineseNumber[] = + { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二" }; + final static String Big_Or_Small[] = + { "大", "小", "大", "小", "大", "小", "大", "大", "小", "大", "小", "大" }; + private String[] LunarHolDayName = + { "小寒", "大寒", "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", + "小暑", "大暑", "立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", + "冬至" }; + + static SimpleDateFormat chineseDateFormat = new SimpleDateFormat(" yyyy年MM月dd日 "); + final static long[] lunarInfo = new long[] + { 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, + 0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, + 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, + 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, + 0x04970, 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, + 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, + 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, + 0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, + 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, + 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, + 0x0b5a0, 0x195a6, 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, + 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, + 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, + 0x092e0, 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, + 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, + 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, + 0x0d260, 0x0ea65, 0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, + 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, + 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, + 0x06d20, 0x0ada0 }; + + // ====== 传回农历 y年的总天数 + final private static int yearDays(int y) + { + int i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) + { + if ((lunarInfo[y - 1900] & i) != 0) + sum += 1; + } + return (sum + leapDays(y)); + } + + // ====== 传回农历 y年闰月的天数 + final private static int leapDays(int y) + { + if (leapMonth(y) != 0) + { + if ((lunarInfo[y - 1900] & 0x10000) != 0) + return 30; + else + return 29; + } + else + return 0; + } + + // ====== 传回农历 y年闰哪个月 1-12 , 没闰传回 0 + final private static int leapMonth(int y) + { + return (int) (lunarInfo[y - 1900] & 0xf); + } + + // ====== 传回农历 y年m月的总天数 + final private static int monthDays(int y, int m) + { + if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0) + return 29; + else + return 30; + } + + // ====== 传回农历 y年的生肖 + final public String animalsYear() + { + final String[] Animals = new String[] + { "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" }; + return Animals[(year - 4) % 12]; + } + + // ====== 传入 月日的offset 传回干支, 0=甲子 + final private static String cyclicalm(int num) + { + final String[] Gan = new String[] + { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" }; + final String[] Zhi = new String[] + { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" }; + return (Gan[num % 10] + Zhi[num % 12]); + } + + // ====== 传入 offset 传回干支, 0=甲子 + final public String cyclical() + { + int num = year - 1900 + 36; + return (cyclicalm(num)); + } + + /** */ + /** + * 传出y年m月d日对应的农历. yearCyl3:农历年与1864的相差数 ? monCyl4:从1900年1月31日以来,闰月数 + * dayCyl5:与1900年1月31日相差的天数,再加40 ? + * + * @param cal + * @return + */ + public Lunar(Calendar cal) + { + // cal.add(cal.get(Calendar.DAY_OF_MONTH),1); + @SuppressWarnings(" unused ") + int yearCyl, monCyl, dayCyl; + int leapMonth = 0; + Date baseDate = null; + try + { + baseDate = chineseDateFormat.parse(" 1900年1月31日 "); + } + catch (ParseException e) + { + e.printStackTrace(); // To change body of catch statement use + // Options | File Templates. + } + + // 求出和1900年1月31日相差的天数 + int offset = (int) ((cal.getTime().getTime() - baseDate.getTime()) / 86400000L); + dayCyl = offset + 40; + monCyl = 14; + + // 用offset减去每农历年的天数 + // 计算当天是农历第几天 + // i最终结果是农历的年份 + // offset是当年的第几天 + int iYear, daysOfYear = 0; + for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) + { + daysOfYear = yearDays(iYear); + offset -= daysOfYear; + monCyl += 12; + } + if (offset < 0) + { + offset += daysOfYear; + iYear--; + monCyl -= 12; + } + // 农历年份 + year = iYear; + + yearCyl = iYear - 1864; + leapMonth = leapMonth(iYear); // 闰哪个月,1-12 + leap = false; + + // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天 + int iMonth, daysOfMonth = 0; + for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) + { + // 闰月 + if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) + { + --iMonth; + leap = true; + daysOfMonth = leapDays(year); + } + else + daysOfMonth = monthDays(year, iMonth); + + offset -= daysOfMonth; + // 解除闰月 + if (leap && iMonth == (leapMonth + 1)) + leap = false; + if (!leap) + monCyl++; + } + // offset为0时,并且刚才计算的月份是闰月,要校正 + if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) + { + if (leap) + { + leap = false; + } + else + { + leap = true; + --iMonth; + --monCyl; + } + } + // offset小于0时,也要校正 + if (offset < 0) + { + offset += daysOfMonth; + --iMonth; + --monCyl; + } + month = iMonth; + day = offset + 1; + } + + public static String getChinaDayString(int day) + { + String chineseTen[] = + { "初", "十", "廿", "卅" }; + int n = day % 10 == 0 ? 9 : day % 10 - 1; + if (day > 30) + return ""; + if (day == 10) + return "初十"; + else + return chineseTen[day / 10] + chineseNumber[n]; + } + + public String toString() + { + return /* cyclical() + "年" + */(leap ? "闰" : "") + + chineseNumber[month - 1] + "月" + getChinaDayString(day); + } + + public String numeric_md() + {// 返回阿拉伯数字的阴历日期 + String temp_day; + String temp_mon; + temp_mon = month < 10 ? "0" + month : "" + month; + temp_day = day < 10 ? "0" + day : "" + day; + + return temp_mon + temp_day; + } + + public String get_month() + {// 返回阴历的月份 + return chineseNumber[month - 1]; + } + + public String get_date(int year, int month, int date) + { + /* // 返回阴历的天 + LunarCalendar lunarCalendar = LunarCalendar.getInstance(); + String str = lunarCalendar.CalculateLunarCalendar(year,month,date); + if(StringHelper.isEmpty(str)){ + str = getChinaDayString(day); + }*/ + return getChinaDayString(day); + } + + public String get_Big_Or_Small() + {// 返回的月份的大或小 + return Big_Or_Small[month - 1]; + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/LunarCalendar.java b/app/src/main/java/xyz/fycz/myreader/util/LunarCalendar.java new file mode 100644 index 0000000..995427d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/LunarCalendar.java @@ -0,0 +1,326 @@ +package xyz.fycz.myreader.util; + + + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +public class LunarCalendar { + + private int lyear; + + private int lmonth; + + private int lday; + + private boolean leap; + + private String solarTerms = ""; + + private int yearCyl, monCyl, dayCyl; + + private String solarFestival = ""; + + private String lunarFestival = ""; + + private Calendar baseDate = Calendar.getInstance(); + + private Calendar offDate = Calendar.getInstance(); + + private SimpleDateFormat chineseDateFormat = new SimpleDateFormat( + "yyyy年MM月dd日"); + + final static long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570, + 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, + 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, + 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566, + 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, + 0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, + 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550, + 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, + 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, + 0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0, + 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6, + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, + 0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3, + 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960, + 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, + 0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, + 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0, + 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, + 0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, + 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, + 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0}; + + final static String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", + "庚", "辛", "壬", "癸"}; + + final static String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", + "午", "未", "申", "酉", "戌", "亥"}; + + final static String[] Animals = new String[]{"鼠", "牛", "虎", "兔", "龙", + "蛇", "马", "羊", "猴", "鸡", "狗", "猪"}; + + final static String[] SolarTerm = new String[]{"小寒", "大寒", "立春", "雨水", + "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", "立秋", + "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"}; + final static long[] STermInfo = new long[]{0, 21208, 42467, 63836, 85337, + 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, + 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, + 462224, 483532, 504758}; + + final static String chineseMonthNumber[] = {"正", "二", "三", "四", "五", "六", + "七", "八", "九", "十", "冬", "腊"}; + + final static String[] sFtv = new String[]{"0101*元旦", "0214 情人节", + "0308 妇女节", "0312 植树节", /*"0314 国际警察日", "0315 消费者权益日", "0323 世界气象日",*/ + "0401 愚人节", /*"0407 世界卫生日",*/ "0501*劳动节", "0504 青年节", /*"0508 红十字日",*/ + "0512 护士节", /*"0515 国际家庭日", "0517 世界电信日", "0519 全国助残日", "0531 世界无烟日",*/ + "0601 儿童节", /*"0605 世界环境日", "0606 全国爱眼日", "0623 奥林匹克日", "0625 全国土地日",*/ + /*"0626 反毒品日",*/ "0701 建党节", /*"0707 抗战纪念日", "0711 世界人口日",*/ "0801 建军节", + /* "0908 国际扫盲日", "0909 毛xx逝世纪念",*/ "0910 教师节", /*"0917 国际和平日",*/ + /* "0920 国际爱牙日", "0922 国际聋人节", "0927 世界旅游日", "0928 孔子诞辰",*/ "1001*国庆节", + /* "1004 世界动物日", "1006 老人节", "1007 国际住房日", "1009 世界邮政日", "1015 国际盲人节",*/ + /* "1016 世界粮食日", "1024 联合国日", */"1031 万圣节",/* "1108 中国记者日", "1109 消防宣传日",*/ + /* "1112 孙中山诞辰", "1114 世界糖尿病日", "1117 国际大学生节",*/ "1128 感恩节", + /* "1201 世界艾滋病日", "1203 世界残疾人日", "1209 世界足球日", "1220 澳门回归",*/ + "1225 圣诞节", /*"1226 毛xx诞辰"*/}; + + final static String[] lFtv = {"0101*春节", "0115 元宵", "0505 端午", + "0707 七夕", "0815 中秋", "0909 重阳", "1208 腊八", "1223 小年", + "0100*除夕"}; + + final static String[] wFtv = {"0521 母亲节", "0631 父亲节"};//每年6月第3个星期日是父亲节,5月的第2个星期日是母亲节 + + //星期日是一个周的第1天第3个星期日也就是第3个完整周的第一天 + + // + private LunarCalendar() { + baseDate.setMinimalDaysInFirstWeek(7);//设置一个月的第一个周是一个完整周 + + } + + final private static int lYearDays(int y)//====== 传回农历 y年的总天数 + { + int i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) { + if ((lunarInfo[y - 1900] & i) != 0) + sum += 1; + } + return (sum + leapDays(y)); + } + + final private static int leapDays(int y)//====== 传回农历 y年闰月的天数 + { + if (leapMonth(y) != 0) { + if ((lunarInfo[y - 1900] & 0x10000) != 0) + return 30; + else + return 29; + } else + return 0; + } + + final private static int leapMonth(int y)//====== 传回农历 y年闰哪个月 1-12 , 没闰传回 0 + { + return (int) (lunarInfo[y - 1900] & 0xf); + } + + final public static int monthDays(int y, int m)//====== 传回农历 y年m月的总天数 + { + if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0) + return 29; + else + return 30; + } + + final private static String AnimalsYear(int y)//====== 传回农历 y年的生肖 + { + + return Animals[(y - 4) % 12]; + } + + final private static String cyclical(int num)//====== 传入 的offset 传回干支, + // 0=甲子 + { + + return (Gan[num % 10] + Zhi[num % 12]); + } + + // ===== 某年的第n个节气为几日(从0小寒起算) + final private int sTerm(int y, int n) { + + offDate.set(1900, 0, 6, 2, 5, 0); + long temp = offDate.getTime().getTime(); + offDate + .setTime(new Date( + (long) ((31556925974.7 * (y - 1900) + STermInfo[n] * 60000L) + temp))); + + return offDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * 传出y年m月d日对应的农历. + */ + public String CalculateLunarCalendar(int y, int m, int d) { + + int leapMonth = 0; + + try { + baseDate.setTime(chineseDateFormat.parse("1900年1月31日")); + + } catch (ParseException e) { + e.printStackTrace(); + } + long base = baseDate.getTimeInMillis(); + try { + baseDate.setTime(chineseDateFormat.parse(y + "年" + m + "月" + d + + "日")); + + } catch (ParseException e) { + e.printStackTrace(); + } + long obj = baseDate.getTimeInMillis(); + + + int offset = (int) ((obj - base) / 86400000L); + //System.out.println(offset); + //求出和1900年1月31日相差的天数 + + dayCyl = offset + 40;//干支天 + monCyl = 14;//干支月 + + //用offset减去每农历年的天数 + // 计算当天是农历第几天 + //i最终结果是农历的年份 + //offset是当年的第几天 + int iYear, daysOfYear = 0; + for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) { + daysOfYear = lYearDays(iYear); + offset -= daysOfYear; + monCyl += 12; + } + if (offset < 0) { + offset += daysOfYear; + iYear--; + monCyl -= 12; + } + //农历年份 + lyear = iYear; + + yearCyl = iYear - 1864;//***********干支年**********// + + leapMonth = leapMonth(iYear); //闰哪个月,1-12 + leap = false; + + //用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天 + int iMonth, daysOfMonth = 0; + for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) { + //闰月 + if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) { + --iMonth; + leap = true; + daysOfMonth = leapDays(iYear); + } else + daysOfMonth = monthDays(iYear, iMonth); + + offset -= daysOfMonth; + //解除闰月 + if (leap && iMonth == (leapMonth + 1)) + leap = false; + if (!leap) + monCyl++; + } + //offset为0时,并且刚才计算的月份是闰月,要校正 + if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) { + if (leap) { + leap = false; + } else { + leap = true; + --iMonth; + --monCyl; + } + } + //offset小于0时,也要校正 + if (offset < 0) { + offset += daysOfMonth; + --iMonth; + --monCyl; + } + lmonth = iMonth; + lday = offset + 1; + + //******************计算节气**********// + + if (d == sTerm(y, (m - 1) * 2)) + solarTerms = SolarTerm[(m - 1) * 2]; + else if (d == sTerm(y, (m - 1) * 2 + 1)) + solarTerms = SolarTerm[(m - 1) * 2 + 1]; + else + solarTerms = ""; + + //计算公历节日 + this.solarFestival = ""; + for (int i = 0; i < sFtv.length; i++) { + if (Integer.parseInt(sFtv[i].substring(0, 2)) == m + && Integer.parseInt(sFtv[i].substring(2, 4)) == d) { + solarFestival = sFtv[i].substring(5); + break; + } + } + //计算农历节日 + this.lunarFestival = ""; + for (int i = 0; i < lFtv.length; i++) { + if (Integer.parseInt(lFtv[i].substring(0, 2)) == lmonth + && Integer.parseInt(lFtv[i].substring(2, 4)) == lday) { + lunarFestival = lFtv[i].substring(5); + break; + } + } + //计算月周节日 + + // System.out.println(baseDate.get(Calendar.WEEK_OF_MONTH) + "" + // + baseDate.get(Calendar.DAY_OF_WEEK)); + + for (int i = 0; i < wFtv.length; i++) { + if (Integer.parseInt(wFtv[i].substring(0, 2)) == m + && Integer.parseInt(wFtv[i].substring(2, 3)) == baseDate + .get(Calendar.WEEK_OF_MONTH) + && Integer.parseInt(wFtv[i].substring(3, 4)) == baseDate + .get(Calendar.DAY_OF_WEEK)) { + solarFestival += wFtv[i].substring(5); + } + } + if(!StringHelper.isEmpty(lunarFestival)){ + return lunarFestival; + } + if(!StringHelper.isEmpty(solarFestival)){ + return solarFestival; + } + if(!StringHelper.isEmpty(solarTerms)){ + return solarTerms; + } + return ""; + + } + + //set方法 + public void set(int y, int m, int d) { + + CalculateLunarCalendar(y, m, d); + } + + public void set(Calendar cal) { + + CalculateLunarCalendar(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)); + } + + // //get方法组 + public static LunarCalendar getInstance() { + + return new LunarCalendar(); + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/NotifyHelper.java b/app/src/main/java/xyz/fycz/myreader/util/NotifyHelper.java new file mode 100644 index 0000000..d924ba6 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/NotifyHelper.java @@ -0,0 +1,69 @@ +package xyz.fycz.myreader.util; + +import android.app.NotificationManager; +import android.content.Context; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + + +public class NotifyHelper { + + private static NotificationManager mNotificationManager; + private static Map mNotifyId = new HashMap(); + private static ArrayList mCurrentConvIds = new ArrayList(); + + public static void init(Context context){ + mNotificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); + + } + + public static int createNotifyId(String convId) { + if (!mNotifyId.containsKey(convId)) { + for (int i = 0; i < 1000; i++) { + if (!mNotifyId.containsValue(i)) { + mNotifyId.put(convId, i); + return i; + } + } + }else { + return (int)mNotifyId.get(convId); + } + return -1; + } + + public static void closeNotification(String convId){ + //关闭通知 + int notifyId = getNotifyId(convId); + if(notifyId != -1){ + mNotificationManager.cancel(notifyId); + mNotifyId.remove(convId); + } + + } + + public static int getNotifyId(String convId){ + if(mNotifyId.get(convId) == null){ + return -1; + }else { + return (int)mNotifyId.get(convId); + } + + } + + public static boolean isOpenConv(String convId){ + return mCurrentConvIds.contains(convId); + } + + public static void addCurrentConv(String convId){ + if(!mCurrentConvIds.contains(convId)){ + mCurrentConvIds.add(convId); + } + } + + public static void removeCurrenConv(String convId){ + mCurrentConvIds.remove(convId); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/OpenFileHelper.java b/app/src/main/java/xyz/fycz/myreader/util/OpenFileHelper.java new file mode 100644 index 0000000..03305d9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/OpenFileHelper.java @@ -0,0 +1,131 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.content.Intent; + +import java.io.File; + + + +public class OpenFileHelper { + private final static String[][] MIME_MapTable={ +//{后缀名,MIME类型} + {".3gp", "video/3gpp"}, + {".apk", "application/vnd.android.package-archive"}, + {".asf", "video/x-ms-asf"}, + {".avi", "video/x-msvideo"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".class", "application/octet-stream"}, + {".conf", "text/plain"}, + {".cpp", "text/plain"}, + {".doc", "application/msword"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".exe", "application/octet-stream"}, + {".gif", "image/gif"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".jar", "application/java-archive"}, + {".java", "text/plain"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/x-JavaScript"}, + {".log", "text/plain"}, + {".m3u", "audio/x-mpegurl"}, + {".m4a", "audio/mp4a-latm"}, + {".m4b", "audio/mp4a-latm"}, + {".m4p", "audio/mp4a-latm"}, + {".m4u", "video/vnd.mpegurl"}, + {".m4v", "video/x-m4v"}, + {".mov", "video/quicktime"}, + {".mp2", "audio/x-mpeg"}, + {".mp3", "audio/x-mpeg"}, + {".mp4", "video/mp4"}, + {".mpc", "application/vnd.mpohun.certificate"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpg", "video/mpeg"}, + {".mpg4", "video/mp4"}, + {".mpga", "audio/mpeg"}, + {".msg", "application/vnd.ms-outlook"}, + {".ogg", "audio/ogg"}, + {".pdf", "application/pdf"}, + {".png", "image/png"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prop", "text/plain"}, + {".rc", "text/plain"}, + {".rmvb", "audio/x-pn-realaudio"}, + {".rtf", "application/rtf"}, + {".sh", "text/plain"}, + {".tar", "application/x-tar"}, + {".tgz", "application/x-compressed"}, + {".txt", "text/plain"}, + {".wav", "audio/x-wav"}, + {".wma", "audio/x-ms-wma"}, + {".wmv", "audio/x-ms-wmv"}, + {".wps", "application/vnd.ms-works"}, + {".xml", "text/plain"}, + {".z", "application/x-compress"}, + {".zip", "application/x-zip-compressed"}, + {"", "*/*"} + }; + + /** + * 打开文件 + * @param file + */ + public static void openFile(Context context,File file){ + + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + //设置intent的Action属性 + intent.setAction(Intent.ACTION_VIEW); + //获取文件file的MIME类型 + String type = getMIMEType(file); + //设置intent的data和Type属性。 + intent.setDataAndType(/*uri*/UriFileUtil.getUriForFile(context,file), type); + //跳转 + try { + context.startActivity(intent); //这里最好try一下,有可能会报错。 比如说你的MIME类型是打开邮箱,但是你手机里面没装邮箱客户端,就会报错。 + }catch (Exception e){ + e.printStackTrace(); + TextHelper.showText("没有安装相应的文件读取工具或者文件错误"); + } + + } + + + + /** + * 根据文件后缀名获得对应的MIME类型。 + * @param file + */ + private static String getMIMEType(File file) { + + String type="*/*"; + String fName = file.getName(); +//获取后缀名前的分隔符"."在fName中的位置。 + int dotIndex = fName.lastIndexOf("."); + if(dotIndex < 0){ + return type; + } +/* 获取文件的后缀名*/ + String end=fName.substring(dotIndex,fName.length()).toLowerCase(); + if(end=="")return type; +//在MIME和文件类型的匹配表中找到对应的MIME类型。 + for(int i=0;i= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.READ_PHONE_STATE}, 1); + TextHelper.showText("当前应用未拥读取设备状态权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.READ_PHONE_STATE}, 1); + TextHelper.showText("当前应用未拥读取设备状态权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + /** + * 蓝牙设备权限 + * @param context + * @return + */ + public static boolean isACCESS_COARSE_LOCATION(Context context) { + boolean permission = false; + if (Build.VERSION.SDK_INT >= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1); + TextHelper.showText("当前应用未拥有蓝牙设备使用权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1); + TextHelper.showText("当前应用未拥有蓝牙设备使用权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + + /** + * 文件读写权限 + * @param context + * @return + */ + public static boolean isWRPermission(Context context) { + boolean permission = false; + if (Build.VERSION.SDK_INT >= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + TextHelper.showText("当前应用未拥有存储设备读写权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + TextHelper.showText("当前应用未拥有存储设备读写权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + /** + * 声音设备权限 + * @param context + * @return + */ + public static boolean isAudioPermission(Context context) { + boolean permission = false; + if (Build.VERSION.SDK_INT >= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.RECORD_AUDIO}, 1); + TextHelper.showText("当前应用未拥有音频录制权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.RECORD_AUDIO}, 1); + TextHelper.showText("当前应用未拥有音频录制权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + /** + * 摄像头权限 + * @param context + * @return + */ + public static boolean isCameraPermission(Context context) { + boolean permission = false; + if (Build.VERSION.SDK_INT >= 23) { + int checkReadPhoneStatePermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA); + if (checkReadPhoneStatePermission != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.CAMERA}, 1); + TextHelper.showText("当前应用未拥有调用摄像头权限"); + } else if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + // 弹出对话框接收权限 + ActivityCompat.requestPermissions((AppCompatActivity) context, new String[]{android.Manifest.permission.CAMERA}, 1); + TextHelper.showText("当前应用未拥有调用摄像头权限"); + } else { + permission = true; + } + } else { + permission = true; + } + return permission; + } + + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/PermissionsChecker.java b/app/src/main/java/xyz/fycz/myreader/util/PermissionsChecker.java new file mode 100644 index 0000000..ee78344 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/PermissionsChecker.java @@ -0,0 +1,33 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.content.pm.PackageManager; +import androidx.core.content.ContextCompat; + +/** + * Created by newbiechen on 2017/10/8. + */ + +public class PermissionsChecker { + private final Context mContext; + + public PermissionsChecker(Context context) { + mContext = context.getApplicationContext(); + } + + // 判断权限集合 + public boolean lacksPermissions(String... permissions) { + for (String permission : permissions) { + if (lacksPermission(permission)) { + return true; + } + } + return false; + } + + // 判断是否缺少权限 + private boolean lacksPermission(String permission) { + return ContextCompat.checkSelfPermission(mContext, permission) == + PackageManager.PERMISSION_DENIED; + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/PingUtil.java b/app/src/main/java/xyz/fycz/myreader/util/PingUtil.java new file mode 100644 index 0000000..07fffc5 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/PingUtil.java @@ -0,0 +1,31 @@ +package xyz.fycz.myreader.util; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + + + +public class PingUtil { + + public static boolean ping(String host, int port) { + if (port == 0) port = 80; + + Socket connect = new Socket(); + try { + connect.connect(new InetSocketAddress(host, port), 10 * 1000); + return connect.isConnected(); + } catch (IOException e) { + e.printStackTrace(); + }finally{ + try { + connect.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return false; + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/QRCodeUtil.java b/app/src/main/java/xyz/fycz/myreader/util/QRCodeUtil.java new file mode 100644 index 0000000..54e5be4 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/QRCodeUtil.java @@ -0,0 +1,123 @@ +package xyz.fycz.myreader.util; + + +import android.graphics.Bitmap; +import android.graphics.Canvas; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import xyz.fycz.myreader.application.MyApplication; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * 二维码生成工具类 + */ +public class QRCodeUtil { + /** + * 生成二维码Bitmap + * + * @param content 内容 + * @param widthPix 图片宽度 + * @param heightPix 图片高度 + * @param logoBm 二维码中心的Logo图标(可以为null) + * @param filePath 用于存储二维码图片的文件路径 + * @return 生成二维码及保存文件是否成功 + */ + public static boolean createQRImage(String content, int widthPix, int heightPix, Bitmap logoBm, String filePath) { + try { + if (content == null || "".equals(content)) { + return false; + } + + //配置参数 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); + //容错级别 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + //设置空白边距的宽度 +// hints.put(EncodeHintType.MARGIN, 2); //default is 4 + + // 图像数据转换,使用了矩阵转换 + BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, widthPix, heightPix, hints); + int[] pixels = new int[widthPix * heightPix]; + // 下面这里按照二维码的算法,逐个生成二维码的图片, + // 两个for循环是图片横列扫描的结果 + for (int y = 0; y < heightPix; y++) { + for (int x = 0; x < widthPix; x++) { + if (bitMatrix.get(x, y)) { + pixels[y * widthPix + x] = 0xff000000; + } else { + pixels[y * widthPix + x] = 0xffffffff; + } + } + } + + // 生成二维码图片的格式,使用ARGB_8888 + Bitmap bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix); + + if (logoBm != null) { + bitmap = addLogo(bitmap, logoBm); + } + + //必须使用compress方法将bitmap保存到文件中再进行读取。直接返回的bitmap是没有任何压缩的,内存消耗巨大! + FileOutputStream fos = MyApplication.getApplication().openFileOutput(filePath, MyApplication.getApplication().MODE_PRIVATE); + return bitmap != null && bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); + } catch (WriterException | IOException e) { + e.printStackTrace(); + } + return false; + } + + /** + * 在二维码中间添加Logo图案 + */ + private static Bitmap addLogo(Bitmap src, Bitmap logo) { + if (src == null) { + return null; + } + + if (logo == null) { + return src; + } + + //获取图片的宽高 + int srcWidth = src.getWidth(); + int srcHeight = src.getHeight(); + int logoWidth = logo.getWidth(); + int logoHeight = logo.getHeight(); + + if (srcWidth == 0 || srcHeight == 0) { + return null; + } + + if (logoWidth == 0 || logoHeight == 0) { + return src; + } + + //logo大小为二维码整体大小的1/5 + float scaleFactor = srcWidth * 1.0f / 5 / logoWidth; + Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888); + try { + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(src, 0, 0, null); + canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2); + canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null); + canvas.save(); + canvas.restore(); + } catch (Exception e) { + bitmap = null; + e.getStackTrace(); + } + return bitmap; + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/RSACoder.java b/app/src/main/java/xyz/fycz/myreader/util/RSACoder.java new file mode 100644 index 0000000..5a979ba --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/RSACoder.java @@ -0,0 +1,289 @@ +package xyz.fycz.myreader.util; + +import android.util.Base64; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + +/** + *
*RSA安全编码组件 + * + */ +public abstract class RSACoder extends Coder { + public static final String KEY_ALGORITHM = "RSA"; + public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; + + private static final String PUBLIC_KEY = "RSAPublicKey"; + private static final String PRIVATE_KEY = "RSAPrivatekey"; + + /** + * 用私钥对信息生成数字签名 + * @param data 加密数据 + * @param privateKey 私钥 + * @return + * @throws Exception + */ + public static String sign(byte[] data, String privateKey) throws Exception { + //解密由base64编码的私钥 + byte[] keyBytes = decryptBASE64(privateKey); + + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes); + + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + + //取私钥对象 + PrivateKey pKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + + //用私钥生成数字签名 + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(pKey); + signature.update(data); + + return encryptBASE64(signature.sign()); + } + + /** + * 校验数字签名 + * @param data 加密数据 + * @param publicKey 公钥 + * @param sign 数字签名 + * @return + * @throws Exception + */ + public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { + + //解密有base64编码的公钥 + byte[] keyBytes = decryptBASE64(publicKey); + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + + //取公钥对象 + PublicKey pKey = keyFactory.generatePublic(keySpec); + + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initVerify(pKey); + signature.update(data); + //验证签名是否正常 + return signature.verify(decryptBASE64(sign)); + } + + /** + * 解密 + * 用私钥解密 + * @param data 加密数据 + * @param key + * @return + * @throws Exception + */ + public static byte[] decryptPrivateKey(byte[] data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + + //取得私钥 + PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); + Key pKey = factory.generatePrivate(encodedKeySpec); + + //对数据解密 + Cipher cipher = Cipher.getInstance(factory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, pKey); + + return cipher.doFinal(data); + } + + /** + * 用公钥解密 + * @param data + * @param key + * @return + * @throws Exception + */ + public static byte[] decryptByPublicKey( byte[] data, String key) throws Exception { + + //解密 + byte[] keyBytes = decryptBASE64(key); + + //取得公钥 + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key pKey = keyFactory.generatePublic(keySpec); + + //对数据解密 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, pKey); + + return cipher.doFinal(data); + } + + /** + * 用公钥加密 + * @param data + * @param key + * @return + * @throws Exception + */ + public static byte[] encryptByPublicKey( byte[] data, String key) throws Exception { + + byte[] keyBytes = decryptBASE64(key); + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key pKey = keyFactory.generatePublic(keySpec); + + + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, pKey); + + return cipher.doFinal(data); + } + + /** + * 用私钥加密 + * @param data + * @param key + * @return + * @throws Exception + */ + public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { + + byte[] keyBytes = decryptBASE64(key); + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateKey = keyFactory.generatePrivate(keySpec); + + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + + return cipher.doFinal(data); + } + + /** + * 取得私钥 + * @param keyMap + * @return + * @throws Exception + */ + public static String getPrivateKey(Map keyMap) throws Exception { + + Key key = (Key) keyMap.get(PRIVATE_KEY); + + return encryptBASE64(key.getEncoded()); + } + + /** + * 取得公钥 + * @param keyMap + * @return + * @throws Exception + */ + public static String getPublicKey(Map keyMap) throws Exception { + + Key key = (Key) keyMap.get(PUBLIC_KEY); + + return encryptBASE64(key.getEncoded()); + } + /** + * 初始化密钥 + * @return + * @throws Exception + */ + public static Map initKey() throws Exception { + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPairGenerator.initialize(1024); + + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + //公钥 + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + + //私钥 + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + + Map keyMap = new HashMap(2); + keyMap.put(PRIVATE_KEY, privateKey); + keyMap.put(PUBLIC_KEY, publicKey); + return keyMap; + } + + public static void test() throws Exception + { + Map keyMap = RSAUtilV2.genKeyPair(); + + String publicKey = RSAUtilV2.getPublicKey(keyMap).replace("\n",""); + String privateKey = RSAUtilV2.getPrivateKey(keyMap).replace("\n",""); + + System.out.println("公钥:\n" + publicKey); + System.out.println("私钥:\n" + privateKey); + System.out.println("公钥加密——私钥解密"); + String inputStr = "{\\\"mobilePhone\\\":\\\"18577113435\\\",\\\"officePhone\\\":\\\"\\\",\\\"email\\\":\\\"test1@dwb.gxi.gov.cn\\\",\\\"token\\\":\\\"d87a6879-f5d3-4327-a5a5-43ed3ec31c8d\\\",\\\"realNameSzm\\\":\\\"c\\\",\\\"realNamePy\\\":\\\"ceshi1\\\",\\\"userState\\\":\\\"1\\\",\\\"sex\\\":\\\"male\\\",\\\"duty\\\":\\\"\\\",\\\"emailPwd\\\":\\\"test1@123456\\\",\\\"jurisdiction\\\":{\\\"meeting\\\":true,\\\"file\\\":true,\\\"supervise\\\":true,\\\"notice\\\":true,\\\"calendar\\\":true,\\\"document\\\":false,\\\"im\\\":true,\\\"email\\\":true,\\\"contacts\\\":true,\\\"commission\\\":false,\\\"meetingCount\\\":true,\\\"interview\\\":true},\\\"userName\\\":\\\"test01\\\",\\\"realName\\\":\\\"测试1\\\",\\\"userKey\\\":\\\"会议报名\\\",\\\"password\\\":\\\"e10adc3949ba59abbe56e057f20f883e\\\",\\\"activitiSync\\\":1,\\\"status\\\":1,\\\"currentDepartId\\\":\\\"8a8afb90582942f501582944770d0028\\\",\\\"currentDepartName\\\":\\\"文电处\\\",\\\"id\\\":\\\"40287d81585174ba0158523e3db20010\\\"}"; + byte[] data = inputStr.getBytes(); + + byte[] encodedData = RSAUtilV2.encryptByPublicKey(data, publicKey); + + String enconde = Base64.encodeToString(encodedData, Base64.DEFAULT); + + + + byte[] decodedData = RSAUtilV2.decryptByPrivateKey(Base64.decode(enconde, Base64.DEFAULT), + privateKey); + + String outputStr = new String(decodedData); + System.out.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr); + } + + public static void test2() throws Exception + { + Map keyMap = RSACoder.initKey(); + + String publicKey = RSACoder.getPublicKey(keyMap); + String privateKey = RSACoder.getPrivateKey(keyMap); + + System.out.println("公钥:\n" + publicKey); + System.out.println("私钥:\n" + privateKey); + + System.err.println("私钥加密——公钥解密"); + String inputStr = "sign"; + byte[] data = inputStr.getBytes(); + + byte[] encodedData = RSACoder.encryptByPrivateKey(data, privateKey); + + byte[] decodedData = RSACoder + .decryptByPublicKey(encodedData, publicKey); + + String outputStr = new String(decodedData); + System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr); + + + System.err.println("私钥签名——公钥验证签名"); + // 产生签名 + String sign = RSACoder.sign(encodedData, privateKey); + System.err.println("签名:\r" + sign); + + // 验证签名 + boolean status = RSACoder.verify(encodedData, publicKey, sign); + System.err.println("状态:\r" + status); + + } + + /* public static void main(String[] args) throws Exception { + + + //test2(); + test(); + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/RSAUtilV2.java b/app/src/main/java/xyz/fycz/myreader/util/RSAUtilV2.java new file mode 100644 index 0000000..bfaf807 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/RSAUtilV2.java @@ -0,0 +1,478 @@ +package xyz.fycz.myreader.util; + +/** + * Created by zhao on 2017/1/20. + */ + + +import java.io.ByteArrayOutputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + + + +/** */ + +/** + *

+ * RSA公钥/私钥/签名工具包 v2 UPDATE BY ZYL + *

+ *

+ * 罗纳德·李维斯特(Ron [R]ivest)、阿迪·萨莫尔(Adi [S]hamir)和伦纳德·阿德曼(Leonard [A]dleman) + *

+ *

+ * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式
+ * 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,
+ * 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全 + *

+ *

+ * 版本2更新变化: + * 1原版本密钥创建后保存在内存map中,新版本密钥创建后保存为public.key,private.key的功能(base64解码后保存). + * 2为保证生成文件的密钥内容可以显示并缩短加密后的长度,对需加密内容进行hex(16进制转换)处理后再加密. + * 即 + *

+ * + * @author IceWee + * @version 1.0 + * @date 2012-4-26 + */ +public class RSAUtilV2 extends Coder { + + /** */ + /** + * 加密算法RSA + */ + public static final String KEY_ALGORITHM = "RSA"; + + /** */ + /** + * 签名算法 + */ + public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; + + /** */ + /** + * 获取公钥的key + */ + private static final String PUBLIC_KEY = "RSAPublicKey"; + + /** */ + /** + * 获取私钥的key + */ + private static final String PRIVATE_KEY = "RSAPrivateKey"; + + /** */ + /** + * RSA最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = 117; + + /** */ + /** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = 128; + + /** */ + /** + *

+ * 生成密钥对(公钥和私钥) + *

+ * + * @return + * @throws Exception + */ + public static Map genKeyPair() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);//"RSA" + keyPairGen.initialize(1024); + KeyPair keyPair = keyPairGen.generateKeyPair();//Generates a key pair 生成公钥私钥. + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();//取得公钥 + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();//取得私钥 + Map keyMap = new HashMap(2); + keyMap.put(PUBLIC_KEY, publicKey); + keyMap.put(PRIVATE_KEY, privateKey); + return keyMap; + } + + /** + * 将公钥私钥(默认base64加密)并转为公钥/私钥字符串还原为对应公钥/私钥 + * by Z10 + * + * @param keyType (publicKey / privateKey) + * @param keystr + * @return + * @throws Exception + */ + public Key getKeybyKeyStr(String keyType, String keystr) throws Exception { + if (keyType == null || !keyType.equals("")) { + throw new Exception("keyType can not be null"); + } + //需要返回公钥 + if (keyType.equals(keyType)) { + byte[] keyBytes = decryptBASE64(keystr); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + return publicK; + } + //需要返回私钥 + else if (keyType.equals("privateKey")) { + byte[] keyBytes = decryptBASE64(keystr); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); + return privateK; + } else { + throw new Exception("keyType'value is incorrect"); + } + } + + + + /** */ + /** + *

+ * 用私钥对信息生成数字签名 + *

+ * + * @param data 已加密数据 + * @param privateKey 私钥(BASE64编码) + * @return + * @throws Exception + */ + public static String sign(byte[] data, String privateKey) throws Exception { + byte[] keyBytes = decryptBASE64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(privateK); + signature.update(data); + return encryptBASE64(signature.sign()); + } + + /** */ + /** + * 校验数字签名 + *

+ * + * @param data 已加密数据 + * @param publicKey 公钥(BASE64编码) + * @param sign 数字签名 + * @return + * @throws Exception + */ + public static boolean verify(byte[] data, String publicKey, String sign) + throws Exception { + byte[] keyBytes = decryptBASE64(publicKey); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicK = keyFactory.generatePublic(keySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initVerify(publicK); + signature.update(data); + return signature.verify(decryptBASE64(sign)); + } + + + /** + * 私钥解密 by zyl + */ + public static byte[] decryptByPrivateKey(byte[] encryptedData, Key privateKey) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + } + + /** */ + /** + * 私钥解密 + *

+ * + * @param encryptedData 已加密数据 + * @param privateKey 私钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) + throws Exception { + byte[] keyBytes = decryptBASE64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateK); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + } + + + /** */ + /** + * 公钥解密 + *

+ * + * @param encryptedData 已加密数据 + * @param publicKey 公钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) + throws Exception { + + byte[] keyBytes = decryptBASE64(publicKey); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, publicK); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + } + + /** + * 公钥加密 by zyl + * + * @param data + * @param publicKey + * @return + */ + public static byte[] encryptByPublicKey(byte[] data, Key publicKey) throws Exception { + // 对数据加密 + /*根据公钥加密*/ + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + + /** + *

+ * 公钥加密 + *

+ * + * @param data 源数据 + * @param publicKey 公钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] encryptByPublicKey(byte[] data, String publicKey) + throws Exception { + byte[] keyBytes = decryptBASE64(publicKey); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + // 对数据加密 + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicK); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + + /** + * 私钥加密by zyl + */ + public static byte[] encryptByPrivateKey(byte[] data, Key privateKey) throws Exception { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + + + /** */ + /** + *

+ * 私钥加密 + *

+ * + * @param data 源数据 + * @param privateKey 私钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] encryptByPrivateKey(byte[] data, String privateKey) + throws Exception { + byte[] keyBytes = decryptBASE64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, privateK); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + + /** */ + /** + *

+ * 获取私钥 + *

+ * + * @param keyMap 密钥对 + * @return + * @throws Exception + */ + public static String getPrivateKey(Map keyMap) + throws Exception { + Key key = (Key) keyMap.get(PRIVATE_KEY); + return encryptBASE64(key.getEncoded()); + } + + /** */ + /** + *

+ * 获取公钥 + *

+ * + * @param keyMap 密钥对 + * @return + * @throws Exception + */ + public static String getPublicKey(Map keyMap) + throws Exception { + Key key = (Key) keyMap.get(PUBLIC_KEY); + return encryptBASE64(key.getEncoded()); + } + + /* public static void main(String[] args) throws Exception { + + + //test2(); + test(); + }*/ +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/ScreenHelper.java b/app/src/main/java/xyz/fycz/myreader/util/ScreenHelper.java new file mode 100644 index 0000000..0e900d8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/ScreenHelper.java @@ -0,0 +1,30 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.provider.Settings; +import android.util.DisplayMetrics; + +import androidx.appcompat.app.AppCompatActivity; + +/** + */ + +public class ScreenHelper { + + public static double getScreenPhysicalSize(AppCompatActivity ctx) { + DisplayMetrics dm = new DisplayMetrics(); + ctx.getWindowManager().getDefaultDisplay().getMetrics(dm); + double diagonalPixels = Math.sqrt(Math.pow(dm.widthPixels, 2) + Math.pow(dm.heightPixels, 2)); + return diagonalPixels / (160 * dm.density); + } + public static int getScreenOffTime(Context context) { + int screenOffTime = 0; + try { + screenOffTime = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_OFF_TIMEOUT); + } catch (Exception e) { + e.printStackTrace(); + } + return screenOffTime; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java b/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java new file mode 100644 index 0000000..b10afb8 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java @@ -0,0 +1,77 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.content.SharedPreferences; + +import xyz.fycz.myreader.application.MyApplication; + +/** + * SharedPreferences工具类 + */ + +public class SharedPreUtils { + public static final String SHARED_NAME = "FYReader_pref"; + private static SharedPreUtils sInstance; + private SharedPreferences sharedReadable; + private SharedPreferences.Editor sharedWritable; + + private SharedPreUtils(){ + sharedReadable = MyApplication.getmContext() + .getSharedPreferences(SHARED_NAME, Context.MODE_MULTI_PROCESS); + sharedWritable = sharedReadable.edit(); + } + + public static SharedPreUtils getInstance(){ + if(sInstance == null){ + synchronized (SharedPreUtils.class){ + if (sInstance == null){ + sInstance = new SharedPreUtils(); + } + } + } + return sInstance; + } + + public SharedPreferences getSharedReadable(){ + return sharedReadable; + } + + public void putString(String key,String value){ + sharedWritable.putString(key,value); + sharedWritable.commit(); + } + + public void putInt(String key,int value){ + sharedWritable.putInt(key, value); + sharedWritable.commit(); + } + + public void putBoolean(String key,boolean value){ + sharedWritable.putBoolean(key, value); + sharedWritable.commit(); + } + + public String getString(String key){ + return getString(key,""); + } + + public String getString(String key, String def){ + return sharedReadable.getString(key,def); + } + + public int getInt(String key){ + return getInt(key,0); + } + + public int getInt(String key, int def){ + return sharedReadable.getInt(key, def); + } + + public boolean getBoolean(String key){ + return getBoolean(key,false); + } + + public boolean getBoolean(String key,boolean def){ + return sharedReadable.getBoolean(key, false); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/SharedPreferencesUtils.java b/app/src/main/java/xyz/fycz/myreader/util/SharedPreferencesUtils.java new file mode 100644 index 0000000..da7f381 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/SharedPreferencesUtils.java @@ -0,0 +1,137 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; +import xyz.fycz.myreader.application.MyApplication; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class SharedPreferencesUtils { + + private static Editor mEditor; + + private SharedPreferences mSharedPreferences; + + private static SharedPreferencesUtils mSharedPreferencesUtils; + + public static SharedPreferencesUtils getInstance() { + if(mSharedPreferencesUtils == null) { + mSharedPreferencesUtils = new SharedPreferencesUtils(MyApplication.getmContext(), + "FYReaderShare"); + } + return mSharedPreferencesUtils; + } + /** + * + * @param context + */ + public SharedPreferencesUtils(Context context){ + mSharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); + mEditor = mSharedPreferences.edit(); + } + /** + * + * @param context + * @param fileName + */ + public SharedPreferencesUtils(Context context, String fileName){ + mSharedPreferences = context + .getSharedPreferences(fileName, Context.MODE_PRIVATE); + mEditor = mSharedPreferences.edit(); + } + + /** + * 设置通知公告是否已被查看 + * @param key + * @param flag + */ + public void setNoticeFlag(String key, boolean flag) { + mEditor.putBoolean(key, flag); + mEditor.commit(); + } + + /** + * 判断在该机该用户的通知公告是否已被查看 + * @param key + * @return + */ + public boolean isNoticeFlag(String key){ + return mSharedPreferences.getBoolean(key,false); + } + + + /** + * 保存数据 + * @param key 键值 + * @param news 需要保存的数据 + */ + public void save(String key, String news) { + Set values = null; + //获取或创建key,如果xml文件中含有key + if(mSharedPreferences.contains(key)){ + //将数据存储到集合里 + values = mSharedPreferences.getStringSet(key, null); + //删除xml文件中key,并提交 + mEditor.remove(key).commit(); + }else{//如果xml文件中没有key + values = new HashSet(); + } + //将新的数据添加到集合中 + values.add(news); + //将集合保存到偏好设置中 + mEditor.putStringSet(key, values); + //提交 + mEditor.commit(); + } + + /** + * 清空数据 + */ + public void clear(){ + mEditor.clear(); + mEditor.commit(); + } + /** + * 获取数据 + * @param key 键值 + * @return 返回一个字符串 + */ + public List query(String key){ + //创建StringBuffer对象,用来存储从偏好设置中获取的数据 + List list = new ArrayList(); + //获取xml中对应key的值 + Set values = mSharedPreferences.getStringSet(key, null); + //判断该数据是否为空,如果不为空,则 + if(key != null && values != null){ + //遍历集合 + Iterator iterator = values.iterator(); + while(iterator.hasNext()){ + //将数据添加到StringBuffer + list.add(iterator.next()); + } + } + return list; + } + + /** + * 设置是否是第一次登录 + * @param values + */ + public void setFirstEntry(int values){ + mEditor.putInt("first_entry", values); + mEditor.commit(); + } + + public int getFirstEntry(){ + return mSharedPreferences.getInt("first_entry",-1); + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/SoftKeyBroadManager.java b/app/src/main/java/xyz/fycz/myreader/util/SoftKeyBroadManager.java new file mode 100644 index 0000000..5b9570d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/SoftKeyBroadManager.java @@ -0,0 +1,98 @@ +package xyz.fycz.myreader.util; +import android.graphics.Rect; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver; + +import java.util.LinkedList; +import java.util.List; + +/** + * 软键盘弹出/关闭监听 + */ + +public class SoftKeyBroadManager implements ViewTreeObserver.OnGlobalLayoutListener{ + + public interface SoftKeyboardStateListener { + void onSoftKeyboardOpened(int keyboardHeightInPx); + + void onSoftKeyboardClosed(); + } + + private final List listeners = new LinkedList(); + private final View activityRootView; + private int lastSoftKeyboardHeightInPx; + private boolean isSoftKeyboardOpened; + + public SoftKeyBroadManager(View activityRootView) { + this(activityRootView,false); + } + + public SoftKeyBroadManager(View activityRootView, boolean isSoftKeyboardOpened) { + this.activityRootView = activityRootView; + this.isSoftKeyboardOpened = isSoftKeyboardOpened; + activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @Override + public void onGlobalLayout() { + final Rect r = new Rect(); + //r will be populated with the coordinates of your view that area still visible. + activityRootView.getWindowVisibleDisplayFrame(r); + + final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top); + Log.d("SoftKeyboardStateHelper","heightDiff:" + heightDiff); + if (!isSoftKeyboardOpened && heightDiff > 500) { // if more than 100 pixels, its probably a keyboard... + isSoftKeyboardOpened = true; + notifyOnSoftKeyboardOpened(heightDiff); + //if (isSoftKeyboardOpened && heightDiff < 100) + } else if (isSoftKeyboardOpened && heightDiff < 500) { + isSoftKeyboardOpened = false; + notifyOnSoftKeyboardClosed(); + } + } + + public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) { + this.isSoftKeyboardOpened = isSoftKeyboardOpened; + } + + public boolean isSoftKeyboardOpened() { + return isSoftKeyboardOpened; + } + + /** + * Default value is zero (0) + * + * @return last saved keyboard height in px + */ + public int getLastSoftKeyboardHeightInPx() { + return lastSoftKeyboardHeightInPx; + } + + public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) { + listeners.add(listener); + } + + public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) { + listeners.remove(listener); + } + + private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) { + this.lastSoftKeyboardHeightInPx = keyboardHeightInPx; + + for (SoftKeyboardStateListener listener : listeners) { + if (listener != null) { + listener.onSoftKeyboardOpened(keyboardHeightInPx); + } + } + } + + private void notifyOnSoftKeyboardClosed() { + for (SoftKeyboardStateListener listener : listeners) { + if (listener != null) { + listener.onSoftKeyboardClosed(); + } + } + } +} + diff --git a/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java b/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java new file mode 100644 index 0000000..ca2da97 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java @@ -0,0 +1,217 @@ +package xyz.fycz.myreader.util; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class StringHelper { + + /** + * 是否是Emoji表情符 + * @param string + * @return + */ + public static boolean isEmoji(String string) { + Pattern p = Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", + Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(string); + return m.find(); + } + + /** + * 字符集编码 + * @param encoded + * @return + */ + public static String encode(String encoded){ + String res = encoded; + try { + res = URLEncoder.encode(encoded,"UTF-8"); + }catch (Exception e){ + e.printStackTrace(); + } + return res; + } + + /** + * 字符集编码 + * @param encoded + * @return + */ + public static String encode(String encoded, String charsetName){ + String res = encoded; + try { + res = URLEncoder.encode(encoded,charsetName); + }catch (Exception e){ + e.printStackTrace(); + } + return res; + } + + /** + * 字符集解码 + * @param decoded + * @return + */ + + public static String decode(String decoded, String charsetName){ + String res = decoded; + try { + res = URLDecoder.decode(decoded,charsetName); + }catch (Exception e){ + e.printStackTrace(); + } + return res; + } + + /** + * 字符集解码 + * @param decoded + * @return + */ + + public static String decode(String decoded){ + String res = decoded; + try { + res = URLDecoder.decode(decoded,"UTF-8"); + }catch (Exception e){ + e.printStackTrace(); + } + return res; + } + + //生成随机数字和字母, + public static String getStringRandom(int length) { + + String val = ""; + Random random = new Random(); + + //参数length,表示生成几位随机数 + for(int i = 0; i < length; i++) { + String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num"; + //输出字母还是数字 + if( "char".equalsIgnoreCase(charOrNum) ) { + //输出是大写字母还是小写字母 + int temp = random.nextInt(2) % 2 == 0 ? 65 : 97; + val += (char)(random.nextInt(26) + temp); + } else if( "num".equalsIgnoreCase(charOrNum) ) { + val += String.valueOf(random.nextInt(10)); + } + } + return val; + } + + public static String jidToUsername(String jid){ + if(jid != null){ + if(jid.contains("@")){ + return jid.substring(0,jid.indexOf("@")); + + }else { + return jid; + } + } + return ""; + } + + public static boolean isEmpty(String str){ + if (str != null){ + str = str.replace(" ",""); + } + return str == null || str.equals(""); + } + + /** + * 缩减字符串 + * @param strlocation + * @param maxLength + * @return + */ + public static String reduceString(String strlocation, int maxLength){ + if(strlocation != null) { + String res = strlocation; + if (strlocation.length() > maxLength) { + char[] tem = res.toCharArray(); + res = String.copyValueOf(tem, 0, maxLength); + res += "..."; + } + return res; + }else { + return null; + } + } + + /** + * 两字符串是否相等或者都为空 + * @param str1 + * @param str2 + * @return + */ + public static boolean isEquals(String str1, String str2){ + if(isEmpty(str1) && isEmpty(str2)){ + return true; + }else return !isEmpty(str1) && !isEmpty(str2) && str1.equals(str2); + } + + /** + * java实现不区分大小写替换 + * @param source + * @param oldString + * @param newString + * @return + */ + public static String IgnoreCaseReplace(String source, String oldString, + String newString){ + Pattern p = Pattern.compile(oldString, Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(source); + String ret = m.replaceAll(newString); + return ret; + } + + /** + * 解析蓝奏云直链 + * @param o + * @return + */ + public static String getDownloadUrl(String o) { + if (o == null){ + return null; + } + String[] info = o.split(","); + String state = info[0].substring(info[0].indexOf(":\"") + 2, info[0].lastIndexOf("\"")); + if (!"1".equals(state)){ + return null; + } + return info[2].substring(info[2].indexOf(":\"") + 2, info[2].lastIndexOf("\"")); + } + + /** + * 取两个文本之间的文本值 + * @param text 源文本 比如:欲取全文本为 12345 + * @param left 文本前面 + * @param right 后面文本 + * @return 返回 String + */ + public static String getSubString(String text, String left, String right) { + String result = ""; + int zLen; + if (left == null || left.isEmpty()) { + zLen = 0; + } else { + zLen = text.indexOf(left); + if (zLen > -1) { + zLen += left.length(); + } else { + zLen = 0; + } + } + int yLen = text.indexOf(right, zLen); + if (yLen < 0 || right == null || right.isEmpty()) { + yLen = text.length(); + } + result = text.substring(zLen, yLen); + return result; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/SystemBarTintManager.java b/app/src/main/java/xyz/fycz/myreader/util/SystemBarTintManager.java new file mode 100644 index 0000000..0034c44 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/SystemBarTintManager.java @@ -0,0 +1,556 @@ +package xyz.fycz.myreader.util; + +/** + * Created by zhao on 2016/11/21. + */ + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout.LayoutParams; + +import androidx.appcompat.app.AppCompatActivity; + +import java.lang.reflect.Method; + +/** + * Class to manage status and navigation bar tint effects when using KitKat + * translucent system UI modes. + * + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class SystemBarTintManager { + + static { + // Android allows a system property to override the presence of the navigation bar. + // Used by the emulator. + // See https://github.com/android/platform_frameworks_base/blob/master/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java#L1076 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + Class c = Class.forName("android.os.SystemProperties"); + Method m = c.getDeclaredMethod("get", String.class); + m.setAccessible(true); + sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys"); + } catch (Throwable e) { + sNavBarOverride = null; + } + } + } + + + /** + * The default system bar tint color value. + */ + public static final int DEFAULT_TINT_COLOR = 0x99000000; + + private static String sNavBarOverride; + + private final SystemBarConfig mConfig; + private boolean mStatusBarAvailable; + private boolean mNavBarAvailable; + private boolean mStatusBarTintEnabled; + private boolean mNavBarTintEnabled; + private View mStatusBarTintView; + private View mNavBarTintView; + + /** + * Constructor. Call this in the host activity onCreate method after its + * content view has been set. You should always create new instances when + * the host activity is recreated. + * + * @param activity The host activity. + */ + @TargetApi(19) + @SuppressWarnings("ResourceType") + public SystemBarTintManager(AppCompatActivity activity) { + + Window win = activity.getWindow(); + ViewGroup decorViewGroup = (ViewGroup) win.getDecorView(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // check theme attrs + int[] attrs = {android.R.attr.windowTranslucentStatus, + android.R.attr.windowTranslucentNavigation}; + TypedArray a = activity.obtainStyledAttributes(attrs); + try { + mStatusBarAvailable = a.getBoolean(0, false); + mNavBarAvailable = a.getBoolean(1, false); + } finally { + a.recycle(); + } + + // check window flags + WindowManager.LayoutParams winParams = win.getAttributes(); + int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; + if ((winParams.flags & bits) != 0) { + mStatusBarAvailable = true; + } + bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; + if ((winParams.flags & bits) != 0) { + mNavBarAvailable = true; + } + } + + mConfig = new SystemBarConfig(activity, mStatusBarAvailable, mNavBarAvailable); + // device might not have virtual navigation keys + if (!mConfig.hasNavigtionBar()) { + mNavBarAvailable = false; + } + + if (mStatusBarAvailable) { + setupStatusBarView(activity, decorViewGroup); + } + if (mNavBarAvailable) { + setupNavBarView(activity, decorViewGroup); + } + + } + + /** + * Enable tinting of the system status bar. + * + * If the platform is running Jelly Bean or earlier, or translucent system + * UI modes have not been enabled in either the theme or via window flags, + * then this method does nothing. + * + * @param enabled True to enable tinting, false to disable it (default). + */ + public void setStatusBarTintEnabled(boolean enabled) { + mStatusBarTintEnabled = enabled; + if (mStatusBarAvailable) { + mStatusBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + } + + /** + * Enable tinting of the system navigation bar. + * + * If the platform does not have soft navigation keys, is running Jelly Bean + * or earlier, or translucent system UI modes have not been enabled in either + * the theme or via window flags, then this method does nothing. + * + * @param enabled True to enable tinting, false to disable it (default). + */ + public void setNavigationBarTintEnabled(boolean enabled) { + mNavBarTintEnabled = enabled; + if (mNavBarAvailable) { + mNavBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + } + + /** + * Apply the specified color tint to all system UI bars. + * + * @param color The color of the background tint. + */ + public void setTintColor(int color) { + setStatusBarTintColor(color); + setNavigationBarTintColor(color); + } + + /** + * Apply the specified drawable or color resource to all system UI bars. + * + * @param res The identifier of the resource. + */ + public void setTintResource(int res) { + setStatusBarTintResource(res); + setNavigationBarTintResource(res); + } + + /** + * Apply the specified drawable to all system UI bars. + * + * @param drawable The drawable to use as the background, or null to remove it. + */ + public void setTintDrawable(Drawable drawable) { + setStatusBarTintDrawable(drawable); + setNavigationBarTintDrawable(drawable); + } + + /** + * Apply the specified alpha to all system UI bars. + * + * @param alpha The alpha to use + */ + public void setTintAlpha(float alpha) { + setStatusBarAlpha(alpha); + setNavigationBarAlpha(alpha); + } + + /** + * Apply the specified color tint to the system status bar. + * + * @param color The color of the background tint. + */ + public void setStatusBarTintColor(int color) { + if (mStatusBarAvailable) { + mStatusBarTintView.setBackgroundColor(color); + } + } + + /** + * Apply the specified drawable or color resource to the system status bar. + * + * @param res The identifier of the resource. + */ + public void setStatusBarTintResource(int res) { + if (mStatusBarAvailable) { + mStatusBarTintView.setBackgroundResource(res); + } + } + + /** + * Apply the specified drawable to the system status bar. + * + * @param drawable The drawable to use as the background, or null to remove it. + */ + @SuppressWarnings("deprecation") + public void setStatusBarTintDrawable(Drawable drawable) { + if (mStatusBarAvailable) { + mStatusBarTintView.setBackgroundDrawable(drawable); + } + } + + /** + * Apply the specified alpha to the system status bar. + * + * @param alpha The alpha to use + */ + @TargetApi(11) + public void setStatusBarAlpha(float alpha) { + if (mStatusBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mStatusBarTintView.setAlpha(alpha); + } + } + + /** + * Apply the specified color tint to the system navigation bar. + * + * @param color The color of the background tint. + */ + public void setNavigationBarTintColor(int color) { + if (mNavBarAvailable) { + mNavBarTintView.setBackgroundColor(color); + } + } + + /** + * Apply the specified drawable or color resource to the system navigation bar. + * + * @param res The identifier of the resource. + */ + public void setNavigationBarTintResource(int res) { + if (mNavBarAvailable) { + mNavBarTintView.setBackgroundResource(res); + } + } + + /** + * Apply the specified drawable to the system navigation bar. + * + * @param drawable The drawable to use as the background, or null to remove it. + */ + @SuppressWarnings("deprecation") + public void setNavigationBarTintDrawable(Drawable drawable) { + if (mNavBarAvailable) { + mNavBarTintView.setBackgroundDrawable(drawable); + } + } + + /** + * Apply the specified alpha to the system navigation bar. + * + * @param alpha The alpha to use + */ + @TargetApi(11) + public void setNavigationBarAlpha(float alpha) { + if (mNavBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mNavBarTintView.setAlpha(alpha); + } + } + + /** + * Get the system bar configuration. + * + * @return The system bar configuration for the current device configuration. + */ + public SystemBarConfig getConfig() { + return mConfig; + } + + /** + * Is tinting enabled for the system status bar? + * + * @return True if enabled, False otherwise. + */ + public boolean isStatusBarTintEnabled() { + return mStatusBarTintEnabled; + } + + /** + * Is tinting enabled for the system navigation bar? + * + * @return True if enabled, False otherwise. + */ + public boolean isNavBarTintEnabled() { + return mNavBarTintEnabled; + } + + private void setupStatusBarView(Context context, ViewGroup decorViewGroup) { + mStatusBarTintView = new View(context); + LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getStatusBarHeight()); + params.gravity = Gravity.TOP; + if (mNavBarAvailable && !mConfig.isNavigationAtBottom()) { + params.rightMargin = mConfig.getNavigationBarWidth(); + } + mStatusBarTintView.setLayoutParams(params); + mStatusBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR); + mStatusBarTintView.setVisibility(View.GONE); + decorViewGroup.addView(mStatusBarTintView); + } + + private void setupNavBarView(Context context, ViewGroup decorViewGroup) { + mNavBarTintView = new View(context); + LayoutParams params; + if (mConfig.isNavigationAtBottom()) { + params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getNavigationBarHeight()); + params.gravity = Gravity.BOTTOM; + } else { + params = new LayoutParams(mConfig.getNavigationBarWidth(), LayoutParams.MATCH_PARENT); + params.gravity = Gravity.RIGHT; + } + mNavBarTintView.setLayoutParams(params); + mNavBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR); + mNavBarTintView.setVisibility(View.GONE); + decorViewGroup.addView(mNavBarTintView); + } + + /** + * Class which describes system bar sizing and other characteristics for the current + * device configuration. + * + */ + public static class SystemBarConfig { + + private static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height"; + private static final String NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height"; + private static final String NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME = "navigation_bar_height_landscape"; + private static final String NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width"; + private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar"; + + private final boolean mTranslucentStatusBar; + private final boolean mTranslucentNavBar; + private final int mStatusBarHeight; + private final int mActionBarHeight; + private final boolean mHasNavigationBar; + private final int mNavigationBarHeight; + private final int mNavigationBarWidth; + private final boolean mInPortrait; + private final float mSmallestWidthDp; + + private SystemBarConfig(AppCompatActivity activity, boolean translucentStatusBar, boolean traslucentNavBar) { + Resources res = activity.getResources(); + mInPortrait = (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT); + mSmallestWidthDp = getSmallestWidthDp(activity); + mStatusBarHeight = getInternalDimensionSize(res, STATUS_BAR_HEIGHT_RES_NAME); + mActionBarHeight = getActionBarHeight(activity); + mNavigationBarHeight = getNavigationBarHeight(activity); + mNavigationBarWidth = getNavigationBarWidth(activity); + mHasNavigationBar = (mNavigationBarHeight > 0); + mTranslucentStatusBar = translucentStatusBar; + mTranslucentNavBar = traslucentNavBar; + } + + @TargetApi(14) + private int getActionBarHeight(Context context) { + int result = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + TypedValue tv = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true); + result = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); + } + return result; + } + + @TargetApi(14) + private int getNavigationBarHeight(Context context) { + Resources res = context.getResources(); + int result = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (hasNavBar(context)) { + String key; + if (mInPortrait) { + key = NAV_BAR_HEIGHT_RES_NAME; + } else { + key = NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME; + } + return getInternalDimensionSize(res, key); + } + } + return result; + } + + @TargetApi(14) + private int getNavigationBarWidth(Context context) { + Resources res = context.getResources(); + int result = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (hasNavBar(context)) { + return getInternalDimensionSize(res, NAV_BAR_WIDTH_RES_NAME); + } + } + return result; + } + + @TargetApi(14) + private boolean hasNavBar(Context context) { + Resources res = context.getResources(); + int resourceId = res.getIdentifier(SHOW_NAV_BAR_RES_NAME, "bool", "android"); + if (resourceId != 0) { + boolean hasNav = res.getBoolean(resourceId); + // check override flag (see static block) + if ("1".equals(sNavBarOverride)) { + hasNav = false; + } else if ("0".equals(sNavBarOverride)) { + hasNav = true; + } + return hasNav; + } else { // fallback + return !ViewConfiguration.get(context).hasPermanentMenuKey(); + } + } + + private int getInternalDimensionSize(Resources res, String key) { + int result = 0; + int resourceId = res.getIdentifier(key, "dimen", "android"); + if (resourceId > 0) { + result = res.getDimensionPixelSize(resourceId); + } + return result; + } + + @SuppressLint("NewApi") + private float getSmallestWidthDp(AppCompatActivity activity) { + DisplayMetrics metrics = new DisplayMetrics(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); + } else { + // TODO this is not correct, but we don't really care pre-kitkat + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + } + float widthDp = metrics.widthPixels / metrics.density; + float heightDp = metrics.heightPixels / metrics.density; + return Math.min(widthDp, heightDp); + } + + /** + * Should a navigation bar appear at the bottom of the screen in the current + * device configuration? A navigation bar may appear on the right side of + * the screen in certain configurations. + * + * @return True if navigation should appear at the bottom of the screen, False otherwise. + */ + public boolean isNavigationAtBottom() { + return (mSmallestWidthDp >= 600 || mInPortrait); + } + + /** + * Get the height of the system status bar. + * + * @return The height of the status bar (in pixels). + */ + public int getStatusBarHeight() { + return mStatusBarHeight; + } + + /** + * Get the height of the action bar. + * + * @return The height of the action bar (in pixels). + */ + public int getActionBarHeight() { + return mActionBarHeight; + } + + /** + * Does this device have a system navigation bar? + * + * @return True if this device uses soft key navigation, False otherwise. + */ + public boolean hasNavigtionBar() { + return mHasNavigationBar; + } + + /** + * Get the height of the system navigation bar. + * + * @return The height of the navigation bar (in pixels). If the device does not have + * soft navigation keys, this will always return 0. + */ + public int getNavigationBarHeight() { + return mNavigationBarHeight; + } + + /** + * Get the width of the system navigation bar when it is placed vertically on the screen. + * + * @return The nbsp;width of the navigation bar (in pixels). If the device does not have + * soft navigation keys, this will always return 0. + */ + public int getNavigationBarWidth() { + return mNavigationBarWidth; + } + + /** + * Get the layout inset for any system UI that appears at the top of the screen. + * + * @param withActionBar True to include the height of the action bar, False otherwise. + * @return The layout inset (in pixels). + */ + public int getPixelInsetTop(boolean withActionBar) { + return (mTranslucentStatusBar ? mStatusBarHeight : 0) + (withActionBar ? mActionBarHeight : 0); + } + + /** + * Get the layout inset for any system UI that appears at the bottom of the screen. + * + * @return The layout inset (in pixels). + */ + public int getPixelInsetBottom() { + if (mTranslucentNavBar && isNavigationAtBottom()) { + return mNavigationBarHeight; + } else { + return 0; + } + } + + /** + * Get the layout inset for any system UI that appears at the right of the screen. + * + * @return The layout inset (in pixels). + */ + public int getPixelInsetRight() { + if (mTranslucentNavBar && !isNavigationAtBottom()) { + return mNavigationBarWidth; + } else { + return 0; + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/TelephonyUtil.java b/app/src/main/java/xyz/fycz/myreader/util/TelephonyUtil.java new file mode 100644 index 0000000..a7269fc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/TelephonyUtil.java @@ -0,0 +1,51 @@ +package xyz.fycz.myreader.util; + +import android.content.Context; +import android.telephony.TelephonyManager; + +import java.lang.reflect.Method; + + + +public class TelephonyUtil { + + + public static String getNum1(Context context){ + String tel; + String IMSI; + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String deviceid = tm.getDeviceId(); + tel = tm.getLine1Number();//手机号码 + Class clazz = tm.getClass(); + Method getPhoneNumber = clazz.getDeclaredMethod("getLine1NumberForSubscriber",int.class); + String tel0 = (String)getPhoneNumber.invoke(tm, 0); + String tel1 = (String)getPhoneNumber.invoke(tm, 1); + + IMSI = tm.getSubscriberId(); + TextHelper.showText(IMSI); + }catch (Exception e){ + e.printStackTrace(); + tel = ""; + } + + + return tel; + } + + public static String getNum2(Context context){ + String tel; + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String deviceid = tm.getDeviceId(); + tel = tm.getLine1Number();//手机号码 + + }catch (Exception e){ + e.printStackTrace(); + tel = ""; + } + + return tel; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/TextHelper.java b/app/src/main/java/xyz/fycz/myreader/util/TextHelper.java new file mode 100644 index 0000000..07ca922 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/TextHelper.java @@ -0,0 +1,31 @@ +package xyz.fycz.myreader.util; + +import android.widget.Toast; + +import xyz.fycz.myreader.application.MyApplication; + + + + +public class TextHelper { + + public static void showText(final String text){ + + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_SHORT).show(); + } + }); + } + + public static void showLongText(final String text){ + + MyApplication.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_LONG).show(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/UploadImageTask.java b/app/src/main/java/xyz/fycz/myreader/util/UploadImageTask.java new file mode 100644 index 0000000..28d90c2 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/UploadImageTask.java @@ -0,0 +1,5 @@ +package xyz.fycz.myreader.util; + + +public class UploadImageTask{ +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/UriFileUtil.java b/app/src/main/java/xyz/fycz/myreader/util/UriFileUtil.java new file mode 100644 index 0000000..2ea1325 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/UriFileUtil.java @@ -0,0 +1,336 @@ +package xyz.fycz.myreader.util; + + +import android.content.ContentResolver; +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.provider.DocumentsContract; +import android.provider.MediaStore; +import androidx.core.content.FileProvider; + +import xyz.fycz.myreader.R; + +import java.io.File; + + + +public class UriFileUtil { + + /** + * 获取URI + * @param context + * @param file + * @return + */ + public static Uri getUriForFile(Context context, File file) { + if (context == null || file == null) { + throw new NullPointerException(); + } + Uri uri; + if (Build.VERSION.SDK_INT >= 24) { + uri = FileProvider.getUriForFile(context.getApplicationContext(), context.getString(R.string.sys_file_provider), file); + } else { + uri = Uri.fromFile(file); + } + return uri; + } + + public static String getRealPathFromUri(Context context, Uri contentUri) { + Cursor cursor = null; + try { + String[] proj = { MediaStore.Images.Media.DATA }; + cursor = context.getContentResolver().query(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + public static String getFilePathByUri(final Context context, final Uri uri) { + if (null == uri) + return null; + final String scheme = uri.getScheme(); + String data = null; + if (scheme == null) + data = uri.getPath(); + else if (ContentResolver.SCHEME_FILE.equals(scheme)) { + data = uri.getPath(); + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { + Cursor cursor = context.getContentResolver().query(uri, + new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null); + if (null != cursor) { + if (cursor.moveToFirst()) { + int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); + if (index > -1) { + data = cursor.getString(index); + } + } + cursor.close(); + } + } + return data; + } + /** + + * Get a file path from a Uri. This will get the the path for Storage Access + + * Framework Documents, as well as the _data field for the MediaStore and + + * other file-based ContentProviders. + + * + + * @param context The context. + + * @param uri The Uri to query. + + * @author paulburke + + */ + + public static String getPath(final Context context, final Uri uri) { + + + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + + + // DocumentProvider + + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + + // ExternalStorageProvider + + if (isExternalStorageDocument(uri)) { + + final String docId = DocumentsContract.getDocumentId(uri); + + final String[] split = docId.split(":"); + + final String type = split[0]; + + + + if ("primary".equalsIgnoreCase(type)) { + + return Environment.getExternalStorageDirectory() + "/" + split[1]; + + } + + + + // TODO handle non-primary volumes + + } + + // DownloadsProvider + + else if (isDownloadsDocument(uri)) { + + + + final String id = DocumentsContract.getDocumentId(uri); + + final Uri contentUri = ContentUris.withAppendedId( + + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + + + return getDataColumn(context, contentUri, null, null); + + } + + // MediaProvider + + else if (isMediaDocument(uri)) { + + final String docId = DocumentsContract.getDocumentId(uri); + + final String[] split = docId.split(":"); + + final String type = split[0]; + + + + Uri contentUri = null; + + if ("image".equals(type)) { + + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + } else if ("video".equals(type)) { + + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + + } else if ("audio".equals(type)) { + + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + + } + + + + final String selection = "_id=?"; + + final String[] selectionArgs = new String[] { + + split[1] + + }; + + + + return getDataColumn(context, contentUri, selection, selectionArgs); + + } + + } + + // MediaStore (and general) + + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + return getDataColumn(context, uri, null, null); + + } + + // File + + else if ("file".equalsIgnoreCase(uri.getScheme())) { + + return uri.getPath(); + + } + + + + return null; + + } + + + + /** + + * Get the value of the data column for this Uri. This is useful for + + * MediaStore Uris, and other file-based ContentProviders. + + * + + * @param context The context. + + * @param uri The Uri to query. + + * @param selection (Optional) Filter used in the query. + + * @param selectionArgs (Optional) Selection arguments used in the query. + + * @return The value of the _data column, which is typically a file path. + + */ + + public static String getDataColumn(Context context, Uri uri, String selection, + + String[] selectionArgs) { + + + + Cursor cursor = null; + + final String column = "_data"; + + final String[] projection = { + + column + + }; + + + + try { + + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + + null); + + if (cursor != null && cursor.moveToFirst()) { + + final int column_index = cursor.getColumnIndexOrThrow(column); + + return cursor.getString(column_index); + + } + + } finally { + + if (cursor != null) + + cursor.close(); + + } + + return null; + + } + + + + + + /** + + * @param uri The Uri to check. + + * @return Whether the Uri authority is ExternalStorageProvider. + + */ + + public static boolean isExternalStorageDocument(Uri uri) { + + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + + } + + + + /** + + * @param uri The Uri to check. + + * @return Whether the Uri authority is DownloadsProvider. + + */ + + public static boolean isDownloadsDocument(Uri uri) { + + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + + } + + + + /** + + * @param uri The Uri to check. + + * @return Whether the Uri authority is MediaProvider. + + */ + + public static boolean isMediaDocument(Uri uri) { + + return "com.android.providers.media.documents".equals(uri.getAuthority()); + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/VibratorUtil.java b/app/src/main/java/xyz/fycz/myreader/util/VibratorUtil.java new file mode 100644 index 0000000..d018c3e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/VibratorUtil.java @@ -0,0 +1,27 @@ +package xyz.fycz.myreader.util; + +import android.app.Service; +import android.os.Vibrator; + +import androidx.appcompat.app.AppCompatActivity; + + +public class VibratorUtil { + + /** + * final Activity activity :调用该方法的Activity实例 + * long milliseconds :震动的时长,单位是毫秒 + * long[] pattern :自定义震动模式 。数组中数字的含义依次是[静止时长,震动时长,静止时长,震动时长。。。]时长的单位是毫秒 + * boolean isRepeat : 是否反复震动,如果是true,反复震动,如果是false,只震动一次 + */ + + public static void Vibrate(final AppCompatActivity activity, long milliseconds) { + Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE); + vib.vibrate(milliseconds); + } + public static void Vibrate(final AppCompatActivity activity, long[] pattern, boolean isRepeat) { + Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE); + vib.vibrate(pattern, isRepeat ? 1 : -1); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/ZipUtils.java b/app/src/main/java/xyz/fycz/myreader/util/ZipUtils.java new file mode 100644 index 0000000..18689f4 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/ZipUtils.java @@ -0,0 +1,429 @@ +package xyz.fycz.myreader.util; + +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + *
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/08/27
+ *     desc  : utils about zip
+ * 
+ */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public final class ZipUtils { + + private static final int BUFFER_LEN = 8192; + + private ZipUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFilePath The path of ZIP file. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFiles(final Collection srcFiles, + final String zipFilePath) + throws IOException { + return zipFiles(srcFiles, zipFilePath, null); + } + + /** + * Zip the files. + * + * @param srcFilePaths The paths of source files. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFiles(final Collection srcFilePaths, + final String zipFilePath, + final String comment) + throws IOException { + if (srcFilePaths == null || zipFilePath == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFilePath)); + for (String srcFile : srcFilePaths) { + if (!zipFile(getFileByPath(srcFile), "", zos, comment)) return false; + } + return true; + } finally { + if (zos != null) { + zos.finish(); + zos.close(); + } + } + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFile The ZIP file. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFiles(final Collection srcFiles, final File zipFile) + throws IOException { + return zipFiles(srcFiles, zipFile, null); + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFiles(final Collection srcFiles, + final File zipFile, + final String comment) + throws IOException { + if (srcFiles == null || zipFile == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFile)); + for (File srcFile : srcFiles) { + if (!zipFile(srcFile, "", zos, comment)) return false; + } + return true; + } finally { + if (zos != null) { + zos.finish(); + zos.close(); + } + } + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFile(final String srcFilePath, + final String zipFilePath) + throws IOException { + return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), null); + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFile(final String srcFilePath, + final String zipFilePath, + final String comment) + throws IOException { + return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), comment); + } + + /** + * Zip the file. + * + * @param srcFile The source of file. + * @param zipFile The ZIP file. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFile(final File srcFile, + final File zipFile) + throws IOException { + return zipFile(srcFile, zipFile, null); + } + + /** + * Zip the file. + * + * @param srcFile The source of file. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return {@code true}: success
{@code false}: fail + * @throws IOException if an I/O error has occurred + */ + public static boolean zipFile(final File srcFile, + final File zipFile, + final String comment) + throws IOException { + if (srcFile == null || zipFile == null) return false; + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + return zipFile(srcFile, "", zos, comment); + } + } + + private static boolean zipFile(final File srcFile, + String rootPath, + final ZipOutputStream zos, + final String comment) throws IOException { + if (!srcFile.exists()) return true; + rootPath = rootPath + (isSpace(rootPath) ? "" : File.separator) + srcFile.getName(); + if (srcFile.isDirectory()) { + File[] fileList = srcFile.listFiles(); + if (fileList == null || fileList.length <= 0) { + ZipEntry entry = new ZipEntry(rootPath + '/'); + entry.setComment(comment); + zos.putNextEntry(entry); + zos.closeEntry(); + } else { + for (File file : fileList) { + if (!zipFile(file, rootPath, zos, comment)) return false; + } + } + } else { + try (InputStream is = new BufferedInputStream(new FileInputStream(srcFile))) { + ZipEntry entry = new ZipEntry(rootPath); + entry.setComment(comment); + zos.putNextEntry(entry); + byte buffer[] = new byte[BUFFER_LEN]; + int len; + while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + } + } + return true; + } + + /** + * Unzip the file. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + public static List unzipFile(final String zipFilePath, final String destDirPath) throws IOException { + return unzipFileByKeyword(zipFilePath, destDirPath, null); + } + + /** + * Unzip the file. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + public static List unzipFile(final File zipFile, + final File destDir) + throws IOException { + return unzipFileByKeyword(zipFile, destDir, null); + } + + /** + * Unzip the file by keyword. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + public static List unzipFileByKeyword(final String zipFilePath, + final String destDirPath, + final String keyword) throws IOException { + return unzipFileByKeyword(getFileByPath(zipFilePath), getFileByPath(destDirPath), keyword); + } + + /** + * Unzip the file by keyword. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + public static List unzipFileByKeyword(final File zipFile, + final File destDir, + final String keyword) throws IOException { + if (zipFile == null || destDir == null) return null; + List files = new ArrayList<>(); + ZipFile zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + try { + if (isSpace(keyword)) { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: " + entryName + " is dangerous!"); + continue; + } + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } else { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: " + entryName + " is dangerous!"); + continue; + } + if (entryName.contains(keyword)) { + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } + } + } finally { + zip.close(); + } + return files; + } + + private static boolean unzipChildFile(final File destDir, + final List files, + final ZipFile zip, + final ZipEntry entry, + final String name) throws IOException { + File file = new File(destDir, name); + files.add(file); + if (entry.isDirectory()) { + return createOrExistsDir(file); + } else { + if (!createOrExistsFile(file)) return false; + try (InputStream in = new BufferedInputStream(zip.getInputStream(entry)); OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { + byte buffer[] = new byte[BUFFER_LEN]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + } + } + return true; + } + + /** + * Return the files' path in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + public static List getFilesPath(final String zipFilePath) + throws IOException { + return getFilesPath(getFileByPath(zipFilePath)); + } + + /** + * Return the files' path in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + public static List getFilesPath(final File zipFile) + throws IOException { + if (zipFile == null) return null; + List paths = new ArrayList<>(); + ZipFile zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + String entryName = ((ZipEntry) entries.nextElement()).getName(); + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: " + entryName + " is dangerous!"); + paths.add(entryName); + } else { + paths.add(entryName); + } + } + zip.close(); + return paths; + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + public static List getComments(final String zipFilePath) + throws IOException { + return getComments(getFileByPath(zipFilePath)); + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + public static List getComments(final File zipFile) + throws IOException { + if (zipFile == null) return null; + List comments = new ArrayList<>(); + ZipFile zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + comments.add(entry.getComment()); + } + zip.close(); + return comments; + } + + private static boolean createOrExistsDir(final File file) { + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + private static boolean createOrExistsFile(final File file) { + if (file == null) return false; + if (file.exists()) return file.isFile(); + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + private static File getFileByPath(final String filePath) { + return isSpace(filePath) ? null : new File(filePath); + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/Charset.java b/app/src/main/java/xyz/fycz/myreader/util/utils/Charset.java new file mode 100644 index 0000000..beb3f92 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/Charset.java @@ -0,0 +1,23 @@ +package xyz.fycz.myreader.util.utils; + +/** + * 编码类型 + */ +public enum Charset { + UTF8("UTF-8"), + UNICODE("Unicode"), + UTF16LE("UTF-16LE"), + UTF16BE("UTF-16BE"), + GBK("GBK"); + + private String mName; + public static final byte BLANK = 0x0a; + + private Charset(String name) { + mName = name; + } + + public String getName() { + return mName; + } +} 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 new file mode 100644 index 0000000..d0161cc --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java @@ -0,0 +1,237 @@ +package xyz.fycz.myreader.util.utils; + +import android.app.Application; +import android.os.Environment; +import info.monitorenter.cpdetector.io.*; +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; +import xyz.fycz.myreader.application.MyApplication; + +import java.io.*; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by newbiechen on 17-5-11. + */ + +public class FileUtils { + //采用自己的格式去设置文件,防止文件被系统文件查询到 + public static final String SUFFIX_NB = ".nb"; + public static final String SUFFIX_FY = ".fy"; + public static final String SUFFIX_TXT = ".txt"; + public static final String SUFFIX_EPUB = ".epub"; + public static final String SUFFIX_PDF = ".pdf"; + + //获取文件夹 + public static File getFolder(String filePath){ + File file = new File(filePath); + //如果文件夹不存在,就创建它 + if (!file.exists()){ + file.mkdirs(); + } + return file; + } + + //获取文件 + public static synchronized File getFile(String filePath){ + File file = new File(filePath); + try { + if (!file.exists()){ + //创建父类文件夹 + getFolder(file.getParent()); + //创建文件 + file.createNewFile(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return file; + } + + //获取Cache文件夹 + public static String getCachePath(){ + if (isSdCardExist()){ + return MyApplication.getmContext() + .getExternalCacheDir() + .getAbsolutePath(); + } + else{ + return MyApplication.getmContext() + .getCacheDir() + .getAbsolutePath(); + } + } + + public static long getDirSize(File file){ + //判断文件是否存在 + if (file.exists()) { + //如果是目录则递归计算其内容的总大小 + if (file.isDirectory()) { + File[] children = file.listFiles(); + long size = 0; + for (File f : children) + size += getDirSize(f); + return size; + } else { + return file.length(); + } + } else { + return 0; + } + } + + public static String getFileSize(long size) { + if (size <= 0) return "0"; + final String[] units = new String[]{"b", "kb", "M", "G", "T"}; + //计算单位的,原理是利用lg,公式是 lg(1024^n) = nlg(1024),最后 nlg(1024)/lg(1024) = n。 + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + //计算原理是,size/单位值。单位值指的是:比如说b = 1024,KB = 1024^2 + return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + /** + * 本来是获取File的内容的。但是为了解决文本缩进、换行的问题 + * 这个方法就是专门用来获取书籍的... + * + * 应该放在BookRepository中。。。 + * @param file + * @return + */ + public static String getFileContent(File file){ + Reader reader = null; + String str = null; + StringBuilder sb = new StringBuilder(); + try { + reader = new FileReader(file); + BufferedReader br = new BufferedReader(reader); + while ((str = br.readLine()) != null){ + //过滤空语句 + if (!str.equals("")){ + //由于sb会自动过滤\n,所以需要加上去 + sb.append(" "+str+"\n"); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return sb.toString(); + } + + //判断是否挂载了SD卡 + public static boolean isSdCardExist(){ + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ + return true; + } + return false; + } + + //递归删除文件夹下的数据 + public static synchronized void deleteFile(String filePath){ + File file = new File(filePath); + if (!file.exists()) return; + + if (file.isDirectory()){ + File[] files = file.listFiles(); + for (File subFile : files){ + String path = subFile.getPath(); + deleteFile(path); + } + } + //删除文件 + file.delete(); + } + + //由于递归的耗时问题,取巧只遍历内部三层 + + //获取txt文件 + public static List getTxtFiles(String filePath, int layer){ + final List txtFiles = new ArrayList(); + File file = new File(filePath); + + //如果层级为 3,则直接返回 + if (layer == 3){ + return txtFiles; + } + + //获取文件夹 + File[] dirs = file.listFiles( + new FileFilter() { + @Override + public boolean accept(File pathname) { + if (pathname.isDirectory() && !pathname.getName().startsWith(".")) { + return true; + } + //获取txt文件 + else if (pathname.getName().endsWith(".txt")) { + txtFiles.add(pathname); + return false; + } else { + return false; + } + } + } + ); + //遍历文件夹 + for (File dir : dirs){ + //递归遍历txt文件 + txtFiles.addAll(getTxtFiles(dir.getPath(),layer + 1)); + } + return txtFiles; + } + + //由于遍历比较耗时 + public static Single> getSDTxtFile(){ + //外部存储卡路径 + final String rootPath = Environment.getExternalStorageDirectory().getPath(); + return Single.create(new SingleOnSubscribe>() { + @Override + public void subscribe(SingleEmitter> e) throws Exception { + List files = getTxtFiles(rootPath,0); + e.onSuccess(files); + } + }); + } + + /** + * 获得文件编码 + * @param filePath + * @return + * @throws Exception + */ + public static String getFileEncode(String filePath) { + String charsetName = null; + try { + File file = new File(filePath); + CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance(); + detector.add(new ParsingDetector(false)); + detector.add(JChardetFacade.getInstance()); + detector.add(ASCIIDetector.getInstance()); + detector.add(UnicodeDetector.getInstance()); + java.nio.charset.Charset charset = null; + charset = detector.detectCodepage(file.toURI().toURL()); + if (charset != null) { + charsetName = charset.name(); + } else { + charsetName = "UTF-8"; + } + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + return charsetName; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java new file mode 100644 index 0000000..2bacaee --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/NetworkUtils.java @@ -0,0 +1,49 @@ +//Copyright (c) 2017. 章钦豪. All rights reserved. +package xyz.fycz.myreader.util.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import xyz.fycz.myreader.application.MyApplication; + + +import java.util.HashMap; +import java.util.Map; + +public class NetworkUtils { + public static final int SUCCESS = 10000; + public static final int ERROR_CODE_NONET = 10001; + public static final int ERROR_CODE_OUTTIME = 10002; + public static final int ERROR_CODE_ANALY = 10003; + @SuppressLint("UseSparseArrays") + private static final Map errorMap = new HashMap<>(); + + static { + errorMap.put(ERROR_CODE_NONET, "没有网络"); + errorMap.put(ERROR_CODE_OUTTIME, "网络连接超时"); + errorMap.put(ERROR_CODE_ANALY, "数据解析失败"); + } + + public static String getErrorTip(int code) { + return errorMap.get(code); + } + + public static boolean isNetWorkAvailable() { + ConnectivityManager manager = (ConnectivityManager) MyApplication.getmContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager != null) { + NetworkInfo info = manager.getActiveNetworkInfo(); + return info != null && info.isConnected(); + } else { + return false; + } + } + + + + public static boolean isUrl(String urlStr) { + String regex = "^(https?)://.+$";//设置正则表达式 + return urlStr.matches(regex); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/RxUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/RxUtils.java new file mode 100644 index 0000000..c65c52b --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/RxUtils.java @@ -0,0 +1,44 @@ +package xyz.fycz.myreader.util.utils; + + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Single; +import io.reactivex.SingleSource; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Function3; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; + +/** + * Created by newbiechen on 17-4-29. + */ + +public class RxUtils { + + public static SingleSource toSimpleSingle(Single upstream){ + return upstream.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public static ObservableSource toSimpleSingle(Observable upstream){ + return upstream.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public static TwoTuple twoTuple(T first,R second){ + return new TwoTuple(first, second); + } + + + public static class TwoTuple { + public final A first; + public final B second; + + public TwoTuple(A a, B b) { + this.first = a; + this.second = b; + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/ScreenUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/ScreenUtils.java new file mode 100644 index 0000000..f50453c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/ScreenUtils.java @@ -0,0 +1,120 @@ +package xyz.fycz.myreader.util.utils; + +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import androidx.appcompat.app.AppCompatActivity; +import xyz.fycz.myreader.application.MyApplication; + +import java.lang.reflect.Method; + +/** + * Created by newbiechen on 17-5-1. + */ + +public class ScreenUtils { + + public static int dpToPx(int dp){ + DisplayMetrics metrics = getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,metrics); + } + + public static int pxToDp(int px){ + DisplayMetrics metrics = getDisplayMetrics(); + return (int) (px / metrics.density); + } + + public static int spToPx(int sp){ + DisplayMetrics metrics = getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,metrics); + } + + public static int pxToSp(int px){ + DisplayMetrics metrics = getDisplayMetrics(); + return (int) (px / metrics.scaledDensity); + } + + /** + * 获取手机显示App区域的大小(头部导航栏+ActionBar+根布局),不包括虚拟按钮 + * @return + */ + public static int[] getAppSize(){ + int[] size = new int[2]; + DisplayMetrics metrics = getDisplayMetrics(); + size[0] = metrics.widthPixels; + size[1] = metrics.heightPixels; + return size; + } + + /** + * 获取整个手机屏幕的大小(包括虚拟按钮) + * 必须在onWindowFocus方法之后使用 + * @param activity + * @return + */ + public static int[] getScreenSize(AppCompatActivity activity){ + int[] size = new int[2]; + View decorView = activity.getWindow().getDecorView(); + size[0] = decorView.getWidth(); + size[1] = decorView.getHeight(); + return size; + } + + /** + * 获取导航栏的高度 + * @return + */ + public static int getStatusBarHeight(){ + Resources resources = MyApplication.getmContext().getResources(); + int resourceId = resources.getIdentifier("status_bar_height","dimen","android"); + return resources.getDimensionPixelSize(resourceId); + } + + /** + * 获取虚拟按键的高度 + * @return + */ + public static int getNavigationBarHeight() { + int navigationBarHeight = 0; + Resources rs = MyApplication.getmContext().getResources(); + int id = rs.getIdentifier("navigation_bar_height", "dimen", "android"); + if (id > 0 && hasNavigationBar()) { + navigationBarHeight = rs.getDimensionPixelSize(id); + } + return navigationBarHeight; + } + + /** + * 是否存在虚拟按键 + * @return + */ + private static boolean hasNavigationBar() { + boolean hasNavigationBar = false; + Resources rs = MyApplication.getmContext().getResources(); + int id = rs.getIdentifier("config_showNavigationBar", "bool", "android"); + if (id > 0) { + hasNavigationBar = rs.getBoolean(id); + } + try { + Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method m = systemPropertiesClass.getMethod("get", String.class); + String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + hasNavigationBar = false; + } else if ("0".equals(navBarOverride)) { + hasNavigationBar = true; + } + } catch (Exception e) { + } + return hasNavigationBar; + } + + public static DisplayMetrics getDisplayMetrics(){ + DisplayMetrics metrics = MyApplication + .getmContext() + .getResources() + .getDisplayMetrics(); + return metrics; + } +} 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 new file mode 100644 index 0000000..da90c0d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/StringUtils.java @@ -0,0 +1,199 @@ +package xyz.fycz.myreader.util.utils; + +import android.content.Context; + +import android.text.Editable; +import androidx.annotation.StringRes; +import xyz.fycz.myreader.application.MyApplication; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + + +/** + * Created by newbiechen on 17-4-22. + * 对文字操作的工具类 + */ + +public class StringUtils { + private static final String TAG = "StringUtils"; + private static final int HOUR_OF_DAY = 24; + private static final int DAY_OF_YESTERDAY = 2; + private static final int TIME_UNIT = 60; + + //将时间转换成日期 + public static String dateConvert(long time, String pattern){ + Date date = new Date(time); + SimpleDateFormat format = new SimpleDateFormat(pattern); + return format.format(date); + } + + //将日期转换成昨天、今天、明天 + public static String dateConvert(String source, String pattern){ + DateFormat format = new SimpleDateFormat(pattern); + Calendar calendar = Calendar.getInstance(); + try { + Date date = format.parse(source); + long curTime = calendar.getTimeInMillis(); + calendar.setTime(date); + //将MISC 转换成 sec + long difSec = Math.abs((curTime - date.getTime())/1000); + long difMin = difSec/60; + long difHour = difMin/60; + long difDate = difHour/60; + int oldHour = calendar.get(Calendar.HOUR); + //如果没有时间 + if (oldHour == 0){ + //比日期:昨天今天和明天 + if (difDate == 0){ + return "今天"; + } + else if (difDate < DAY_OF_YESTERDAY){ + return "昨天"; + } + else { + DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); + String value = convertFormat.format(date); + return value; + } + } + + if (difSec < TIME_UNIT){ + return difSec+"秒前"; + } + else if (difMin < TIME_UNIT){ + return difMin+"分钟前"; + } + else if (difHour < HOUR_OF_DAY){ + return difHour+"小时前"; + } + else if (difDate < DAY_OF_YESTERDAY){ + return "昨天"; + } + else { + DateFormat convertFormat = new SimpleDateFormat("yyyy-MM-dd"); + String value = convertFormat.format(date); + return value; + } + } catch (ParseException e) { + e.printStackTrace(); + } + return ""; + } + + public static String toFirstCapital(String str){ + return str.substring(0,1).toUpperCase()+str.substring(1); + } + + public static String getString(@StringRes int id){ + return MyApplication.getmContext().getResources().getString(id); + } + + public static String getString(@StringRes int id, Object... formatArgs){ + return MyApplication.getmContext().getResources().getString(id,formatArgs); + } + + /** + * 将文本中的半角字符,转换成全角字符 + * @param input + * @return + */ + public static String halfToFull(String input) + { + char[] c = input.toCharArray(); + for (int i = 0; i< c.length; i++) + { + if (c[i] == 32) //半角空格 + { + c[i] = (char) 12288; + continue; + } + //根据实际情况,过滤不需要转换的符号 + //if (c[i] == 46) //半角点号,不转换 + // continue; + + if (c[i]> 32 && c[i]< 127) //其他符号都转换为全角 + c[i] = (char) (c[i] + 65248); + } + return new String(c); + } + + //功能:字符串全角转换为半角 + public static String fullToHalf(String input) + { + char[] c = input.toCharArray(); + for (int i = 0; i< c.length; i++) + { + if (c[i] == 12288) //全角空格 + { + c[i] = (char) 32; + continue; + } + + if (c[i]> 65280&& c[i]< 65375) + c[i] = (char) (c[i] - 65248); + } + 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){ + //禁止输入中文 + if (s.length() > 0) { + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c >= 0x4e00 && c <= 0X9fff) { // 根据字节码判断 + // 如果是中文,则清除输入的字符,否则保留 + s.delete(i,i+1); + } + } + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java new file mode 100644 index 0000000..21c767a --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BaseApi.java @@ -0,0 +1,237 @@ +package xyz.fycz.myreader.webapi; + +import xyz.fycz.myreader.application.SysManager; +import xyz.fycz.myreader.common.ErrorCode; +import xyz.fycz.myreader.entity.JsonModel; +import xyz.fycz.myreader.util.HttpUtil; +import xyz.fycz.myreader.util.JsonArrayToObjectArray; +import xyz.fycz.myreader.util.TextHelper; +import com.google.gson.Gson; +import xyz.fycz.myreader.callback.JsonCallback; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.source.HttpDataSource; + +import java.util.Map; + +/** + * Created by zhao on 2017/6/20. + */ + +public class BaseApi { + + /** + * post通用返回实体api + * @param url + * @param params + * @param c 返回的实体类型 + * @param callback + */ + public static void postCommonApi(String url, Map params, final Class c, final ResultCallback callback) { + HttpDataSource.httpPost(url, HttpUtil.makePostOutput(params), new JsonCallback() { + @Override + public void onFinish(JsonModel jsonModel) { + if (jsonModel.isSuccess()) { + callback.onFinish(new Gson().fromJson(jsonModel.getResult(),c), jsonModel.getError()); + } else { + noSuccess(jsonModel,callback); + } + } + @Override + public void onError(Exception e) { + error(e,callback); + } + }); + } + + /** + * post通用返回字符串api + * @param url + * @param params + * @param callback + */ + public static void postCommonReturnStringApi(String url, Map params, final ResultCallback callback) { + HttpDataSource.httpPost(url, HttpUtil.makePostOutput(params), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(o, code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + /** + * get通用返回字符串api + * 通过api获取蓝奏云直链 + * @param url + * @param params + * @param callback + */ + public static void postLanzousApi(String url, Map params, final ResultCallback callback, final String referer) { + HttpDataSource.httpPost(url, HttpUtil.makePostOutput(params), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(o, code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }, referer); + } + /** + * get通用返回实体api + * @param url + * @param params + * @param c 返回的实体类型 + * @param callback + */ + public static void getCommonApi(String url, Map params, final Class c, final ResultCallback callback) { + HttpDataSource.httpGet(HttpUtil.makeURL(url,params), new JsonCallback() { + @Override + public void onFinish(JsonModel jsonModel) { + if (jsonModel.isSuccess()) { + callback.onFinish(new Gson().fromJson(jsonModel.getResult(),c), jsonModel.getError()); + } else { + noSuccess(jsonModel,callback); + } + } + + @Override + public void onError(Exception e) { + error(e,callback); + } + }); + } + + /** + * get通用返回字符串api + * @param url + * @param params + * @param callback + */ + public static void getCommonReturnStringApi(String url, Map params, final ResultCallback callback) { + HttpDataSource.httpGet(HttpUtil.makeURL(url,params), new JsonCallback() { + @Override + public void onFinish(JsonModel jsonModel) { + if (jsonModel.isSuccess()) { + callback.onFinish(jsonModel.getResult(), jsonModel.getError()); + } else { + noSuccess(jsonModel, callback); + } + } + @Override + public void onError(Exception e) { + error(e,callback); + } + }); + } + /** + * get通用返回字符串api + * @param url + * @param params + * @param callback + */ + public static void getCommonReturnStringApi2(String url, Map params, final ResultCallback callback) { + HttpDataSource.httpGet(HttpUtil.makeURL(url, params), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(o,code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + /** + * get通用返回Html字符串api + * @param url + * @param params + * @param callback + */ + public static void getCommonReturnHtmlStringApi(String url, Map params, String charsetName, final ResultCallback callback) { + HttpDataSource.httpGet_html(HttpUtil.makeURL(url, params), charsetName, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(o,code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); +// error(e,callback); + } + }); + } + + + /** + * get通用获取实体列表api + * @param url + * @param params + * @param c 返回是列表实体类型 + * @param callback + */ + protected static void getCommonListApi(String url, Map params, final Class c, final ResultCallback callback) { + HttpDataSource.httpGet(HttpUtil.makeURL(url,params), new JsonCallback() { + @Override + public void onFinish(JsonModel jsonModel) { + if (jsonModel.isSuccess()) { + try { + callback.onFinish(JsonArrayToObjectArray.getArray(jsonModel.getResult(),c), jsonModel.getError()); + }catch (Exception e){ + callback.onError(e); + e.printStackTrace(); + } + } else { + noSuccess(jsonModel,callback); + } + } + + @Override + public void onError(Exception e) { + error(e,callback); + } + }); + } + + /** + * api异常处理 + * @param e + * @param callback + */ + private static void error(Exception e, final ResultCallback callback){ + /* if (e.toString().contains("SocketTimeoutException") || e.toString().contains("UnknownHostException")) { + TextHelper.showText("网络连接超时,请检查网络"); + }*/ + e.printStackTrace(); + callback.onError(e); + } + + /** + * api请求失败处理 + * @param jsonModel + * @param callback + */ + private static void noSuccess(JsonModel jsonModel, ResultCallback callback){ + if (!jsonModel.isSuccess()) { + if (jsonModel.getError() == ErrorCode.no_security) { + TextHelper.showText("登录过期,请重新登录"); + SysManager.logout(); + } else { + if (jsonModel.getError() == 0) { + callback.onFinish(jsonModel.getResult(), -1); + } else { + callback.onFinish(jsonModel.getResult(), jsonModel.getError()); + } + } + } + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java new file mode 100644 index 0000000..ce920f7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java @@ -0,0 +1,58 @@ +package xyz.fycz.myreader.webapi; + + +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.crawler.BiQuGeReadCrawler; + +/** + * Created by zhao on 2017/7/24. + */ + +public class BookStoreApi extends BaseApi{ + + + /** + * 获取书城小说分类列表 + * @param url + * @param callback + */ + public static void getBookTypeList(String url, final ResultCallback callback){ + + getCommonReturnHtmlStringApi(url, null, "GBK", new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(BiQuGeReadCrawler.getBookTypeList((String) o),0); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + + } + }); + } + + + /** + * 获取某一分类小说排行榜列表 + * @param url + * @param callback + */ + public static void getBookRankList(String url, final ResultCallback callback){ + + getCommonReturnHtmlStringApi(url, null, "GBK", new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(BiQuGeReadCrawler.getBookRankList((String) o),0); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + + } + }); + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java new file mode 100644 index 0000000..f757c58 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java @@ -0,0 +1,171 @@ +package xyz.fycz.myreader.webapi; + +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.crawler.*; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.greendao.entity.Book; + +import java.util.HashMap; +import java.util.Map; + + +public class CommonApi extends BaseApi { + + /** + * 获取章节列表 + * + * @param url + * @param callback + */ + public static void getBookChapters(String url, final ReadCrawler rc, final ResultCallback callback) { + String charset = rc.getCharset(); + getCommonReturnHtmlStringApi(url, null, charset, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(rc.getChaptersFromHtml((String) o), 0); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + + } + }); + } + + /** + * 获取章节正文 + * + * @param url + * @param callback + */ + + public static void getChapterContent(String url, final ReadCrawler rc, final ResultCallback callback) { + int tem = url.indexOf("\""); + if (tem != -1) { + url = url.substring(0, tem); + } + String charset = rc.getCharset(); + if (rc instanceof FYReadCrawler) { + if (url.contains("47.105.152.62")) { + url.replace("47.105.152.62", "novel.fycz.xyz"); + } + if (!url.contains("novel.fycz.xyz")) { + url = URLCONST.nameSpace_FY + url; + } + } + getCommonReturnHtmlStringApi(url, null, charset, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(rc.getContentFormHtml((String) o), 0); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + + /** + * 搜索小说 + * + * @param key + * @param callback + */ + + public static void search(String key, final ReadCrawler rc, final ResultCallback callback) { + Map params = new HashMap<>(); + String charset = "utf-8"; + if (rc instanceof TianLaiReadCrawler) { + charset = "utf-8"; + } else { + charset = rc.getCharset(); + } + params.put(rc.getSearchKey(), key); + if (rc instanceof PinShuReadCrawler) { + params.put("SearchClass", 1); + } + getCommonReturnHtmlStringApi(rc.getSearchLink(), params, charset, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(rc.getBooksFromSearchHtml((String) o), code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + /** + * 获取小说详细信息 + * + * @param book + * @param callback + */ + public static void getBookInfo(final Book book, final BookInfoCrawler bic, final ResultCallback callback) { + getCommonReturnHtmlStringApi(book.getChapterUrl(), null, bic.getCharset(), new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(bic.getBookInfo((String) o, book), 0); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + + } + }); + } + + public static void getNewestAppVersion(final ResultCallback callback) { + getCommonReturnStringApi(URLCONST.method_getCurAppVersion, null, callback); + } + + /** + * 通过api获取蓝奏云可下载直链 + * + * @param lanZouUrl + * @param callback + */ + public static void getUrl(final String lanZouUrl, final ResultCallback callback) { + LanZousApi.getUrl1(lanZouUrl, new ResultCallback() { + @Override + public void onFinish(final Object o, int code) { + LanZousApi.getKey((String) o, new ResultCallback() { + final String referer = (String) o; + @Override + public void onFinish(Object o, int code) { + LanZousApi.getUrl2((String) o, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + LanZousApi.getRedirectUrl((String) o, callback); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }, referer); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/LanZousApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/LanZousApi.java new file mode 100644 index 0000000..7064bad --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/LanZousApi.java @@ -0,0 +1,149 @@ +package xyz.fycz.myreader.webapi; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.URLCONST; +import xyz.fycz.myreader.util.SharedPreUtils; +import xyz.fycz.myreader.util.StringHelper; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; + +import static xyz.fycz.myreader.webapi.BaseApi.*; + +/** + * @author fengyue + * @date 2020/7/1 8:48 + */ +public class LanZousApi { + + /** + * 获取蓝奏云含有key的url + * + * @param lanZouUrl + * @param callback + */ + public static void getUrl1(String lanZouUrl, final ResultCallback callback) { + getCommonReturnHtmlStringApi(lanZouUrl, null, "utf-8", new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(getUrl1((String) o), code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + /** + * 获取key + * + * @param url + * @param callback + */ + public static void getKey(String url, final ResultCallback callback) { + getCommonReturnHtmlStringApi(url, null, "utf-8", new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(getKey((String) o), code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }); + } + + /** + * 获取蓝奏云直链 + * + * @param callback + */ + public static void getUrl2(String key, final ResultCallback callback, final String referer) { + HashMap params = new HashMap<>(); + params.put("action", "downprocess"); + params.put("sign", key); + params.put("ves", 1); + postLanzousApi(URLCONST.LAN_ZOUS_URL + "/ajaxm.php", params, new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + callback.onFinish(getUrl2((String) o), code); + } + + @Override + public void onError(Exception e) { + callback.onError(e); + } + }, referer); + } + + private static String getUrl1(String html) { + Document doc = Jsoup.parse(html); + return URLCONST.LAN_ZOUS_URL + doc.getElementsByClass("ifr2").attr("src"); + } + + private static String getKey(String html) { + SharedPreUtils spu = SharedPreUtils.getInstance(); + String lanzousKeyStart = "var pposturl = '"; + try { + lanzousKeyStart = spu.getString("lanzousKeyStart"); + }catch (Exception e){ + e.printStackTrace(); + } + return StringHelper.getSubString(html, lanzousKeyStart, "'"); + } + + private static String getUrl2(String o) { + String[] info = o.split(","); + String zt = info[0].substring(info[0].indexOf(":") + 1); + if (!"1".endsWith(zt)) { + return null; + } + String dom = info[1].substring(info[1].indexOf(":") + 2, info[1].lastIndexOf("\"")); + String url = info[2].substring(info[2].indexOf(":") + 2, info[2].lastIndexOf("\"")); + dom = dom.replace("\\", ""); + url = url.replace("\\", ""); + return dom + "/file/" + url; + } + + /** + * 获取重定向地址 + * + * @param path + * + */ + public static void getRedirectUrl(final String path, final ResultCallback callback) { + MyApplication.getApplication().newThread(() -> { + HttpURLConnection conn = null; + try { + conn = (HttpURLConnection) new URL(path) + .openConnection(); + conn.setInstanceFollowRedirects(false); + conn.setConnectTimeout(5000); + conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); + conn.setRequestProperty("Accept-Language", "zh-cn"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Accept", "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/x-silverlight, */*"); + conn.connect(); + String redirectUrl = conn.getHeaderField("Location"); +// Log.d("D/Http: RedirectUrl", redirectUrl); + callback.onFinish(redirectUrl, 1); + } catch (IOException e) { + callback.onError(e); + }finally { + if (conn != null) { + conn.disconnect(); + } + } + + }); + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/BadgeView.java b/app/src/main/java/xyz/fycz/myreader/widget/BadgeView.java new file mode 100644 index 0000000..a12f326 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/BadgeView.java @@ -0,0 +1,235 @@ +package xyz.fycz.myreader.widget; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; +import android.widget.TabWidget; + + +import androidx.appcompat.widget.AppCompatTextView; +import xyz.fycz.myreader.R; + + +/** + * Created by milad heydari on 5/6/2016. + */ +public class BadgeView extends AppCompatTextView { + + private boolean mHideOnNull = true; + private float radius; + + public BadgeView(Context context) { + this(context, null); + } + + public BadgeView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.textViewStyle); + } + + public BadgeView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(); + } + + private void init() { + if (!(getLayoutParams() instanceof LayoutParams)) { + LayoutParams layoutParams = + new LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.CENTER); + setLayoutParams(layoutParams); + } + + // set default font + setTextColor(Color.WHITE); + //setTypeface(Typeface.DEFAULT_BOLD); + setTextSize(TypedValue.COMPLEX_UNIT_SP, 11); + setPadding(dip2Px(5), dip2Px(1), dip2Px(5), dip2Px(1)); + radius = 8; + + // set default background + setBackground(radius, Color.parseColor("#d3321b")); + + setGravity(Gravity.CENTER); + + // default values + setHideOnNull(true); + setBadgeCount(0); + setMinWidth(dip2Px(16)); + setMinHeight(dip2Px(16)); + } + + public void setBackground(float dipRadius, int badgeColor) { + int radius = dip2Px(dipRadius); + float[] radiusArray = new float[]{radius, radius, radius, radius, radius, radius, radius, radius}; + + RoundRectShape roundRect = new RoundRectShape(radiusArray, null, null); + ShapeDrawable bgDrawable = new ShapeDrawable(roundRect); + bgDrawable.getPaint().setColor(badgeColor); + setBackground(bgDrawable); + } + + public void setBackground(int badgeColor) { + setBackground(radius, badgeColor); + } + + /** + * @return Returns true if view is hidden on badge value 0 or null; + */ + public boolean isHideOnNull() { + return mHideOnNull; + } + + /** + * @param hideOnNull the hideOnNull to set + */ + public void setHideOnNull(boolean hideOnNull) { + mHideOnNull = hideOnNull; + setText(getText()); + } + + /** + * @see android.widget.TextView#setText(CharSequence, android.widget.TextView.BufferType) + */ + @Override + public void setText(CharSequence text, BufferType type) { + if (isHideOnNull() && TextUtils.isEmpty(text)) { + setVisibility(GONE); + } else { + setVisibility(VISIBLE); + } + super.setText(text, type); + } + + public void setBadgeCount(int count) { + setText(String.valueOf(count)); + if (count == 0) { + setVisibility(GONE); + } + } + + public void setHighlight(boolean highlight) { + setBackground(getResources().getColor(highlight ? R.color.highlight : R.color.darker_gray)); + } + + public Integer getBadgeCount() { + if (getText() == null) { + return null; + } + String text = getText().toString(); + try { + return Integer.parseInt(text); + } catch (NumberFormatException e) { + return null; + } + } + + public void setBadgeGravity(int gravity) { + LayoutParams params = (LayoutParams) getLayoutParams(); + params.gravity = gravity; + setLayoutParams(params); + } + + public int getBadgeGravity() { + LayoutParams params = (LayoutParams) getLayoutParams(); + return params.gravity; + } + + public void setBadgeMargin(int dipMargin) { + setBadgeMargin(dipMargin, dipMargin, dipMargin, dipMargin); + } + + public void setBadgeMargin(int leftDipMargin, int topDipMargin, int rightDipMargin, int bottomDipMargin) { + LayoutParams params = (LayoutParams) getLayoutParams(); + params.leftMargin = dip2Px(leftDipMargin); + params.topMargin = dip2Px(topDipMargin); + params.rightMargin = dip2Px(rightDipMargin); + params.bottomMargin = dip2Px(bottomDipMargin); + setLayoutParams(params); + } + + public int[] getBadgeMargin() { + LayoutParams params = (LayoutParams) getLayoutParams(); + return new int[]{params.leftMargin, params.topMargin, params.rightMargin, params.bottomMargin}; + } + + public void incrementBadgeCount(int increment) { + Integer count = getBadgeCount(); + if (count == null) { + setBadgeCount(increment); + } else { + setBadgeCount(increment + count); + } + } + + public void decrementBadgeCount(int decrement) { + incrementBadgeCount(-decrement); + } + + /** + * Attach the BadgeView to the TabWidget + * + * @param target the TabWidget to attach the BadgeView + * @param tabIndex index of the tab + */ + public void setTargetView(TabWidget target, int tabIndex) { + View tabView = target.getChildTabViewAt(tabIndex); + setTargetView(tabView); + } + + /** + * Attach the BadgeView to the target view + * + * @param target the view to attach the BadgeView + */ + public void setTargetView(View target) { + if (getParent() != null) { + ((ViewGroup) getParent()).removeView(this); + } + + if (target == null) { + return; + } + + if (target.getParent() instanceof FrameLayout) { + ((FrameLayout) target.getParent()).addView(this); + + } else if (target.getParent() instanceof ViewGroup) { + // use a new Framelayout container for adding badge + ViewGroup parentContainer = (ViewGroup) target.getParent(); + int groupIndex = parentContainer.indexOfChild(target); + parentContainer.removeView(target); + + FrameLayout badgeContainer = new FrameLayout(getContext()); + ViewGroup.LayoutParams parentLayoutParams = target.getLayoutParams(); + + badgeContainer.setLayoutParams(parentLayoutParams); + target.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams); + badgeContainer.addView(target); + + badgeContainer.addView(this); + } + + } + + /** + * converts dip to px + */ + private int dip2Px(float dip) { + return (int) (dip * getContext().getResources().getDisplayMetrics().density + 0.5f); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/AnimationProvider.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/AnimationProvider.java new file mode 100644 index 0000000..8c51cd7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/AnimationProvider.java @@ -0,0 +1,98 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.widget.Scroller; + +/** + * Created by Administrator on 2016/8/1 0001. + */ +public abstract class AnimationProvider { + + public enum Direction { + NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false); + + public final boolean isHorizontal; + + Direction(boolean isHorizontal) { + this.isHorizontal = isHorizontal; + } + } + + protected Bitmap mCurPageBitmap,mNextPageBitmap; + protected float myStartX; + protected float myStartY; + protected int myEndX; + protected int myEndY; + protected Direction myDirection; + + protected int mScreenWidth; + protected int mScreenHeight; + + protected PointF mTouch = new PointF(); // 拖拽点 + private Direction direction = Direction.NONE; + private boolean isCancel = false; + + public AnimationProvider(int width,int height) { + mCurPageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + mNextPageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + + this.mScreenWidth = width; + this.mScreenHeight = height; + } + + //绘制滑动页面 + public abstract void drawMove(Canvas canvas); + + //绘制不滑动页面 + public abstract void drawStatic(Canvas canvas); + + //设置开始拖拽点 + public void setStartPoint(float x,float y){ + myStartX = x; + myStartY = y; + } + + //设置拖拽点 + public void setTouchPoint(float x,float y){ + mTouch.x = x; + mTouch.y = y; + } + + //设置方向 + public void setDirection(Direction direction){ + this.direction = direction; + } + + public Direction getDirection(){ + return direction; + } + + public void setCancel(boolean isCancel){ + this.isCancel = isCancel; + } + + public abstract void startAnimation(Scroller scroller); + + /** + * 转换页面,在显示下一章的时候,必须首先调用此方法 + */ + public void changePage(){ + Bitmap bitmap = mCurPageBitmap; + mCurPageBitmap = mNextPageBitmap; + mNextPageBitmap = bitmap; + } + + public Bitmap getNextBitmap(){ + return mNextPageBitmap; + } + + public Bitmap getBgBitmap(){ + return mNextPageBitmap; + } + + public boolean getCancel(){ + return isCancel; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/animation/CoverPageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/CoverPageAnim.java new file mode 100644 index 0000000..df9167a --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/CoverPageAnim.java @@ -0,0 +1,100 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.view.View; + +/** + * Created by newbiechen on 17-7-24. + */ + +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 drawStatic(Canvas canvas) { + if (isCancel){ + mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true); + canvas.drawBitmap(mCurBitmap, 0, 0, null); + }else { + canvas.drawBitmap(mNextBitmap, 0, 0, null); + } + } + + @Override + public void drawMove(Canvas canvas) { + + switch (mDirection){ + case NEXT: + int dis = (int) (mViewWidth - mStartX + mTouchX); + if (dis > mViewWidth){ + dis = mViewWidth; + } + //计算bitmap截取的区域 + mSrcRect.left = mViewWidth - dis; + //计算bitmap在canvas显示的区域 + mDestRect.right = dis; + canvas.drawBitmap(mNextBitmap,0,0,null); + canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null); + addShadow(dis,canvas); + break; + default: + mSrcRect.left = (int) (mViewWidth - mTouchX); + mDestRect.right = (int) mTouchX; + canvas.drawBitmap(mCurBitmap,0,0,null); + canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null); + addShadow((int) mTouchX,canvas); + break; + } + } + + //添加阴影 + public void addShadow(int left, Canvas canvas) { + mBackShadowDrawableLR.setBounds(left, 0, left + 30 , mScreenHeight); + mBackShadowDrawableLR.draw(canvas); + } + + @Override + public void startAnim() { + super.startAnim(); + int dx = 0; + switch (mDirection){ + case NEXT: + if (isCancel){ + int dis = (int) ((mViewWidth - mStartX) + mTouchX); + if (dis > mViewWidth){ + dis = mViewWidth; + } + dx = mViewWidth - dis; + }else{ + dx = (int) -(mTouchX + (mViewWidth - mStartX)); + } + break; + default: + if (isCancel){ + dx = (int) -mTouchX; + }else{ + dx = (int) (mViewWidth - mTouchX); + } + break; + } + + //滑动速度保持一致 + int duration = (400 * Math.abs(dx)) / mViewWidth; + mScroller.startScroll((int) mTouchX, 0, dx, 0, 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 new file mode 100644 index 0000000..83b9c8c --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/HorizonPageAnim.java @@ -0,0 +1,230 @@ +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; + +/** + * Created by newbiechen on 17-7-24. + * 横向动画的模板 + */ + +public abstract class HorizonPageAnim 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 HorizonPageAnim(int w, int h, View view, OnPageChangeListener listener) { + this(w, h, 0, 0, view, listener); + } + + public HorizonPageAnim(int w, int h, int marginWidth, int marginHeight, + View view, OnPageChangeListener listener) { + super(w, h, marginWidth, marginHeight, 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 (x - mStartX > 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 (x - mMoveX > 0){ + isCancel = true; + }else { + isCancel = false; + } + }else{ + if (x - mMoveX < 0){ + isCancel = true; + }else { + isCancel = false; + } + } + } + + mMoveX = x; + mMoveY = y; + isRunning = true; + mView.invalidate(); + } + break; + case MotionEvent.ACTION_UP: + if (!isMove){ + if (x < mScreenWidth / 2){ + isNext = false; + }else{ + isNext = true; + } + + 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/animation/NonePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/NonePageAnim.java new file mode 100644 index 0000000..175b704 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/NonePageAnim.java @@ -0,0 +1,37 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Canvas; +import android.view.View; + +/** + * Created by newbiechen on 17-7-24. + */ + +public class NonePageAnim extends HorizonPageAnim{ + + public NonePageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + } + + @Override + public void drawStatic(Canvas canvas) { + if (isCancel){ + canvas.drawBitmap(mCurBitmap, 0, 0, null); + }else { + canvas.drawBitmap(mNextBitmap, 0, 0, null); + } + } + + @Override + public void drawMove(Canvas canvas) { + if (isCancel){ + canvas.drawBitmap(mCurBitmap, 0, 0, null); + }else { + canvas.drawBitmap(mNextBitmap, 0, 0, null); + } + } + + @Override + public void startAnim() { + } +} 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 new file mode 100644 index 0000000..742a410 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/PageAnimation.java @@ -0,0 +1,157 @@ +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.animation.LinearInterpolator; +import android.widget.Scroller; + +/** + * Created by newbiechen on 17-7-24. + * 翻页动画抽象类 + */ + +public abstract class PageAnimation { + //正在使用的View + protected View mView; + //滑动装置 + protected Scroller mScroller; + //监听器 + protected OnPageChangeListener mListener; + //移动方向 + protected Direction mDirection = Direction.NONE; + + protected boolean isRunning = false; + + //屏幕的尺寸 + protected int mScreenWidth; + protected int mScreenHeight; + //屏幕的间距 + protected int mMarginWidth; + protected int mMarginHeight; + //视图的尺寸 + protected int mViewWidth; + protected int mViewHeight; + //起始点 + protected float mStartX; + protected float mStartY; + //触碰点 + protected float mTouchX; + protected float mTouchY; + //上一个触碰点 + protected float mLastX; + protected float mLastY; + + public PageAnimation(int w, int h, View view, OnPageChangeListener listener){ + this(w, h, 0, 0, view,listener); + } + + public PageAnimation(int w, int h, int marginWidth, int marginHeight, View view, OnPageChangeListener listener){ + mScreenWidth = w; + mScreenHeight = h; + + mMarginWidth = marginWidth; + mMarginHeight = marginHeight; + + mViewWidth = mScreenWidth - mMarginWidth * 2; + mViewHeight = mScreenHeight - mMarginHeight * 2; + + mView = view; + mListener = listener; + + mScroller = new Scroller(mView.getContext(), new LinearInterpolator()); + } + + 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 boolean isRunning(){ + return isRunning; + } + + /** + * 开启翻页动画 + */ + public void startAnim(){ + if (isRunning){ + return; + } + isRunning = true; + } + + public void setDirection(Direction direction){ + mDirection = direction; + } + + public Direction getDirection(){ + return mDirection; + } + + public void clear(){ + mView = null; + } + /** + * 点击事件的处理 + * @param event + */ + public abstract boolean onTouchEvent(MotionEvent event); + + /** + * 绘制图形 + * @param canvas + */ + public abstract void draw(Canvas canvas); + + /** + * 滚动动画 + * 必须放在computeScroll()方法中执行 + */ + public abstract void scrollAnim(); + + /** + * 取消动画 + */ + public abstract void abortAnim(); + + /** + * 获取背景板 + * @return + */ + public abstract Bitmap getBgBitmap(); + + /** + * 获取内容显示版面 + */ + public abstract Bitmap getNextBitmap(); + + public enum Direction { + NONE(true),NEXT(true), PRE(true), UP(false), DOWN(false); + + public final boolean isHorizontal; + + Direction(boolean isHorizontal) { + this.isHorizontal = isHorizontal; + } + } + + public interface OnPageChangeListener { + boolean hasPrev(); + boolean hasNext(); + void pageCancel(); + } + +} 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 new file mode 100644 index 0000000..3914acb --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/ScrollPageAnim.java @@ -0,0 +1,411 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Created by newbiechen on 17-7-23. + * 原理:仿照ListView源码实现的上下滑动效果 + * Alter by: zeroAngus + *

+ * 问题: + * 1. 向上翻页,重复的问题 (完成) + * 2. 滑动卡顿的问题。原因:由于绘制的数据过多造成的卡顿问题。 (主要是文字绘制需要的时长比较多) 解决办法:做文字缓冲 + * 3. 弱网环境下,显示的问题 + */ +public class ScrollPageAnim extends PageAnimation { + private static final String TAG = "ScrollAnimation"; + //动画速度 + static final int animationSpeed = 300; + // 滑动追踪的时间 + private static final int VELOCITY_DURATION = 1000; + private VelocityTracker mVelocity; + + // 整个Bitmap的背景显示 + private Bitmap mBgBitmap; + + // 下一个展示的图片 + private Bitmap mNextBitmap; + + // 被废弃的图片列表 + private ArrayDeque mScrapViews; + // 正在被利用的图片列表 + private ArrayList mActiveViews = new ArrayList<>(2); + + // 是否处于刷新阶段 + private boolean isRefresh = true; + + public ScrollPageAnim(int w, int h, int marginWidth, int marginHeight, + View view, OnPageChangeListener listener) { + super(w, h, marginWidth, marginHeight, view, listener); + // 创建两个BitmapView + initWidget(); + } + + private void initWidget() { + mBgBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.RGB_565); + + mScrapViews = new ArrayDeque<>(2); + for (int i = 0; i < 2; ++i) { + BitmapView view = new BitmapView(); + view.bitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Bitmap.Config.RGB_565); + view.srcRect = new Rect(0, 0, mViewWidth, mViewHeight); + view.destRect = new Rect(0, 0, mViewWidth, mViewHeight); + view.top = 0; + view.bottom = view.bitmap.getHeight(); + + mScrapViews.push(view); + } + onLayout(); + isRefresh = false; + } + + // 修改布局,填充内容 + private void onLayout() { + // 如果还没有开始加载,则从上到下进行绘制 + if (mActiveViews.size() == 0) { + fillDown(0, 0); + mDirection = Direction.NONE; + } else { + int offset = (int) (mTouchY - mLastY); + // 判断是下滑还是上拉 (下滑) + if (offset > 0) { + int topEdge = mActiveViews.get(0).top; + fillUp(topEdge, offset); + } + // 上拉 + else { + // 底部的距离 = 当前底部的距离 + 滑动的距离 (因为上滑,得到的值肯定是负的) + int bottomEdge = mActiveViews.get(mActiveViews.size() - 1).bottom; + fillDown(bottomEdge, offset); + } + } + } + + // 底部填充 + private Iterator downIt; + + /** + * 创建View填充底部空白部分 + * + * @param bottomEdge :当前最后一个View的底部,在整个屏幕上的位置,即相对于屏幕顶部的距离 + * @param offset :滑动的偏移量 + */ + private void fillDown(int bottomEdge, int offset) { + + downIt = mActiveViews.iterator(); + BitmapView view; + + // 进行删除 + while (downIt.hasNext()) { + view = downIt.next(); + view.top = view.top + offset; + view.bottom = view.bottom + offset; + // 设置允许显示的范围 + view.destRect.top = view.top; + view.destRect.bottom = view.bottom; + + // 判断是否越界了 + if (view.bottom <= 0) { + // 添加到废弃的View中 + mScrapViews.add(view); + // 从Active中移除 + downIt.remove(); + // 如果原先是从上加载,现在变成从下加载,则表示取消 + if (mDirection == Direction.UP) { + mListener.pageCancel(); + mDirection = Direction.NONE; + } + } + } + + // 滑动之后的最后一个 View 的距离屏幕顶部上的实际位置 + int realEdge = bottomEdge + offset; + + // 进行填充 + while (realEdge < mViewHeight && mActiveViews.size() < 2) { + // 从废弃的Views中获取一个 + view = mScrapViews.getFirst(); +/* //擦除其Bitmap(重新创建会不会更好一点) + eraseBitmap(view.bitmap,view.bitmap.getWidth(),view.bitmap.getHeight(),0,0);*/ + if (view == null) return; + + Bitmap cancelBitmap = mNextBitmap; + mNextBitmap = view.bitmap; + + if (!isRefresh) { + boolean hasNext = mListener.hasNext(); //如果不成功则无法滑动 + + // 如果不存在next,则进行还原 + if (!hasNext) { + mNextBitmap = cancelBitmap; + for (BitmapView activeView : mActiveViews) { + activeView.top = 0; + activeView.bottom = mViewHeight; + // 设置允许显示的范围 + activeView.destRect.top = activeView.top; + activeView.destRect.bottom = activeView.bottom; + } + abortAnim(); + return; + } + } + + // 如果加载成功,那么就将View从ScrapViews中移除 + mScrapViews.removeFirst(); + // 添加到存活的Bitmap中 + mActiveViews.add(view); + mDirection = Direction.DOWN; + + // 设置Bitmap的范围 + view.top = realEdge; + view.bottom = realEdge + view.bitmap.getHeight(); + // 设置允许显示的范围 + view.destRect.top = view.top; + view.destRect.bottom = view.bottom; + + realEdge += view.bitmap.getHeight(); + } + } + + private Iterator upIt; + + /** + * 创建View填充顶部空白部分 + * + * @param topEdge : 当前第一个View的顶部,到屏幕顶部的距离 + * @param offset : 滑动的偏移量 + */ + private void fillUp(int topEdge, int offset) { + // 首先进行布局的调整 + upIt = mActiveViews.iterator(); + BitmapView view; + while (upIt.hasNext()) { + view = upIt.next(); + view.top = view.top + offset; + view.bottom = view.bottom + offset; + //设置允许显示的范围 + view.destRect.top = view.top; + view.destRect.bottom = view.bottom; + + // 判断是否越界了 + if (view.top >= mViewHeight) { + // 添加到废弃的View中 + mScrapViews.add(view); + // 从Active中移除 + upIt.remove(); + + // 如果原先是下,现在变成从上加载了,则表示取消加载 + + if (mDirection == Direction.DOWN) { + mListener.pageCancel(); + mDirection = Direction.NONE; + } + } + } + + // 滑动之后,第一个 View 的顶部距离屏幕顶部的实际位置。 + int realEdge = topEdge + offset; + + // 对布局进行View填充 + while (realEdge > 0 && mActiveViews.size() < 2) { + // 从废弃的Views中获取一个 + view = mScrapViews.getFirst(); + if (view == null) return; + + // 判断是否存在上一章节 + Bitmap cancelBitmap = mNextBitmap; + mNextBitmap = view.bitmap; + if (!isRefresh) { + boolean hasPrev = mListener.hasPrev(); // 如果不成功则无法滑动 + // 如果不存在next,则进行还原 + if (!hasPrev) { + mNextBitmap = cancelBitmap; + for (BitmapView activeView : mActiveViews) { + activeView.top = 0; + activeView.bottom = mViewHeight; + // 设置允许显示的范围 + activeView.destRect.top = activeView.top; + activeView.destRect.bottom = activeView.bottom; + } + abortAnim(); + return; + } + } + // 如果加载成功,那么就将View从ScrapViews中移除 + mScrapViews.removeFirst(); + // 加入到存活的对象中 + mActiveViews.add(0, view); + mDirection = Direction.UP; + // 设置Bitmap的范围 + view.top = realEdge - view.bitmap.getHeight(); + view.bottom = realEdge; + + // 设置允许显示的范围 + view.destRect.top = view.top; + view.destRect.bottom = view.bottom; + realEdge -= view.bitmap.getHeight(); + } + } + + /** + * 对Bitmap进行擦除 + * + * @param b + * @param width + * @param height + * @param paddingLeft + * @param paddingTop + */ + private void eraseBitmap(Bitmap b, int width, int height, + int paddingLeft, int paddingTop) { + /* if (mInitBitmapPix == null) return; + b.setPixels(mInitBitmapPix, 0, width, paddingLeft, paddingTop, width, height);*/ + } + + /** + * 重置位移 + */ + public void resetBitmap() { + isRefresh = true; + // 将所有的Active加入到Scrap中 + for (BitmapView view : mActiveViews) { + mScrapViews.add(view); + } + // 清除所有的Active + mActiveViews.clear(); + // 重新进行布局 + onLayout(); + isRefresh = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + 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: + isRunning = false; + // 设置起始点 + setStartPoint(x, y); + // 停止动画 + abortAnim(); + break; + case MotionEvent.ACTION_MOVE: + mVelocity.computeCurrentVelocity(VELOCITY_DURATION); + isRunning = true; + // 进行刷新 + mView.postInvalidate(); + break; + case MotionEvent.ACTION_UP: + isRunning = false; + // 开启动画 + startAnim(); + // 删除检测器 + mVelocity.recycle(); + mVelocity = null; + break; + + case MotionEvent.ACTION_CANCEL: + try { + mVelocity.recycle(); // if velocityTracker won't be used should be recycled + mVelocity = null; + } catch (Exception e) { + e.printStackTrace(); + } + break; + } + return true; + } + + + BitmapView tmpView; + + @Override + public void draw(Canvas canvas) { + //进行布局 + onLayout(); + + //绘制背景 + canvas.drawBitmap(mBgBitmap, 0, 0, null); + //绘制内容 + canvas.save(); + //移动位置 + canvas.translate(0, mMarginHeight); + //裁剪显示区域 + canvas.clipRect(0, 0, mViewWidth, mViewHeight); +/* //设置背景透明 + canvas.drawColor(0x40);*/ + //绘制Bitmap + for (int i = 0; i < mActiveViews.size(); ++i) { + tmpView = mActiveViews.get(i); + canvas.drawBitmap(tmpView.bitmap, tmpView.srcRect, tmpView.destRect, null); + } + canvas.restore(); + } + + @Override + public synchronized void startAnim() { + isRunning = true; + mScroller.fling(0, (int) mTouchY, 0, (int) mVelocity.getYVelocity() + , 0, 0, Integer.MAX_VALUE * -1, Integer.MAX_VALUE); + } + + + @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; + } + } + + @Override + public Bitmap getBgBitmap() { + return mBgBitmap; + } + + @Override + public Bitmap getNextBitmap() { + return mNextBitmap; + } + + private static class BitmapView { + Bitmap bitmap; + Rect srcRect; + Rect destRect; + int top; + int bottom; + } +} 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 new file mode 100644 index 0000000..2b85402 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SimulationPageAnim.java @@ -0,0 +1,659 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.*; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.view.View; + +/** + * Created by newbiechen on 17-7-24. + */ + +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 mIsRTandLB; // 是否属于右上左下 + private float mMaxLength; + 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; + + // 适配 android 高版本无法使用 XOR 的问题 + private Path mXORPath; + + public SimulationPageAnim(int w, int h, View view, OnPageChangeListener listener) { + super(w, h, view, listener); + mPath0 = new Path(); + mPath1 = new Path(); + mXORPath = 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 drawMove(Canvas canvas) { + switch (mDirection) { + case NEXT: + calcPoints(); + drawCurrentPageArea(canvas, mCurBitmap, mPath0); + drawNextPageAreaAndShadow(canvas, mNextBitmap); + drawCurrentPageShadow(canvas); + drawCurrentBackArea(canvas, mCurBitmap); + break; + default: + calcPoints(); + drawCurrentPageArea(canvas, mNextBitmap, mPath0); + drawNextPageAreaAndShadow(canvas, mCurBitmap); + drawCurrentPageShadow(canvas); + drawCurrentBackArea(canvas, mNextBitmap); + break; + } + } + + @Override + public void drawStatic(Canvas canvas) { + if (isCancel) { + mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true); + canvas.drawBitmap(mCurBitmap, 0, 0, null); + } else { + canvas.drawBitmap(mNextBitmap, 0, 0, null); + } + } + + @Override + public void startAnim() { + super.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, 400); + } + + @Override + public void setDirection(Direction direction) { + super.setDirection(direction); + + switch (direction) { + case PRE: + //上一页滑动不出现对角 + if (mStartX > mScreenWidth / 2) { + calcCornerXY(mStartX, mScreenHeight); + } else { + calcCornerXY(mScreenWidth - mStartX, mScreenHeight); + } + break; + case NEXT: + if (mScreenWidth / 2 > mStartX) { + calcCornerXY(mScreenWidth - mStartX, mStartY); + } + break; + } + } + + @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 && mStartY < mScreenHeight * 2 / 3) || mDirection.equals(Direction.PRE)) { + mTouchY = mScreenHeight; + } + + if (mStartY > mScreenHeight / 3 && mStartY < mScreenHeight / 2 && mDirection.equals(Direction.NEXT)) { + mTouchY = 1; + } + } + + /** + * 创建阴影的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); + + // 背面颜色组 + int[] 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); + + // 前面颜色组 + int[] 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); + } + + /** + * 是否能够拖动过去 + * + * @return + */ + public boolean canDragOver() { + return mTouchToCornerDis > mScreenWidth / 10; + } + + public boolean right() { + return mCornerX <= -4; + } + + /** + * 绘制翻起页背面 + * + * @param canvas + * @param bitmap + */ + 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 (mIsRTandLB) { + 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); + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception e) { + } + + mPaint.setColorFilter(mColorMatrixFilter); + //对Bitmap进行取色 + int color = bitmap.getPixel(1, 1); + //获取对应的三色 + int red = (color & 0xff0000) >> 16; + int green = (color & 0x00ff00) >> 8; + int blue = (color & 0x0000ff); + //转换成含有透明度的颜色 + int tempColor = Color.argb(200, red, green, blue); + + + 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); + //背景叠加 + canvas.drawColor(tempColor); + + 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(); + } + + /** + * 绘制翻起页的阴影 + * + * @param canvas + */ + private void drawCurrentPageShadow(Canvas canvas) { + double degree; + if (mIsRTandLB) { + 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 (mIsRTandLB) { + 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(); + float rotateDegrees; + canvas.save(); + try { + if (Build.VERSION.SDK_INT >= 28) { + mXORPath.reset(); + mXORPath.moveTo(0f, 0f); + mXORPath.lineTo(canvas.getWidth(), 0f); + mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); + mXORPath.lineTo(0f, canvas.getHeight()); + mXORPath.close(); + + // 取 path 的补集,作为 canvas 的交集 + mXORPath.op(mPath0, Path.Op.XOR); + canvas.clipPath(mXORPath); + } else { + canvas.clipPath(mPath0, Region.Op.XOR); + } + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception e) { + // TODO: handle exception + } + + int leftx; + int rightx; + GradientDrawable mCurrentPageShadow; + if (mIsRTandLB) { + leftx = (int) (mBezierControl1.x); + rightx = (int) mBezierControl1.x + 25; + mCurrentPageShadow = mFrontShadowDrawableVLR; + } else { + leftx = (int) (mBezierControl1.x - 25); + rightx = (int) mBezierControl1.x + 1; + mCurrentPageShadow = mFrontShadowDrawableVRL; + } + + 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 >= 28) { + mXORPath.reset(); + mXORPath.moveTo(0f, 0f); + mXORPath.lineTo(canvas.getWidth(), 0f); + mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); + mXORPath.lineTo(0f, canvas.getHeight()); + mXORPath.close(); + + // 取 path 的补给,作为 canvas 的交集 + mXORPath.op(mPath0, Path.Op.XOR); + canvas.clipPath(mXORPath); + } else { + canvas.clipPath(mPath0, Region.Op.XOR); + } + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception e) { + } + + if (mIsRTandLB) { + 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 (mIsRTandLB) { //左下及右上 + 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); + canvas.clipPath(mPath1, Region.Op.INTERSECT); + } catch (Exception e) { + } + + + 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, Path path) { + 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 >= 28) { + mXORPath.reset(); + mXORPath.moveTo(0f, 0f); + mXORPath.lineTo(canvas.getWidth(), 0f); + mXORPath.lineTo(canvas.getWidth(), canvas.getHeight()); + mXORPath.lineTo(0f, canvas.getHeight()); + mXORPath.close(); + + // 取 path 的补给,作为 canvas 的交集 + mXORPath.op(path, Path.Op.XOR); + canvas.clipPath(mXORPath); + } else { + canvas.clipPath(path, Region.Op.XOR); + } + canvas.drawBitmap(bitmap, 0, 0, null); + try { + canvas.restore(); + } catch (Exception e) { + + } + + } + + /** + * 计算拖拽点对应的拖拽脚 + * + * @param x + * @param y + */ + public void calcCornerXY(float x, float y) { + if (x <= mScreenWidth / 2) { + mCornerX = 0; + } else { + mCornerX = mScreenWidth; + } + if (y <= mScreenHeight / 2) { + mCornerY = 0; + } else { + mCornerY = mScreenHeight; + } + + if ((mCornerX == 0 && mCornerY == mScreenHeight) + || (mCornerX == mScreenWidth && mCornerY == 0)) { + mIsRTandLB = true; + } else { + mIsRTandLB = false; + } + + } + + 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; + + float f4 = mCornerY - mMiddleY; + if (f4 == 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; + + // 当mBezierStart1.x < 0或者mBezierStart1.x > 480时 + // 如果继续翻页,会出现BUG故在此限制 + 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的交点坐标 + * + * @param P1 + * @param P2 + * @param P3 + * @param P4 + * @return + */ + 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/animation/SlidePageAnim.java b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java new file mode 100644 index 0000000..c8ceba9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/animation/SlidePageAnim.java @@ -0,0 +1,101 @@ +package xyz.fycz.myreader.widget.animation; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +/** + * Created by newbiechen on 17-7-24. + */ + +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 drawStatic(Canvas canvas) { + if (isCancel){ + canvas.drawBitmap(mCurBitmap, 0, 0, null); + }else { + canvas.drawBitmap(mNextBitmap, 0, 0, null); + } + } + + @Override + public void drawMove(Canvas canvas) { + int dis = 0; + switch (mDirection){ + case 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(mNextBitmap,mNextSrcRect,mNextDestRect,null); + canvas.drawBitmap(mCurBitmap,mSrcRect,mDestRect,null); + break; + default: + 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(mCurBitmap,mNextSrcRect,mNextDestRect,null); + canvas.drawBitmap(mNextBitmap,mSrcRect,mDestRect,null); + break; + } + } + + @Override + public void startAnim() { + super.startAnim(); + int dx = 0; + switch (mDirection){ + case NEXT: + if (isCancel){ + int dis = (int)((mScreenWidth - mStartX) + mTouchX); + if (dis > mScreenWidth){ + dis = mScreenWidth; + } + dx = mScreenWidth - dis; + }else{ + dx = (int) -(mTouchX + (mScreenWidth - mStartX)); + } + break; + default: + if (isCancel){ + dx = (int)-Math.abs(mTouchX - mStartX); + }else{ + dx = (int) (mScreenWidth - (mTouchX - mStartX)); + } + break; + } + //滑动速度保持一致 + int duration = (400 * Math.abs(dx)) / mScreenWidth; + mScroller.startScroll((int) mTouchX, 0, dx, 0, duration); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java new file mode 100644 index 0000000..9a1cff2 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java @@ -0,0 +1,447 @@ +package xyz.fycz.myreader.widget.page; + +import android.util.Log; +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.application.MyApplication; +import xyz.fycz.myreader.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.entity.Void; +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 java.io.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 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 collBook, ChapterService mChapterService, Setting setting) { + super(pageView, collBook, setting); + mStatus = STATUS_PARING; + this.mChapterService = mChapterService; + } + + + /*private List convertTxtChapter(List bookChapters) { + List txtChapters = new ArrayList<>(bookChapters.size()); + for (BookChapterBean bean : bookChapters) { + TxtChapter chapter = new TxtChapter(); + chapter.title = bean.getTitle(); + chapter.start = bean.getStart(); + chapter.end = bean.getEnd(); + txtChapters.add(chapter); + } + return txtChapters; + }*/ + + /** + * 未完成的部分: + * 1. 序章的添加 + * 2. 章节存在的书本的虚拟分章效果 + */ + public void loadChapters(final ResultCallback resultCallback) { + MyApplication.getApplication().newThread(() -> { + mBookFile = new File(mCollBook.getChapterUrl()); + //获取文件编码 + mCharset = FileUtils.getFileEncode(mBookFile.getAbsolutePath()); + List chapters = new ArrayList<>(); + RandomAccessFile bookStream = null; + boolean hasChapter = false; + try { + //获取文件流 + 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) { + //创建序章 + TxtChapter preChapter = new TxtChapter(); + preChapter.title = "序章"; + preChapter.start = 0; + preChapter.end = chapterContent.getBytes(mCharset).length; //获取String的byte值,作为最终值 + + //如果序章大小大于30才添加进去 + if (preChapter.end - preChapter.start > 30) { + chapters.add(preChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = preChapter.end; + chapters.add(curChapter); + } + //否则就block分割之后,上一个章节的剩余内容 + else { + //获取上一章节 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + //将当前段落添加上一章去 + lastChapter.end += chapterContent.getBytes(mCharset).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + 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(); + + //获取上一章节 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = lastChapter.start + chapterContent.getBytes(mCharset).length; + + //如果章节内容太小,则移除 + if (lastChapter.end - lastChapter.start < 30) { + chapters.remove(lastChapter); + } + + //创建当前章节 + TxtChapter curChapter = new TxtChapter(); + curChapter.title = matcher.group(); + curChapter.start = lastChapter.end; + chapters.add(curChapter); + } + //如果章节不存在则创建章节 + else { + TxtChapter curChapter = new TxtChapter(); + 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; + } + } + TxtChapter chapter = new TxtChapter(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + end; + chapters.add(chapter); + //减去已经被分配的长度 + strLength = strLength - (end - chapterOffset); + //设置偏移的位置 + chapterOffset = end; + } else { + TxtChapter chapter = new TxtChapter(); + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")"; + chapter.start = curOffset + chapterOffset + 1; + chapter.end = curOffset + length; + chapters.add(chapter); + strLength = 0; + } + } + } + + //block的偏移点 + curOffset += length; + + if (hasChapter) { + //设置上一章的结尾 + TxtChapter lastChapter = chapters.get(chapters.size() - 1); + lastChapter.end = curOffset; + } + + //当添加的block太多的时候,执行GC + if (blockPos % 15 == 0) { + System.gc(); + System.runFinalization(); + } + } + List mChapters = new ArrayList<>(); + int i = 0; + for (TxtChapter txtChapter : chapters) { + Chapter chapter = new Chapter(); + chapter.setBookId(mCollBook.getId()); + chapter.setTitle(txtChapter.getTitle().replace("\n", "").replaceAll("^\\s+|\\s+$", "")); + chapter.setId(StringHelper.getStringRandom(25)); + String content = getChapterContent(txtChapter); + if (StringHelper.isEmpty(content)) { + continue; + } + chapter.setNumber(i++); + mChapterService.saveOrUpdateChapter(chapter, content); + mChapters.add(chapter); + } + if (resultCallback != null) { + resultCallback.onFinish(mChapters, 1); + } + } catch (IOException e) { + e.printStackTrace(); + if (resultCallback != null) { + resultCallback.onError(e); + } + } finally { + IOUtils.close(bookStream); + System.gc(); + System.runFinalization(); + } + + }); + + } + + /** + * 从文件中提取一章的内容 + * + * @param chapter + * @return + */ + private String getChapterContent(TxtChapter 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(mCollBook.getChapterUrl()); + + // 判断文件是否已经加载过,并具有缓存 + if (mCollBook.getChapterTotalNum() != 0) { + + mChapterList = mChapterService.findBookAllChapterByBookId(mCollBook.getId()); + isChapterListPrepare = true; + + //提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 加载并显示当前章节 + openChapter(); + + return; + } + + // 通过RxJava异步处理分章事件 + Single.create((SingleOnSubscribe) e -> { + loadChapters(null); + e.onSuccess(new Void()); + }).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + mChapterDisp = d; + } + + @Override + public void onSuccess(Void value) { + mChapterDisp = null; + isChapterListPrepare = true; + + // 提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + mCollBook.setChapterTotalNum(mChapterList.size()); + + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Throwable e) { + chapterError(); + Log.d(TAG, "file load error:" + e.toString()); + } + }); + /*loadChapters(new ResultCallback() { + @Override + public void onFinish(Object o, int code) { + isChapterListPrepare = true; + + // 提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Exception e) { + chapterError(); + Log.d(TAG, "file load error:" + e.toString()); + } + });*/ + } + + @Override + protected BufferedReader getChapterReader(Chapter chapter) throws Exception { + File file = new File(APPCONST.BOOK_CACHE_PATH + mCollBook.getId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY); + if (!file.exists()) return null; + return new BufferedReader(new FileReader(file)); + } + + + @Override + protected boolean hasChapterData(Chapter chapter) { + return ChapterService.isChapterCached(mCollBook.getId(), chapter.getTitle()); + } + + + @Override + public void getChapterContent(Chapter chapter) { + + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java new file mode 100644 index 0000000..cb1749e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java @@ -0,0 +1,257 @@ +package xyz.fycz.myreader.widget.page; + + +import xyz.fycz.myreader.callback.ResultCallback; +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.StringHelper; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.webapi.CommonApi; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by newbiechen on 17-5-29. + * 网络页面加载器 + */ + +public class NetPageLoader extends PageLoader { + private static final String TAG = "PageFactory"; + private ChapterService mChapterService; + private ReadCrawler mReadCrawler; + + public NetPageLoader(PageView pageView, Book collBook, ChapterService mChapterService, + ReadCrawler mReadCrawler, Setting setting) { + super(pageView, collBook, setting); + this.mChapterService = mChapterService; + this.mReadCrawler = mReadCrawler; + } + + /*private List convertTxtChapter(List bookChapters) { + List txtChapters = new ArrayList<>(bookChapters.size()); + for (Chapter bean : bookChapters) { + TxtChapter chapter = new TxtChapter(); + chapter.setBookId(bean.getBookId()); + chapter.setTitle(bean.getTitle()); + chapter.setLink(bean.getUrl()); + txtChapters.add(chapter); + } + return txtChapters; + }*/ + + @Override + public void refreshChapterList() { + List chapters = mChapterService.findBookAllChapterByBookId(mCollBook.getId()); + if (chapters == null) return; + + // 将 BookChapter 转换成当前可用的 Chapter +// mChapterList = convertTxtChapter(chapters); + mChapterList = chapters; + isChapterListPrepare = true; + + // 目录加载完成,执行回调操作。 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 如果章节未打开 + if (!isChapterOpen()) { + // 打开章节 + openChapter(); + } + } + + @Override + protected BufferedReader getChapterReader(Chapter chapter) throws FileNotFoundException { + File file = new File(APPCONST.BOOK_CACHE_PATH + mCollBook.getId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY); + if (!file.exists()) return null; + BufferedReader br = new BufferedReader(new FileReader(file)); + /*try { + if (StringHelper.isEmpty(br.readLine())){ + getChapterContent(chapter); + assert chapter.getContent() != null; + //保证chapter更新完毕,暂时先这么用着 + for (int i = 0; i < 5; i++) { + if (!StringHelper.isEmpty(chapter.getContent())){ + break; + } + Thread.sleep(1000); + } + return br; + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + }*/ + return br; + } + + @Override + protected boolean hasChapterData(Chapter chapter) { + return ChapterService.isChapterCached(mCollBook.getId(), chapter.getTitle()); + } + + // 装载上一章节的内容 + @Override + boolean parsePrevChapter() { + boolean isRight = super.parsePrevChapter(); + + if (mStatus == STATUS_FINISH) { + loadPrevChapter(); + } else if (mStatus == STATUS_LOADING) { + loadCurrentChapter(); + } + return isRight; + } + + // 装载当前章内容。 + @Override + boolean parseCurChapter() { + boolean isRight = super.parseCurChapter(); + + if (mStatus == STATUS_LOADING) { + loadCurrentChapter(); + } + return isRight; + } + + // 装载下一章节的内容 + @Override + boolean parseNextChapter() { + boolean isRight = super.parseNextChapter(); + + if (mStatus == STATUS_FINISH) { + loadNextChapter(); + } else if (mStatus == STATUS_LOADING) { + loadCurrentChapter(); + } + + return isRight; + } + + /** + * 加载当前页的前面两个章节 + */ + private void loadPrevChapter() { + if (mPageChangeListener != null) { + int end = mCurChapterPos; + int begin = end - 2; + if (begin < 0) { + begin = 0; + } + requestChapters(begin, end); + } + } + + /** + * 加载前一页,当前页,后一页。 + */ + private void loadCurrentChapter() { + if (mPageChangeListener != null) { + int begin = mCurChapterPos; + int end = mCurChapterPos; + + // 是否当前不是最后一章 + if (end < mChapterList.size()) { + end = end + 1; + if (end >= mChapterList.size()) { + end = mChapterList.size() - 1; + } + } + + // 如果当前不是第一章 + if (begin != 0) { + begin = begin - 1; + if (begin < 0) { + begin = 0; + } + } + requestChapters(begin, end); + } + } + + /** + * 加载当前页的后一个章节 + */ + private void loadNextChapter() { + if (mPageChangeListener != null) { + + // 提示加载后两章 + int begin = mCurChapterPos + 1; + int end = begin + 1; + + // 判断是否大于最后一章 + if (begin >= mChapterList.size()) { + // 如果下一章超出目录了,就没有必要加载了 + return; + } + + if (end > mChapterList.size()) { + end = mChapterList.size() - 1; + } + requestChapters(begin, end); + } + } + + private void requestChapters(int start, int end) { + // 检验输入值 + if (start < 0) { + start = 0; + } + + if (end >= mChapterList.size()) { + end = mChapterList.size() - 1; + } + + + List chapters = new ArrayList<>(); + + // 过滤,哪些数据已经加载了 + for (int i = start; i <= end; ++i) { + Chapter txtChapter = mChapterList.get(i); + if (!hasChapterData(txtChapter)) { + chapters.add(txtChapter); + } + } + + if (!chapters.isEmpty()) { + mPageChangeListener.requestChapters(chapters); + } + } + + /** + * 加载章节内容 + * + * @param chapter + */ + public void getChapterContent(final Chapter chapter) { + Chapter cacheChapter = mChapterService.findChapterByBookIdAndTitle(chapter.getBookId(), chapter.getTitle()); + if (cacheChapter != null && hasChapterData(chapter)) { + chapter.setContent(APPCONST.BOOK_CACHE_PATH + chapter.getBookId() + + File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY); + chapter.setId(cacheChapter.getId()); + mChapterService.saveOrUpdateChapter(chapter, null); + } else { + CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, new ResultCallback() { + @Override + public void onFinish(final Object o, int code) { +// chapter.setContent((String) o); + mChapterService.saveOrUpdateChapter(chapter, (String) o); + } + @Override + public void onError(Exception e) { + + } + + }); + } + + } +} + 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 new file mode 100644 index 0000000..b8389e4 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java @@ -0,0 +1,1603 @@ +package xyz.fycz.myreader.widget.page; + +import android.content.Context; +import android.graphics.*; +import androidx.core.content.ContextCompat; +import android.text.TextPaint; +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.callback.ResultCallback; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.enums.Font; +import xyz.fycz.myreader.enums.Language; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.util.TextHelper; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.util.utils.ScreenUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by newbiechen on 17-7-1. + */ + +public abstract class PageLoader { + private static final String TAG = "PageLoader"; + + // 当前页面的状态 + public static final int STATUS_LOADING = 1; // 正在加载 + public static final int STATUS_FINISH = 2; // 加载完成 + public static final int STATUS_ERROR = 3; // 加载错误 (一般是网络加载情况) + public static final int STATUS_EMPTY = 4; // 空数据 + public static final int STATUS_PARING = 5; // 正在解析 (装载本地数据) + 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; + private static final int DEFAULT_TIP_SIZE = 12; + private static final int EXTRA_TITLE_SIZE = 4; + private static final int TIP_ALPHA = 180; + + // 当前章节列表 + protected List mChapterList; + // 书本对象 + protected Book mCollBook; + // 监听器 + protected OnPageChangeListener mPageChangeListener; + + private Context mContext; + // 页面显示类 + private PageView mPageView; + // 当前显示的页 + private TxtPage mCurPage; + // 上一章的页面列表缓存 + private List mPrePageList; + // 当前章节的页面列表 + private List mCurPageList; + // 下一章的页面列表缓存 + private List mNextPageList; + + // 绘制电池的画笔 + private Paint mBatteryPaint; + // 绘制提示的画笔 + private Paint mTipPaint; + // 绘制标题的画笔 + private Paint mTitlePaint; + // 绘制背景颜色的画笔(用来擦除需要重绘的部分) + private Paint mBgPaint; + // 绘制小说内容的画笔 + private TextPaint mTextPaint; + // 阅读器的配置选项 + private Setting mSettingManager; + // 被遮盖的页,或者认为被取消显示的页 + private TxtPage mCancelPage; + // 存储阅读记录类 +// private BookRecordBean mBookRecord; + + private Disposable mPreLoadDisp; + + /*****************params**************************/ + // 当前的状态 + protected int mStatus = STATUS_LOADING; + // 判断章节列表是否加载完成 + protected boolean isChapterListPrepare; + + // 是否打开过章节 + private boolean isChapterOpen; + private boolean isFirstOpen = true; + private boolean isClose; + // 页面的翻页效果模式 + private PageMode mPageMode; + // 加载器的颜色主题 +// private PageStyle mPageStyle; + //当前是否是日间模式 + private boolean isDayMode; + //书籍绘制区域的宽高 + private int mVisibleWidth; + private int mVisibleHeight; + //应用的宽高 + private int mDisplayWidth; + private int mDisplayHeight; + //间距 + private int mMarginWidth; + private int mMarginHeight; + //字体的颜色 + private int mTextColor; + //字体类型 + private Typeface mTypeFace; + //标题的大小 + private float mTitleSize; + //字体的大小 + private float mTextSize; + //行间距 + private float mTextInterval; + //标题的行间距 + private float mTitleInterval; + //段落距离(基于行间距的额外距离) + private float mTextPara; + private float mTitlePara; + //电池的百分比 + private int mBatteryLevel; + //当前页面的背景 + private int mBgColor; + + //繁简体 + private Language language; + // 当前章 + protected int mCurChapterPos = 0; + //上一章的记录 + private int mLastChapterPos = 0; + + /*****************************init params*******************************/ + public PageLoader(PageView pageView, Book collBook, Setting setting) { + mPageView = pageView; + mContext = pageView.getContext(); + mCollBook = collBook; + mChapterList = new ArrayList<>(1); + // 获取配置管理器 + mSettingManager = setting; + // 初始化数据 + initData(); + // 初始化画笔 + initPaint(); + // 初始化PageView + initPageView(); + /*// 初始化书籍 + prepareBook();*/ + } + + public void init(){ + // 初始化数据 + initData(); + // 初始化画笔 + initPaint(); + // 初始化PageView + initPageView(); + // 初始化书籍 + prepareBook(); + } + + 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); + // 配置文字有关的参数 + setUpTextParams(mSettingManager.getReadWordSize()); + } + + + /** + * 作用:设置与文字相关的参数 + * @param textSize + */ + private void setUpTextParams(float textSize) { + // 文字大小 + mTextSize = textSize * 2; + mTitleSize = mTextSize + ScreenUtils.spToPx(EXTRA_TITLE_SIZE); + // 行间距(大小为字体的一半) + mTextInterval = mTextSize / 2; + mTitleInterval = mTitleSize / 2; + // 段落间距(大小为字体的高度) + mTextPara = mTextSize; + mTitlePara = mTitleSize; + } + + private void initPaint() { + // 绘制提示的画笔 + mTipPaint = new Paint(); + mTipPaint.setColor(mTextColor); + mTipPaint.setTextAlign(Paint.Align.LEFT); // 绘制的起始点 + mTipPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE)); // Tip默认的字体大小 + mTipPaint.setTypeface(mTypeFace); + mTipPaint.setAntiAlias(true); + mTipPaint.setSubpixelText(true); + + // 绘制页面内容的画笔 + mTextPaint = new TextPaint(); + mTextPaint.setColor(mTextColor); + mTextPaint.setTextSize(mTextSize); + mTextPaint.setTypeface(mTypeFace); + mTextPaint.setAntiAlias(true); + + // 绘制标题的画笔 + mTitlePaint = new TextPaint(); + mTitlePaint.setColor(mTextColor); + mTitlePaint.setTextSize(mTitleSize); + mTitlePaint.setStyle(Paint.Style.FILL_AND_STROKE); + mTitlePaint.setTypeface(mTypeFace); + mTitlePaint.setAntiAlias(true); + + // 绘制背景的画笔 + mBgPaint = new Paint(); + mBgPaint.setColor(mBgColor); + + // 绘制电池的画笔 + mBatteryPaint = new TextPaint(); + mBatteryPaint.setAntiAlias(true); + mBatteryPaint.setDither(true); + mBatteryPaint.setTextSize(ScreenUtils.spToPx(DEFAULT_TIP_SIZE - 3)); + mBatteryPaint.setTypeface(Typeface.createFromAsset(mContext.getAssets(), "fonts/number.ttf")); + + // 初始化页面样式 + setPageStyle(isDayMode); + } + + private void initPageView() { + //配置参数 + mPageView.setPageMode(mPageMode); + mPageView.setBgColor(mBgColor); + } + + /****************************** public method***************************/ + /** + * 跳转到上一章 + * + * @return + */ + public boolean skipPreChapter() { + if (!hasPrevChapter()) { + return false; + } + + // 载入上一章。 + if (parsePrevChapter()) { + mCurPage = getCurPage(0); + } else { + mCurPage = new TxtPage(); + } + mPageView.drawCurPage(false); + return true; + } + + /** + * 跳转到下一章 + * + * @return + */ + public boolean skipNextChapter() { + if (!hasNextChapter()) { + return false; + } + + //判断是否达到章节的终止点 + if (parseNextChapter()) { + mCurPage = getCurPage(0); + } else { + mCurPage = new TxtPage(); + } + mPageView.drawCurPage(false); + return true; + } + + /** + * 跳转到指定章节 + * + * @param pos:从 0 开始。 + */ + public void skipToChapter(int pos) { + // 设置参数 + mCurChapterPos = pos; + + // 将上一章的缓存设置为null + mPrePageList = null; + // 如果当前下一章缓存正在执行,则取消 + if (mPreLoadDisp != null) { + mPreLoadDisp.dispose(); + } + // 将下一章缓存设置为null + mNextPageList = null; + + // 打开指定章节 + openChapter(); + } + + /** + * 跳转到指定的页 + * + * @param pos + */ + public boolean skipToPage(int pos) { + if (!isChapterListPrepare) { + return false; + } + mCurPage = getCurPage(pos); + mPageView.drawCurPage(false); + return true; + } + + /** + * 翻到上一页 + * + * @return + */ + public boolean skipToPrePage() { + return mPageView.autoPrevPage(); + } + + /** + * 翻到下一页 + * + * @return + */ + public boolean skipToNextPage() { + return mPageView.autoNextPage(); + } + + /** + * 更新时间 + */ + public void updateTime() { + if (!mPageView.isRunning()) { + mPageView.drawCurPage(true); + } + } + + /** + * 更新电量 + * + * @param level + */ + public void updateBattery(int level) { + mBatteryLevel = level; + if (!mPageView.isRunning()) { + mPageView.drawCurPage(true); + } + } + + /** + * 设置提示的文字大小 + * + * @param textSize:单位为 px。 + */ + public void setTipTextSize(int textSize) { + mTipPaint.setTextSize(textSize); + + // 如果屏幕大小加载完成 + mPageView.drawCurPage(false); + } + + /** + * 设置文字相关参数 + * + * @param textSize + */ + public void setTextSize(int textSize) { + // 设置文字相关参数 + setUpTextParams(textSize); + + // 设置画笔的字体大小 + mTextPaint.setTextSize(mTextSize); + // 设置标题的字体大小 + mTitlePaint.setTextSize(mTitleSize); + /*// 存储文字大小 + mSettingManager.setReadWordSize(mTextSize);*/ + // 取消缓存 + mPrePageList = null; + mNextPageList = null; + + // 如果当前已经显示数据 + if (isChapterListPrepare && mStatus == STATUS_FINISH) { + // 重新计算当前页面 + dealLoadPageList(mCurChapterPos); + + // 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题 + if (mCurPage.position >= mCurPageList.size()) { + mCurPage.position = mCurPageList.size() - 1; + } + + // 重新获取指定页面 + mCurPage = mCurPageList.get(mCurPage.position); + } + + mPageView.drawCurPage(false); + } + + /** + * 设置夜间模式 + * + * @param dayMode + */ + public void setNightMode(boolean dayMode) { + isDayMode = dayMode; + if (!dayMode) { + mBatteryPaint.setColor(Color.WHITE); + } else { + mBatteryPaint.setColor(Color.BLACK); + } + } + + /** + * 设置页面样式 + * + */ + public void setPageStyle(boolean dayMode) { + /*if (pageStyle != PageStyle.NIGHT) { + mPageStyle = pageStyle; + mSettingManager.setPageStyle(pageStyle); + } + + if (isNightMode && pageStyle != PageStyle.NIGHT) { + return; + } +*/ + if (!dayMode) { + mTextColor = ContextCompat.getColor(mContext, R.color.sys_night_word); + mBgColor = ContextCompat.getColor(mContext, R.color.sys_night_bg); + mBatteryPaint.setColor(mTextColor); + } else { + mTextColor = ContextCompat.getColor(mContext, mSettingManager.getReadWordColor()); + mBgColor = ContextCompat.getColor(mContext, mSettingManager.getReadBgColor()); + mBatteryPaint.setColor(Color.BLACK); + } + // 设置当前颜色样式 + + mTipPaint.setColor(mTextColor); + mTitlePaint.setColor(mTextColor); + mTextPaint.setColor(mTextColor); + mBatteryPaint.setColor(mTextColor); + mBgPaint.setColor(mBgColor); + + mBatteryPaint.setAlpha(TIP_ALPHA); + + mPageView.drawCurPage(false); + } + + /** + * 翻页动画 + * + * @param pageMode:翻页模式 + * @see PageMode + */ + public void setPageMode(PageMode pageMode) { + mPageMode = pageMode; + + mPageView.setPageMode(mPageMode); + mSettingManager.setPageMode(mPageMode); + + // 重新绘制当前页 + mPageView.drawCurPage(false); + } + + /** + * 设置字体 + * @param font + */ + public void setFont(Font font){ + mSettingManager = SysManager.getSetting(); + //获取字体 + getFont(font); + mTipPaint.setTypeface(mTypeFace); + mTextPaint.setTypeface(mTypeFace); + mTitlePaint.setTypeface(mTypeFace); + // 取消缓存 + mPrePageList = null; + mNextPageList = null; + + // 如果当前已经显示数据 + if (isChapterListPrepare && mStatus == STATUS_FINISH) { + // 重新计算当前页面 + dealLoadPageList(mCurChapterPos); + + // 防止在最后一页,通过修改字体,以至于页面数减少导致崩溃的问题 + if (mCurPage.position >= mCurPageList.size()) { + mCurPage.position = mCurPageList.size() - 1; + } + + // 重新获取指定页面 + mCurPage = mCurPageList.get(mCurPage.position); + } + + mPageView.drawCurPage(false); + } + + /** + * 获取字体 + * @param font + */ + public void getFont(Font font){ + String fontFileName = mSettingManager.getFont().fileName; + if (font == Font.本地字体){ + fontFileName = mSettingManager.getLocalFontName(); + } + File fontFile = new File(APPCONST.FONT_BOOK_DIR + fontFileName); + if (font == Font.默认字体 || !fontFile.exists()) { + mTypeFace = null; + if (!fontFile.exists()){ + mSettingManager.setFont(Font.默认字体); + SysManager.saveSetting(mSettingManager); + } + } else { + try { + mTypeFace = Typeface.createFromFile(fontFile); + }catch (Exception e){ + TextHelper.showText(e.getLocalizedMessage()); + mSettingManager.setFont(Font.默认字体); + SysManager.saveSetting(mSettingManager); + } + } + } + /** + * 刷新章节 + * @param chapter + */ + public void refreshChapter(Chapter chapter){ + chapter.setContent(null); + getChapterContent(chapter); + openChapter(); + } + /** + * 设置内容与屏幕的间距 + * + * @param marginWidth :单位为 px + * @param marginHeight :单位为 px + */ + public void setMargin(int marginWidth, int marginHeight) { + mMarginWidth = marginWidth; + mMarginHeight = marginHeight; + + // 如果是滑动动画,则需要重新创建了 + if (mPageMode == PageMode.SCROLL) { + mPageView.setPageMode(PageMode.SCROLL); + } + + mPageView.drawCurPage(false); + } + + /** + * 设置页面切换监听 + * + * @param listener + */ + public void setOnPageChangeListener(OnPageChangeListener listener) { + mPageChangeListener = listener; + + // 如果目录加载完之后才设置监听器,那么会默认回调 + if (isChapterListPrepare) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + } + + /** + * 获取当前页的状态 + * + * @return + */ + public int getPageStatus() { + return mStatus; + } + + /** + * 获取书籍信息 + * + * @return + */ + public Book getCollBook() { + return mCollBook; + } + + /** + * 获取章节目录。 + * + * @return + */ + public List getChapterCategory() { + return mChapterList; + } + + /** + * 获取当前页的页码 + * + * @return + */ + public int getPagePos() { + if (mCurPage == null) { + return 0; + } + return mCurPage.position; + } + + /** + * 获取总页码 + * + * @return + */ + public int getAllPagePos() { + if (mCurPageList == null) { + return 0; + } + return mCurPageList.size(); + } + + /** + * 获取当前章节的章节位置 + * + * @return + */ + public int getChapterPos() { + return mCurChapterPos; + } + + /** + * 获取距离屏幕的高度 + * + * @return + */ + public int getMarginHeight() { + return mMarginHeight; + } + + /** + * 保存阅读记录 + */ + /*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); + }*/ + + /** + * 初始化书籍 + */ + private void prepareBook() { + mCurChapterPos = mCollBook.getHisttoryChapterNum(); + mLastChapterPos = mCurChapterPos; + } + + /** + * 打开指定章节 + */ + public void openChapter() { + isFirstOpen = false; + + if (!mPageView.isPrepare()) { + return; + } + + // 如果章节目录没有准备好 + if (!isChapterListPrepare) { + mStatus = STATUS_LOADING; + mPageView.drawCurPage(false); + return; + } + + // 如果获取到的章节目录为空 + if (mChapterList.isEmpty()) { + mStatus = STATUS_CATEGORY_EMPTY; + mPageView.drawCurPage(false); + return; + } + + if (parseCurChapter()) { + // 如果章节从未打开 + if (!isChapterOpen) { + int position = mCollBook.getLastReadPosition(); + + // 防止记录页的页号,大于当前最大页号 + if (position >= mCurPageList.size()) { + position = mCurPageList.size() - 1; + } + mCurPage = getCurPage(position); + mCancelPage = mCurPage; + // 切换状态 + isChapterOpen = true; + } else { + mCurPage = getCurPage(0); + } + } else { + mCurPage = new TxtPage(); + } + + mPageView.drawCurPage(false); + } + + public void chapterError() { + //加载错误 + mStatus = STATUS_ERROR; + mPageView.drawCurPage(false); + } + + /** + * 关闭书本 + */ + public void closeBook() { + isChapterListPrepare = false; + isClose = true; + + if (mPreLoadDisp != null) { + mPreLoadDisp.dispose(); + } + + clearList(mChapterList); + clearList(mCurPageList); + clearList(mNextPageList); + + mChapterList = null; + mCurPageList = null; + mNextPageList = null; + mPageView = null; + mCurPage = null; + } + + private void clearList(List list) { + if (list != null) { + list.clear(); + } + } + + public boolean isClose() { + return isClose; + } + + public boolean isChapterOpen() { + return isChapterOpen; + } + + /** + * 加载页面列表 + * + * @param chapterPos:章节序号 + * @return + */ + private List loadPageList(int chapterPos) throws Exception { + // 获取章节 + Chapter chapter = mChapterList.get(chapterPos); + // 判断章节是否存在 + if (!hasChapterData(chapter)) { + return null; + } + // 获取章节的文本流 + BufferedReader reader = getChapterReader(chapter); + List chapters = loadPages(chapter, reader); + + return chapters; + } + + + /*******************************abstract method***************************************/ + + /** + * 刷新章节列表 + */ + public abstract void refreshChapterList(); + + /** + * 获取章节的文本流 + * + * @param chapter + * @return + */ + protected abstract BufferedReader getChapterReader(Chapter chapter) throws Exception; + + /** + * 章节数据是否存在 + * + * @return + */ + protected abstract boolean hasChapterData(Chapter chapter); + + + /** + * + */ + public abstract void getChapterContent(Chapter chapter); + /***********************************default method***********************************************/ + + void drawPage(Bitmap bitmap, boolean isUpdate) { + drawBackground(mPageView.getBgBitmap(), isUpdate); + if (!isUpdate) { + drawContent(bitmap); + } + //更新绘制 + mPageView.invalidate(); + } + + private void drawBackground(Bitmap bitmap, boolean isUpdate) { + Canvas canvas = new Canvas(bitmap); + int tipMarginHeight = ScreenUtils.dpToPx(3); + String progress = (mStatus != STATUS_FINISH) ? "" + : getReadProgress(getChapterPos(), mChapterList.size(), getPagePos(), getAllPagePos()); + if (!isUpdate) { + /****绘制背景****/ + canvas.drawColor(mBgColor); + + if (!mChapterList.isEmpty()) { + /*****初始化标题的参数********/ + //需要注意的是:绘制text的y的起始点是text的基准线的位置,而不是从text的头部的位置 + float tipTop = tipMarginHeight - mTipPaint.getFontMetrics().top; + //根据状态不一样,数据不一样 + if (mStatus != STATUS_FINISH) { + if (isChapterListPrepare) { + canvas.drawText(mChapterList.get(mCurChapterPos).getTitle() + , mMarginWidth, tipTop, mTipPaint); + } + } else { + canvas.drawText(mCurPage.title, mMarginWidth, tipTop, mTipPaint); + } + + + /*******绘制进度*******/ + float progressTipLeft = mDisplayWidth - mMarginWidth - 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) + "/" + mCurPageList.size(); + canvas.drawText(percent, mMarginWidth, y, mTipPaint); + } + + } + } else { + //擦除区域 + mBgPaint.setColor(mBgColor); + canvas.drawRect(mDisplayWidth / 2, mDisplayHeight - mMarginHeight + ScreenUtils.dpToPx(2), mDisplayWidth, mDisplayHeight, mBgPaint); + } + + /******绘制电池********/ + + int visibleRight = mDisplayWidth - mMarginWidth; + int visibleBottom = mDisplayHeight - tipMarginHeight; + + 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 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); + + //外框的制作 + 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.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); + } + + private void drawContent(Bitmap bitmap) { + Canvas canvas = new Canvas(bitmap); + + if (mPageMode == PageMode.SCROLL) { + canvas.drawColor(mBgColor); + } + /******绘制内容****/ + + if (mStatus != STATUS_FINISH) { + //绘制字体 + String tip = ""; + switch (mStatus) { + case STATUS_LOADING: + tip = "正在拼命加载中..."; + break; + case STATUS_ERROR: + tip = "加载失败(点击边缘重试)"; + break; + case STATUS_EMPTY: + tip = "文章内容为空"; + break; + case STATUS_PARING: + tip = "正在拆分章节请等待..."; + break; + case STATUS_PARSE_ERROR: + tip = "文件解析错误"; + break; + case STATUS_CATEGORY_EMPTY: + tip = "目录列表为空"; + break; + } + + //将提示语句放到正中间 + Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); + float textHeight = fontMetrics.top - fontMetrics.bottom; + float textWidth = mTextPaint.measureText(tip); + float pivotX = (mDisplayWidth - textWidth) / 2; + float pivotY = (mDisplayHeight - textHeight) / 2; + canvas.drawText(tip, pivotX, pivotY, mTextPaint); + } else { + float top; + + if (mPageMode == PageMode.SCROLL) { + top = -mTextPaint.getFontMetrics().top; + } else { + top = mMarginHeight - mTextPaint.getFontMetrics().top; + } + + //设置总距离 + float interval = mTextInterval + mTextPaint.getTextSize(); + float para = mTextPara + mTextPaint.getTextSize(); + float titleInterval = mTitleInterval + mTitlePaint.getTextSize(); + float titlePara = mTitlePara + mTextPaint.getTextSize(); + String str = null; + + //对标题进行绘制 + for (int i = 0; i < mCurPage.titleLines; ++i) { + str = mCurPage.lines.get(i); + + //设置顶部间距 + if (i == 0) { + top += mTitlePara; + } + + //计算文字显示的起始点 + int start = (int) (mDisplayWidth - mTitlePaint.measureText(str)) / 2; + //进行绘制 + canvas.drawText(str, start, top, mTitlePaint); + + //设置尾部间距 + if (i == mCurPage.titleLines - 1) { + top += titlePara; + } else { + //行间距 + top += titleInterval; + } + } + + //对内容进行绘制 + for (int i = mCurPage.titleLines; i < mCurPage.lines.size(); ++i) { + str = mCurPage.lines.get(i); + + canvas.drawText(str, mMarginWidth, top, mTextPaint); + if (str.endsWith("\n")) { + top += para; + } else { + top += interval; + } + } + } + } + + void prepareDisplay(int w, int h) { + // 获取PageView的宽高 + mDisplayWidth = w; + mDisplayHeight = h; + + // 获取内容显示位置的大小 + mVisibleWidth = mDisplayWidth - mMarginWidth * 2; + mVisibleHeight = mDisplayHeight - mMarginHeight * 2; + + // 重置 PageMode + mPageView.setPageMode(mPageMode); + + if (!isChapterOpen) { + // 展示加载界面 + mPageView.drawCurPage(false); + // 如果在 display 之前调用过 openChapter 肯定是无法打开的。 + // 所以需要通过 display 再重新调用一次。 + if (!isFirstOpen) { + // 打开书籍 + openChapter(); + } + } else { + // 如果章节已显示,那么就重新计算页面 + if (mStatus == STATUS_FINISH) { + dealLoadPageList(mCurChapterPos); + // 重新设置文章指针的位置 + mCurPage = getCurPage(mCurPage.position); + } + mPageView.drawCurPage(false); + } + } + + /** + * 翻阅上一页 + * + * @return + */ + boolean prev() { + // 以下情况禁止翻页 + if (!canTurnPage()) { + return false; + } + + if (mStatus == STATUS_FINISH) { + // 先查看是否存在上一页 + TxtPage prevPage = getPrevPage(); + if (prevPage != null) { + mCancelPage = mCurPage; + mCurPage = prevPage; + mPageView.drawNextPage(); + return true; + } + } + + if (!hasPrevChapter()) { + return false; + } + + mCancelPage = mCurPage; + if (parsePrevChapter()) { + mCurPage = getPrevLastPage(); + } else { + mCurPage = new TxtPage(); + } + mPageView.drawNextPage(); + return true; + } + + /** + * 解析上一章数据 + * + * @return:数据是否解析成功 + */ + boolean parsePrevChapter() { + // 加载上一章数据 + int prevChapter = mCurChapterPos - 1; + + mLastChapterPos = mCurChapterPos; + mCurChapterPos = prevChapter; + + // 当前章缓存为下一章 + mNextPageList = mCurPageList; + + // 判断是否具有上一章缓存 + if (mPrePageList != null) { + mCurPageList = mPrePageList; + mPrePageList = null; + + // 回调 + chapterChangeCallback(); + } else { + dealLoadPageList(prevChapter); + } + return mCurPageList != null; + } + + private boolean hasPrevChapter() { + //判断是否上一章节为空 + if (mCurChapterPos - 1 < 0) { + return false; + } + return true; + } + + /** + * 翻到下一页 + * + * @return:是否允许翻页 + */ + boolean next() { + // 以下情况禁止翻页 + if (!canTurnPage()) { + return false; + } + + if (mStatus == STATUS_FINISH) { + // 先查看是否存在下一页 + TxtPage nextPage = getNextPage(); + if (nextPage != null) { + mCancelPage = mCurPage; + mCurPage = nextPage; + mPageView.drawNextPage(); + return true; + } + } + + if (!hasNextChapter()) { + return false; + } + + mCancelPage = mCurPage; + // 解析下一章数据 + if (parseNextChapter()) { + mCurPage = mCurPageList.get(0); + } else { + mCurPage = new TxtPage(); + } + mPageView.drawNextPage(); + return true; + } + + private boolean hasNextChapter() { + // 判断是否到达目录最后一章 + if (mCurChapterPos + 1 >= mChapterList.size()) { + return false; + } + return true; + } + + boolean parseCurChapter() { + // 解析数据 + dealLoadPageList(mCurChapterPos); + // 预加载下一页面 + preLoadNextChapter(); + return mCurPageList != null; + } + + /** + * 解析下一章数据 + * + * @return:返回解析成功还是失败 + */ + boolean parseNextChapter() { + int nextChapter = mCurChapterPos + 1; + + mLastChapterPos = mCurChapterPos; + mCurChapterPos = nextChapter; + + // 将当前章的页面列表,作为上一章缓存 + mPrePageList = mCurPageList; + + // 是否下一章数据已经预加载了 + if (mNextPageList != null) { + mCurPageList = mNextPageList; + mNextPageList = null; + // 回调 + chapterChangeCallback(); + } else { + // 处理页面解析 + dealLoadPageList(nextChapter); + } + // 预加载下一页面 + preLoadNextChapter(); + return mCurPageList != null; + } + + private void dealLoadPageList(int chapterPos) { + try { + mCurPageList = loadPageList(chapterPos); + if (mCurPageList != null) { + if (mCurPageList.isEmpty()) { + mStatus = STATUS_EMPTY; + + // 添加一个空数据 + TxtPage page = new TxtPage(); + page.lines = new ArrayList<>(1); + mCurPageList.add(page); + } else { + mStatus = STATUS_FINISH; + } + } else { + mStatus = STATUS_LOADING; + } + } catch (Exception e) { + e.printStackTrace(); + mCurPageList = null; + mStatus = STATUS_ERROR; + } + + // 回调 + chapterChangeCallback(); + } + + private void chapterChangeCallback() { + if (mPageChangeListener != null) { + mPageChangeListener.onChapterChange(mCurChapterPos); + mPageChangeListener.onPageCountChange(mCurPageList != null ? mCurPageList.size() : 0); + } + } + + // 预加载下一章 + private void preLoadNextChapter() { + int nextChapter = mCurChapterPos + 1; + + // 如果不存在下一章,且下一章没有数据,则不进行加载。 + if (!hasNextChapter() + || !hasChapterData(mChapterList.get(nextChapter))) { + return; + } + //如果之前正在加载则取消 + if (mPreLoadDisp != null) { + mPreLoadDisp.dispose(); + } + + //调用异步进行预加载加载 + Single.create((SingleOnSubscribe>) e -> e.onSuccess(loadPageList(nextChapter))).compose(RxUtils::toSimpleSingle) + .subscribe(new SingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + mPreLoadDisp = d; + } + + @Override + public void onSuccess(List pages) { + mNextPageList = pages; + } + + @Override + public void onError(Throwable e) { + //无视错误 + } + }); + } + + // 取消翻页 + void pageCancel() { + if (mCurPage.position == 0 && mCurChapterPos > mLastChapterPos) { // 加载到下一章取消了 + if (mPrePageList != null) { + cancelNextChapter(); + } else { + if (parsePrevChapter()) { + mCurPage = getPrevLastPage(); + } else { + mCurPage = new TxtPage(); + } + } + } else if (mCurPageList == null + || (mCurPage.position == mCurPageList.size() - 1 + && mCurChapterPos < mLastChapterPos)) { // 加载上一章取消了 + + if (mNextPageList != null) { + cancelPreChapter(); + } else { + if (parseNextChapter()) { + mCurPage = mCurPageList.get(0); + } else { + mCurPage = new TxtPage(); + } + } + } else { + // 假设加载到下一页,又取消了。那么需要重新装载。 + mCurPage = mCancelPage; + } + } + + private void cancelNextChapter() { + int temp = mLastChapterPos; + mLastChapterPos = mCurChapterPos; + mCurChapterPos = temp; + + mNextPageList = mCurPageList; + mCurPageList = mPrePageList; + mPrePageList = null; + + chapterChangeCallback(); + + mCurPage = getPrevLastPage(); + mCancelPage = null; + } + + private void cancelPreChapter() { + // 重置位置点 + int temp = mLastChapterPos; + mLastChapterPos = mCurChapterPos; + mCurChapterPos = temp; + // 重置页面列表 + mPrePageList = mCurPageList; + mCurPageList = mNextPageList; + mNextPageList = null; + + chapterChangeCallback(); + + mCurPage = getCurPage(0); + mCancelPage = null; + } + + /**************************************private method********************************************/ + /** + * 将章节数据,解析成页面列表 + * + * @param chapter:章节信息 + * @param br:章节的文本流 + * @return + */ + private List loadPages(Chapter chapter, BufferedReader br) { + //生成的页面 + List pages = new ArrayList<>(); + //使用流的方式加载 + List lines = new ArrayList<>(); + float rHeight = mVisibleHeight; + int titleLinesCount = 0; + boolean showTitle = true; // 是否展示标题 + String paragraph = chapter.getTitle();//默认展示标题 + try { + while (showTitle || (paragraph = br.readLine()) != null) { + // 重置段落 + if (!showTitle) { + paragraph = paragraph.replaceAll("\\s", ""); + // 如果只有换行符,那么就不执行 + if (paragraph.equals("")) continue; + paragraph = StringUtils.halfToFull(" " + paragraph + "\n"); + } else { + //设置 title 的顶部间距 + rHeight -= mTitlePara; + } + int wordCount = 0; + String subStr = null; + while (paragraph.length() > 0) { + //当前空间,是否容得下一行文字 + if (showTitle) { + rHeight -= mTitlePaint.getTextSize(); + } else { + rHeight -= mTextPaint.getTextSize(); + } + // 一页已经填充满了,创建 TextPage + if (rHeight <= 0) { + // 创建Page + TxtPage page = new TxtPage(); + page.position = pages.size(); + page.title = chapter.getTitle(); + page.lines = new ArrayList<>(lines); + page.titleLines = titleLinesCount; + pages.add(page); + // 重置Lines + lines.clear(); + rHeight = mVisibleHeight; + titleLinesCount = 0; + continue; + } + + //测量一行占用的字节数 + if (showTitle) { + wordCount = mTitlePaint.breakText(paragraph, + true, mVisibleWidth, null); + } else { + wordCount = mTextPaint.breakText(paragraph, + true, mVisibleWidth, null); + } + + subStr = paragraph.substring(0, wordCount); + if (!subStr.equals("\n")) { + //将一行字节,存储到lines中 + lines.add(subStr); + + //设置段落间距 + if (showTitle) { + titleLinesCount += 1; + rHeight -= mTitleInterval; + } else { + rHeight -= mTextInterval; + } + } + //裁剪 + paragraph = paragraph.substring(wordCount); + } + + //增加段落的间距 + if (!showTitle && lines.size() != 0) { + rHeight = rHeight - mTextPara + mTextInterval; + } + + if (showTitle) { + rHeight = rHeight - mTitlePara + mTitleInterval; + showTitle = false; + } + } + + if (lines.size() != 0) { + //创建Page + TxtPage page = new TxtPage(); + page.position = pages.size(); + page.title = chapter.getTitle(); + page.lines = new ArrayList<>(lines); + page.titleLines = titleLinesCount; + pages.add(page); + //重置Lines + lines.clear(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return pages; + } + + + /** + * @return:获取初始显示的页面 + */ + private TxtPage getCurPage(int pos) { + if (mPageChangeListener != null) { + mPageChangeListener.onPageChange(pos); + } + return mCurPageList.get(pos); + } + + /** + * @return:获取上一个页面 + */ + private TxtPage getPrevPage() { + int pos = mCurPage.position - 1; + if (pos < 0) { + return null; + } + if (mPageChangeListener != null) { + mPageChangeListener.onPageChange(pos); + } + return mCurPageList.get(pos); + } + + /** + * @return:获取下一的页面 + */ + private TxtPage getNextPage() { + int pos = mCurPage.position + 1; + if (pos >= mCurPageList.size()) { + return null; + } + if (mPageChangeListener != null) { + mPageChangeListener.onPageChange(pos); + } + return mCurPageList.get(pos); + } + + /** + * @return:获取上一个章节的最后一页 + */ + private TxtPage getPrevLastPage() { + int pos = mCurPageList.size() - 1; + + if (mPageChangeListener != null) { + mPageChangeListener.onPageChange(pos); + } + + return mCurPageList.get(pos); + } + + /** + * 根据当前状态,决定是否能够翻页 + * + * @return + */ + private boolean canTurnPage() { + + if (!isChapterListPrepare) { + return false; + } + + if (mStatus == STATUS_PARSE_ERROR + || mStatus == STATUS_PARING) { + return false; + } else if (mStatus == STATUS_ERROR) { + mStatus = STATUS_LOADING; + } + return true; + } + + /** + * 获取书籍进度 + * @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; + } + + /** + * * @return curPageLength 当前页字数 + */ + public int curPageLength() { + if (getCurPage(getPagePos()) == null) return 0; + if (getPageStatus() != STATUS_FINISH) return 0; + String str; + int strLength = 0; + TxtPage txtPage = getCurPage(getPagePos()); + if (txtPage != null) { + for (int i = txtPage.getTitleLines(); i < txtPage.size(); ++i) { + str = txtPage.getLine(i); + strLength = strLength + str.length(); + } + } + return strLength; + } + + /*****************************************interface*****************************************/ + + public interface OnPageChangeListener { + /** + * 作用:章节切换的时候进行回调 + * + * @param pos:切换章节的序号 + */ + void onChapterChange(int pos); + + /** + * 作用:请求加载章节内容 + * + * @param requestChapters:需要下载的章节列表 + */ + void requestChapters(List requestChapters); + + /** + * 作用:章节目录加载完成时候回调 + * + * @param chapters:返回章节目录 + */ + void onCategoryFinish(List chapters); + + /** + * 作用:章节页码数量改变之后的回调。==> 字体大小的调整,或者是否关闭虚拟按钮功能都会改变页面的数量。 + * + * @param count:页面的数量 + */ + void onPageCountChange(int count); + + /** + * 作用:当页面改变的时候回调 + * + * @param pos:当前的页面的序号 + */ + void onPageChange(int pos); + + void preLoading(); + + } +} 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 new file mode 100644 index 0000000..b5ca9ef --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageMode.java @@ -0,0 +1,10 @@ +package xyz.fycz.myreader.widget.page; + +/** + * Created by newbiechen on 2018/2/5. + * 作用:翻页动画的模式 + */ + +public enum PageMode { + SIMULATION, COVER, SLIDE, NONE, SCROLL +} 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 new file mode 100644 index 0000000..ceaf428 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java @@ -0,0 +1,373 @@ +package xyz.fycz.myreader.widget.page; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.crawler.ReadCrawler; +import xyz.fycz.myreader.widget.animation.*; + +/** + * Created by Administrator on 2016/8/29 0029. + * 原作者的GitHub Project Path:(https://github.com/PeachBlossom/treader) + * 绘制页面显示内容的类 + */ +public class PageView extends View { + + private final static String TAG = "BookPageWidget"; + + private int mViewWidth = 0; // 当前View的宽 + private int mViewHeight = 0; // 当前View的高 + + private int mStartX = 0; + private int mStartY = 0; + private boolean isMove = false; + // 初始化参数 + private int mBgColor = 0xFFCEC29C; + private PageMode mPageMode = PageMode.SIMULATION; + // 是否允许点击 + private boolean canTouch = true; + // 唤醒菜单的区域 + private RectF mCenterRect = null; + private boolean isPrepare; + // 动画类 + private PageAnimation mPageAnim; + // 动画监听类 + private PageAnimation.OnPageChangeListener mPageAnimListener = new PageAnimation.OnPageChangeListener() { + @Override + public boolean hasPrev() { + return PageView.this.hasPrevPage(); + } + + @Override + public boolean hasNext() { + return PageView.this.hasNextPage(); + } + + @Override + public void pageCancel() { + PageView.this.pageCancel(); + } + }; + + //点击监听 + private TouchListener mTouchListener; + //内容加载器 + private PageLoader mPageLoader; + + 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); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mViewWidth = w; + mViewHeight = h; + + isPrepare = true; + + if (mPageLoader != null) { + mPageLoader.prepareDisplay(w, h); + } + } + + //设置翻页的模式 + void setPageMode(PageMode pageMode) { + mPageMode = pageMode; + //视图未初始化的时候,禁止调用 + if (mViewWidth == 0 || mViewHeight == 0) return; + + switch (mPageMode) { + case SIMULATION: + mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); + break; + case COVER: + mPageAnim = new CoverPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); + break; + case SLIDE: + mPageAnim = new SlidePageAnim(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); + break; + default: + mPageAnim = new SimulationPageAnim(mViewWidth, mViewHeight, this, mPageAnimListener); + } + } + + public Bitmap getNextBitmap() { + if (mPageAnim == null) return null; + return mPageAnim.getNextBitmap(); + } + + public Bitmap getBgBitmap() { + if (mPageAnim == null) return null; + return mPageAnim.getBgBitmap(); + } + + public boolean autoPrevPage() { + //滚动暂时不支持自动翻页 + if (mPageAnim instanceof ScrollPageAnim) { + return false; + } else { + startPageAnim(PageAnimation.Direction.PRE); + return true; + } + } + + public boolean autoNextPage() { + if (mPageAnim instanceof ScrollPageAnim) { + return false; + } else { + startPageAnim(PageAnimation.Direction.NEXT); + return true; + } + } + + private void startPageAnim(PageAnimation.Direction direction) { + if (mTouchListener == null) return; + //是否正在执行动画 + abortAnimation(); + if (direction == PageAnimation.Direction.NEXT) { + int x = mViewWidth; + int y = mViewHeight; + //初始化动画 + mPageAnim.setStartPoint(x, y); + //设置点击点 + mPageAnim.setTouchPoint(x, y); + //设置方向 + Boolean hasNext = hasNextPage(); + + mPageAnim.setDirection(direction); + if (!hasNext) { + return; + } + } else { + int x = 0; + int y = mViewHeight; + //初始化动画 + mPageAnim.setStartPoint(x, y); + //设置点击点 + mPageAnim.setTouchPoint(x, y); + mPageAnim.setDirection(direction); + //设置方向方向 + Boolean hashPrev = hasPrevPage(); + if (!hashPrev) { + return; + } + } + mPageAnim.startAnim(); + this.postInvalidate(); + } + + public void setBgColor(int color) { + mBgColor = color; + } + + @Override + protected void onDraw(Canvas canvas) { + + //绘制背景 + canvas.drawColor(mBgColor); + + //绘制动画 + mPageAnim.draw(canvas); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + + if (!canTouch && event.getAction() != MotionEvent.ACTION_DOWN) return true; + + int x = (int) event.getX(); + int y = (int) event.getY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mStartX = x; + mStartY = y; + isMove = false; + canTouch = mTouchListener.onTouch(); + mPageAnim.onTouchEvent(event); + break; + case MotionEvent.ACTION_MOVE: + // 判断是否大于最小滑动值。 + int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + if (!isMove) { + isMove = Math.abs(mStartX - event.getX()) > slop || Math.abs(mStartY - event.getY()) > slop; + } + + // 如果滑动了,则进行翻页。 + if (isMove) { + mPageAnim.onTouchEvent(event); + } + break; + case MotionEvent.ACTION_UP: + if (!isMove) { + //设置中间区域范围 + if (mCenterRect == null) { + mCenterRect = new RectF(mViewWidth / 5, mViewHeight / 3, + mViewWidth * 4 / 5, mViewHeight * 2 / 3); + } + + //是否点击了中间 + if (mCenterRect.contains(x, y)) { + if (mTouchListener != null) { + mTouchListener.center(); + } + return true; + } + } + mPageAnim.onTouchEvent(event); + break; + } + return true; + } + + /** + * 判断是否存在上一页 + * + * @return + */ + private boolean hasPrevPage() { + mTouchListener.prePage(); + return mPageLoader.prev(); + } + + /** + * 判断是否下一页存在 + * + * @return + */ + private boolean hasNextPage() { + boolean hasNextPage = mPageLoader.next(); + mTouchListener.nextPage(hasNextPage); + return hasNextPage; + } + + private void pageCancel() { + mTouchListener.cancel(); + mPageLoader.pageCancel(); + } + + @Override + public void computeScroll() { + //进行滑动 + mPageAnim.scrollAnim(); + super.computeScroll(); + } + + //如果滑动状态没有停止就取消状态,重新设置Anim的触碰点 + public void abortAnimation() { + mPageAnim.abortAnim(); + } + + public boolean isRunning() { + if (mPageAnim == null) { + return false; + } + return mPageAnim.isRunning(); + } + + public boolean isPrepare() { + return isPrepare; + } + + public void setTouchListener(TouchListener mTouchListener) { + this.mTouchListener = mTouchListener; + } + + public void drawNextPage() { + if (!isPrepare) return; + + if (mPageAnim instanceof HorizonPageAnim) { + ((HorizonPageAnim) mPageAnim).changePage(); + } + mPageLoader.drawPage(getNextBitmap(), false); + } + + /** + * 绘制当前页。 + * + * @param isUpdate + */ + public void drawCurPage(boolean isUpdate) { + if (!isPrepare) return; + + if (!isUpdate){ + if (mPageAnim instanceof ScrollPageAnim) { + ((ScrollPageAnim) mPageAnim).resetBitmap(); + } + } + + mPageLoader.drawPage(getNextBitmap(), isUpdate); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mPageAnim.abortAnim(); + mPageAnim.clear(); + + mPageLoader = null; + mPageAnim = null; + } + + /** + * 获取 PageLoader + * + * @param collBook + * @return + */ + public PageLoader getPageLoader(Book collBook, ReadCrawler mReadCrawler, Setting setting) { + // 判是否已经存在 + if (mPageLoader != null) { + return mPageLoader; + } + // 获取具体的加载器 + if ("本地书籍".equals(collBook.getType())){ + mPageLoader = new LocalPageLoader(this, collBook, new ChapterService(), setting); + }else { + mPageLoader = new NetPageLoader(this, collBook, new ChapterService(), mReadCrawler, setting); + } + // 判断是否 PageView 已经初始化完成 + if (mViewWidth != 0 || mViewHeight != 0) { + // 初始化 PageLoader 的屏幕大小 + mPageLoader.prepareDisplay(mViewWidth, mViewHeight); + } + + return mPageLoader; + } + + public interface TouchListener { + boolean onTouch(); + + void center(); + + void prePage(); + + void nextPage(boolean hasNextChange); + + void cancel(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/TxtChapter.java b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtChapter.java new file mode 100644 index 0000000..f8cd90d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtChapter.java @@ -0,0 +1,59 @@ +package xyz.fycz.myreader.widget.page; + +/** + * Created by newbiechen on 17-7-1. + */ + +public class TxtChapter { + + //章节所属的小说(网络) + String bookId; + //章节名 + String title; + + //章节内容在文章中的起始位置(本地) + long start; + //章节内容在文章中的终止位置(本地) + long end; + + public String getBookId() { + return bookId; + } + + public void setBookId(String id) { + this.bookId = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public long getStart() { + return start; + } + + public void setStart(long start) { + this.start = start; + } + + public long getEnd() { + return end; + } + + public void setEnd(long end) { + this.end = end; + } + + @Override + public String toString() { + return "TxtChapter{" + + "title='" + title + '\'' + + ", start=" + start + + ", end=" + end + + '}'; + } +} 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 new file mode 100644 index 0000000..aaf12d7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/TxtPage.java @@ -0,0 +1,26 @@ +package xyz.fycz.myreader.widget.page; + +import java.util.List; + +/** + * Created by newbiechen on 17-7-1. + */ + +public class TxtPage { + int position; + String title; + int titleLines; //当前 lines 中为 title 的行数。 + List lines; + + public int getTitleLines() { + return titleLines; + } + + public int size() { + return lines.size(); + } + + public String getLine(int i) { + return lines.get(i); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_history_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_history_black_24dp.png new file mode 100644 index 0000000..007d17e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_history_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_black_24dp.png new file mode 100644 index 0000000..4c91cfa Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png new file mode 100644 index 0000000..6998e9f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..5e28ca5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_history_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_history_black_24dp.png new file mode 100644 index 0000000..00d0fbd Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_history_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_black_24dp.png new file mode 100644 index 0000000..001be7e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png new file mode 100644 index 0000000..8a8d85d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..e3312b6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/arm_left.png b/app/src/main/res/drawable-xhdpi/arm_left.png new file mode 100644 index 0000000..24369fe Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/arm_left.png differ diff --git a/app/src/main/res/drawable-xhdpi/arm_right.png b/app/src/main/res/drawable-xhdpi/arm_right.png new file mode 100644 index 0000000..b237b84 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/arm_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/bg.jpeg b/app/src/main/res/drawable-xhdpi/bg.jpeg new file mode 100644 index 0000000..e3461f1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg.jpeg differ diff --git a/app/src/main/res/drawable-xhdpi/bg_login.jpg b/app/src/main/res/drawable-xhdpi/bg_login.jpg new file mode 100644 index 0000000..212b2c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_login.jpg differ diff --git a/app/src/main/res/drawable-xhdpi/font_example_bg.xml b/app/src/main/res/drawable-xhdpi/font_example_bg.xml new file mode 100644 index 0000000..22c53b0 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/font_example_bg.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/font_use_btn_bg.xml b/app/src/main/res/drawable-xhdpi/font_use_btn_bg.xml new file mode 100644 index 0000000..58445ef --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/font_use_btn_bg.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/font_using_btn_bg.xml b/app/src/main/res/drawable-xhdpi/font_using_btn_bg.xml new file mode 100644 index 0000000..9e14635 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/font_using_btn_bg.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/ic_history_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_history_black_24dp.png new file mode 100644 index 0000000..189efa7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_history_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_item_category_download.png b/app/src/main/res/drawable-xhdpi/ic_item_category_download.png new file mode 100644 index 0000000..bb61a3c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_item_category_download.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_item_category_normal.png b/app/src/main/res/drawable-xhdpi/ic_item_category_normal.png new file mode 100644 index 0000000..b4e9b49 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_item_category_normal.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_black_24dp.png new file mode 100644 index 0000000..782eebf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png new file mode 100644 index 0000000..d42027e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..5386c0a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_hand.png b/app/src/main/res/drawable-xhdpi/icon_hand.png new file mode 100644 index 0000000..bf1db64 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_hand.png differ diff --git a/app/src/main/res/drawable-xhdpi/iconfont_password.png b/app/src/main/res/drawable-xhdpi/iconfont_password.png new file mode 100644 index 0000000..2cc8fd9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/iconfont_password.png differ diff --git a/app/src/main/res/drawable-xhdpi/iconfont_user.png b/app/src/main/res/drawable-xhdpi/iconfont_user.png new file mode 100644 index 0000000..8dfb2ee Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/iconfont_user.png differ diff --git a/app/src/main/res/drawable-xhdpi/owl_head.png b/app/src/main/res/drawable-xhdpi/owl_head.png new file mode 100644 index 0000000..efacadd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/owl_head.png differ diff --git a/app/src/main/res/drawable-xhdpi/setting_blue_deep_style_btn_bg.xml b/app/src/main/res/drawable-xhdpi/setting_blue_deep_style_btn_bg.xml new file mode 100644 index 0000000..03044ce --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_blue_deep_style_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_breen_style_btn_bg.xml b/app/src/main/res/drawable-xhdpi/setting_breen_style_btn_bg.xml new file mode 100644 index 0000000..23df42d --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_breen_style_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_common_style_btn_bg.xml b/app/src/main/res/drawable-xhdpi/setting_common_style_btn_bg.xml new file mode 100644 index 0000000..a055eb1 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_common_style_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_leather_style_btn_bg.xml b/app/src/main/res/drawable-xhdpi/setting_leather_style_btn_bg.xml new file mode 100644 index 0000000..affb285 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_leather_style_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_protect_eye_style_btn_bg.xml b/app/src/main/res/drawable-xhdpi/setting_protect_eye_style_btn_bg.xml new file mode 100644 index 0000000..37c9226 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_protect_eye_style_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle.xml b/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle.xml new file mode 100644 index 0000000..2825928 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle_selected.xml b/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle_selected.xml new file mode 100644 index 0000000..4b170f2 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_blue_deep_circle_selected.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_breen_circle.xml b/app/src/main/res/drawable-xhdpi/setting_style_breen_circle.xml new file mode 100644 index 0000000..6c0b696 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_breen_circle.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_breen_circle_selected.xml b/app/src/main/res/drawable-xhdpi/setting_style_breen_circle_selected.xml new file mode 100644 index 0000000..d5db6a4 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_breen_circle_selected.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_common_circle.xml b/app/src/main/res/drawable-xhdpi/setting_style_common_circle.xml new file mode 100644 index 0000000..e23c3f0 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_common_circle.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_common_circle_selected.xml b/app/src/main/res/drawable-xhdpi/setting_style_common_circle_selected.xml new file mode 100644 index 0000000..b6a6165 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_common_circle_selected.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_leather_circle.xml b/app/src/main/res/drawable-xhdpi/setting_style_leather_circle.xml new file mode 100644 index 0000000..f300a09 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_leather_circle.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_leather_circle_selected.xml b/app/src/main/res/drawable-xhdpi/setting_style_leather_circle_selected.xml new file mode 100644 index 0000000..a75c56a --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_leather_circle_selected.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle.xml b/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle.xml new file mode 100644 index 0000000..607998f --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle_selected.xml b/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle_selected.xml new file mode 100644 index 0000000..75e89fe --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/setting_style_protect_eye_circle_selected.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_history_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_history_black_24dp.png new file mode 100644 index 0000000..c9d3163 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_history_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_black_24dp.png new file mode 100644 index 0000000..63bfd2d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu_setting.png b/app/src/main/res/drawable-xxhdpi/ic_menu_setting.png new file mode 100644 index 0000000..4f9c0b4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_menu_setting.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png new file mode 100644 index 0000000..79aebbe Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..90581a9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_history_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_history_black_24dp.png new file mode 100644 index 0000000..72358f1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_history_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_black_24dp.png new file mode 100644 index 0000000..6818b9b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png new file mode 100644 index 0000000..766b4a4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000..d16e423 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml new file mode 100644 index 0000000..4cef809 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg1.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg1.xml new file mode 100644 index 0000000..3348b73 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg1.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/setting_btn_bg2.xml b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg2.xml new file mode 100644 index 0000000..2d2ea5c --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg2.xml @@ -0,0 +1,14 @@ + + + + + + + + \ 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 new file mode 100644 index 0000000..627c28d --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_btn_bg_color.xml @@ -0,0 +1,6 @@ + + + + + + \ 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_circle.xml new file mode 100644 index 0000000..128b945 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/setting_circle.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxxhdpi/suggest_item_bg.xml b/app/src/main/res/drawable-xxxhdpi/suggest_item_bg.xml new file mode 100644 index 0000000..d84b5e0 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/suggest_item_bg.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_skip.xml b/app/src/main/res/drawable/bg_skip.xml new file mode 100644 index 0000000..a4ffc4d --- /dev/null +++ b/app/src/main/res/drawable/bg_skip.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/editview_shape.xml b/app/src/main/res/drawable/editview_shape.xml new file mode 100644 index 0000000..f847108 --- /dev/null +++ b/app/src/main/res/drawable/editview_shape.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/font_btn_selector.xml b/app/src/main/res/drawable/font_btn_selector.xml new file mode 100644 index 0000000..44957c9 --- /dev/null +++ b/app/src/main/res/drawable/font_btn_selector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..c7542ba --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrange.xml b/app/src/main/res/drawable/ic_arrange.xml new file mode 100644 index 0000000..ce2d501 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrange.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_author.xml b/app/src/main/res/drawable/ic_author.xml new file mode 100644 index 0000000..3278e86 --- /dev/null +++ b/app/src/main/res/drawable/ic_author.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_backup.xml b/app/src/main/res/drawable/ic_backup.xml new file mode 100644 index 0000000..fe8737d --- /dev/null +++ b/app/src/main/res/drawable/ic_backup.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 0000000..af1de9c --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_disclaimer.xml b/app/src/main/res/drawable/ic_disclaimer.xml new file mode 100644 index 0000000..6cc4ea5 --- /dev/null +++ b/app/src/main/res/drawable/ic_disclaimer.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 0000000..e43b864 --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download_line.xml b/app/src/main/res/drawable/ic_download_line.xml new file mode 100644 index 0000000..3b0ae72 --- /dev/null +++ b/app/src/main/res/drawable/ic_download_line.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..3dabf49 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..93e07f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_stop_black_24dp.xml b/app/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 0000000..c5b211a --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update.xml b/app/src/main/res/drawable/ic_update.xml new file mode 100644 index 0000000..2490a57 --- /dev/null +++ b/app/src/main/res/drawable/ic_update.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_view_quilt.xml b/app/src/main/res/drawable/ic_view_quilt.xml new file mode 100644 index 0000000..a0fb3d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_view_quilt.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_web_outline.xml b/app/src/main/res/drawable/ic_web_outline.xml new file mode 100644 index 0000000..ec69306 --- /dev/null +++ b/app/src/main/res/drawable/ic_web_outline.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/input_bg_shape.xml b/app/src/main/res/drawable/input_bg_shape.xml new file mode 100644 index 0000000..c10b71a --- /dev/null +++ b/app/src/main/res/drawable/input_bg_shape.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/login_btn_selector.xml b/app/src/main/res/drawable/login_btn_selector.xml new file mode 100644 index 0000000..9df4169 --- /dev/null +++ b/app/src/main/res/drawable/login_btn_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_btn_backcolor.xml b/app/src/main/res/drawable/search_btn_backcolor.xml new file mode 100644 index 0000000..9e64f57 --- /dev/null +++ b/app/src/main/res/drawable/search_btn_backcolor.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_et_backcolor.xml b/app/src/main/res/drawable/search_et_backcolor.xml new file mode 100644 index 0000000..dcd93a2 --- /dev/null +++ b/app/src/main/res/drawable/search_et_backcolor.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/seekbar_bg.xml b/app/src/main/res/drawable/seekbar_bg.xml new file mode 100644 index 0000000..17b4047 --- /dev/null +++ b/app/src/main/res/drawable/seekbar_bg.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_category_load.xml b/app/src/main/res/drawable/selector_category_load.xml new file mode 100644 index 0000000..29e2ce1 --- /dev/null +++ b/app/src/main/res/drawable/selector_category_load.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_category_unload.xml b/app/src/main/res/drawable/selector_category_unload.xml new file mode 100644 index 0000000..aeb48bd --- /dev/null +++ b/app/src/main/res/drawable/selector_category_unload.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/start.jpg b/app/src/main/res/drawable/start.jpg new file mode 100644 index 0000000..bb2d64f Binary files /dev/null and b/app/src/main/res/drawable/start.jpg differ diff --git a/app/src/main/res/layout/activity_book_info.xml b/app/src/main/res/layout/activity_book_info.xml new file mode 100644 index 0000000..add0a94 --- /dev/null +++ b/app/src/main/res/layout/activity_book_info.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +