diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
index c297635..f4879e1 100644
Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ
diff --git a/README.md b/README.md
index fa52d82..9120666 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,12 @@
发现界面:排行榜、分类、书城
- 详细功能可查看图片或下载自行体验
+ 详细功能可查看图片或下载自行体验或自行编译
demo下载:https://fycz.lanzoui.com/iBofFh42pxg
+如有问题请加QQ群:1085028304
+



diff --git a/app/build.gradle b/app/build.gradle
index 637f1a4..2c03329 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'//greendao插件dependencies
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
def releaseTime() {
return new Date().format("yy.MMddHH", TimeZone.getTimeZone("GMT+08:00"))
@@ -78,7 +80,13 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
+ api 'androidx.core:core-ktx:1.3.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ //anko
+ def anko_version = '0.10.8'
+ implementation "org.jetbrains.anko:anko-sdk27:$anko_version"
+ implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
implementation 'com.jakewharton:butterknife:10.0.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dd10e23..e66becc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -122,6 +122,7 @@
+
diff --git a/app/src/main/assets/updatelog.fy b/app/src/main/assets/updatelog.fy
index ae88e67..e998b38 100644
--- a/app/src/main/assets/updatelog.fy
+++ b/app/src/main/assets/updatelog.fy
@@ -1,5 +1,11 @@
+2020.10.03
+风月读书v1.20.100315
+1、优化搜索书籍高频刷新的问题
+2、优化书籍默认封面显示
+3、修复换源对话框书源重复的问题
+
2020.10.02
-风月读书v1.20.100120
+风月读书v1.20.100220
1、新增书源:搜小说网、全小说网、奇奇小说
2、新增停止搜索按钮
3、优化搜索书籍显示
diff --git a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
index 4deef83..24976bb 100644
--- a/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
+++ b/app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
@@ -40,6 +40,7 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
+import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
@@ -95,7 +96,7 @@ public class MyApplication extends Application {
}
public boolean isNightFS() {
- return SharedPreUtils.getInstance().getBoolean("isNightFS", false);
+ return SharedPreUtils.getInstance().getBoolean(getString(R.string.isNightFS), false);
}
/**
@@ -103,7 +104,7 @@ public class MyApplication extends Application {
* @param isNightMode
*/
public void setNightTheme(boolean isNightMode) {
- SharedPreUtils.getInstance().putBoolean("isNightFS", false);
+ SharedPreUtils.getInstance().putBoolean(getmContext().getString(R.string.isNightFS), false);
Setting setting = SysManager.getSetting();
setting.setDayStyle(!isNightMode);
SysManager.saveSetting(setting);
@@ -181,6 +182,10 @@ public class MyApplication extends Application {
handler.post(runnable);
}
+ public static Handler getHandler(){
+ return handler;
+ }
+
public static MyApplication getApplication() {
return application;
}
@@ -313,11 +318,11 @@ public class MyApplication extends Application {
downloadLink = contents[2].substring(contents[2].indexOf(":") + 1).trim();
updateContent = contents[3].substring(contents[3].indexOf(":") + 1);
SharedPreUtils spu = SharedPreUtils.getInstance();
- spu.putString("lanzousKeyStart", contents[4].substring(contents[4].indexOf(":") + 1));
+ spu.putString(getmContext().getString(R.string.lanzousKeyStart), contents[4].substring(contents[4].indexOf(":") + 1));
if (!StringHelper.isEmpty(downloadLink)) {
- spu.putString("downloadLink", downloadLink);
+ spu.putString(getmContext().getString(R.string.downloadLink), downloadLink);
} else {
- spu.putString("downloadLink", URLCONST.APP_DIR_UR);
+ spu.putString(getmContext().getString(R.string.downloadLink), URLCONST.APP_DIR_UR);
}
String[] updateContents = updateContent.split("/");
for (String string : updateContents) {
diff --git a/app/src/main/java/xyz/fycz/myreader/base/observer/MyObserver.java b/app/src/main/java/xyz/fycz/myreader/base/observer/MyObserver.java
new file mode 100644
index 0000000..633e8a8
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/base/observer/MyObserver.java
@@ -0,0 +1,23 @@
+//Copyright (c) 2017. 章钦豪. All rights reserved.
+package xyz.fycz.myreader.base.observer;
+
+import io.reactivex.Observer;
+import io.reactivex.disposables.Disposable;
+
+public abstract class MyObserver implements Observer {
+
+ @Override
+ public void onSubscribe(Disposable d) {
+
+ }
+
+ @Override
+ public void onError(Throwable e) {
+
+ }
+
+ @Override
+ public void onComplete() {
+
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/base/observer/MySingleObserver.java b/app/src/main/java/xyz/fycz/myreader/base/observer/MySingleObserver.java
new file mode 100644
index 0000000..154b9cb
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/base/observer/MySingleObserver.java
@@ -0,0 +1,17 @@
+package xyz.fycz.myreader.base.observer;
+
+import io.reactivex.SingleObserver;
+import io.reactivex.disposables.Disposable;
+
+public abstract class MySingleObserver implements SingleObserver {
+
+ @Override
+ public void onSubscribe(Disposable d) {
+
+ }
+
+ @Override
+ public void onError(Throwable e) {
+
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
index c893364..e0843dd 100644
--- a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
+++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
@@ -84,4 +84,7 @@ public class APPCONST {
public static final String FORMAT_FILE_DATE = "yyyy-MM-dd";
public final static String channelIdDownload = "channel_download";
+
+ public static final String DEFAULT_WEB_DAV_URL = "https://dav.jianguoyun.com/dav/";
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/common/Common.java b/app/src/main/java/xyz/fycz/myreader/common/Common.java
deleted file mode 100644
index ea1cb64..0000000
--- a/app/src/main/java/xyz/fycz/myreader/common/Common.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/greendao/service/BookService.java b/app/src/main/java/xyz/fycz/myreader/greendao/service/BookService.java
index d725ba1..ac1eb27 100644
--- a/app/src/main/java/xyz/fycz/myreader/greendao/service/BookService.java
+++ b/app/src/main/java/xyz/fycz/myreader/greendao/service/BookService.java
@@ -6,6 +6,8 @@ import android.text.TextUtils;
import net.ricecode.similarity.JaroWinklerStrategy;
import net.ricecode.similarity.StringSimilarityService;
import net.ricecode.similarity.StringSimilarityServiceImpl;
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.greendao.entity.Chapter;
@@ -101,7 +103,7 @@ public class BookService extends BaseService {
book.setSortCode(0);
book.setGroupSort(0);
- book.setGroupId(SharedPreUtils.getInstance().getString("curBookGroupId", ""));
+ book.setGroupId(SharedPreUtils.getInstance().getString(MyApplication.getmContext().getString(R.string.curBookGroupId), ""));
if (StringHelper.isEmpty(book.getId())) {
book.setId(StringHelper.getStringRandom(25));
}
diff --git a/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java b/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java
index c6d040a..3e97682 100644
--- a/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java
+++ b/app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java
@@ -7,13 +7,19 @@ import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.greendao.entity.Chapter;
+import xyz.fycz.myreader.greendao.service.BookMarkService;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.webapi.CommonApi;
+import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import java.io.UnsupportedEncodingException;
@@ -44,7 +50,7 @@ public class SearchEngine {
private OnSearchListener searchListener;
public SearchEngine() {
- threadsNum = SharedPreUtils.getInstance().getInt("threadNum", 8);
+ threadsNum = SharedPreUtils.getInstance().getInt(MyApplication.getmContext().getString(R.string.threadNum), 8);
}
public void setOnSearchListener(OnSearchListener searchListener) {
@@ -237,6 +243,33 @@ public class SearchEngine {
}
+ public synchronized void getBookInfo(Book book, BookInfoCrawler bic, OnGetBookInfoListener listener){
+ CommonApi.getBookInfo(book, bic)
+ .subscribeOn(scheduler)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@io.reactivex.annotations.NonNull Book book) {
+ listener.loadFinish(true);
+ }
+
+ @Override
+ public void onError(@io.reactivex.annotations.NonNull Throwable e) {
+ listener.loadFinish(false);
+ }
+
+ @Override
+ public void onComplete() {
+
+ }
+ });
+ }
+
/************************************************************************/
public interface OnSearchListener {
@@ -250,4 +283,16 @@ public class SearchEngine {
void searchBookError(Throwable throwable);
}
+
+ public interface OnGetBookInfoListener{
+ void loadFinish(Boolean isSuccess);
+ }
+
+ public interface OnGetBookChaptersListener{
+ void loadFinish(List chapters, Boolean isSuccess);
+ }
+
+ public interface OnGetChapterContentListener{
+ void loadFinish(String content, Boolean isSuccess);
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java b/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java
index c7aa256..be9c073 100644
--- a/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java
+++ b/app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java
@@ -1,6 +1,9 @@
package xyz.fycz.myreader.model.backup;
+import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.model.storage.Backup;
+import xyz.fycz.myreader.model.storage.Restore;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
@@ -190,68 +193,80 @@ public class UserService {
* 网络备份
* @return
*/
- public static boolean webBackup(){
+ public static void webBackup(ResultCallback rc){
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;
+ rc.onFinish(false, 0);
}
- File zipFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup.zip");
- FileInputStream fis = null;
- HttpURLConnection conn = null;
- try {
- //压缩文件
- ZipUtils.zipFile(inputFile, zipFile);
- fis = new FileInputStream(zipFile);
- URL url = new URL(URLCONST.APP_WEB_URL + "bak?username=" + userInfo.get("userName") +
- makeSignalParam());
- conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-type", "multipart/form-data");
- conn.setDoInput(true);
- conn.setDoOutput(true);
- OutputStream out = conn.getOutputStream();
- byte[] bytes = new byte[1024];
- int len = -1;
- while ((len = fis.read(bytes)) != -1){
- out.write(bytes, 0, len);
+ Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.FILE_DIR + "webBackup/", new Backup.CallBack() {
+ @Override
+ public void backupSuccess() {
+ MyApplication.getApplication().newThread(() ->{
+ File inputFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup");
+ if (!inputFile.exists()) {
+ rc.onFinish(false, 0);
+ }
+ File zipFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup.zip");
+ FileInputStream fis = null;
+ HttpURLConnection conn = null;
+ try {
+ //压缩文件
+ ZipUtils.zipFile(inputFile, zipFile);
+ fis = new FileInputStream(zipFile);
+ URL url = new URL(URLCONST.APP_WEB_URL + "bak?username=" + userInfo.get("userName") +
+ makeSignalParam());
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-type", "multipart/form-data");
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ OutputStream out = conn.getOutputStream();
+ byte[] bytes = new byte[1024];
+ int len = -1;
+ while ((len = fis.read(bytes)) != -1){
+ out.write(bytes, 0, len);
+ }
+ out.flush();
+ zipFile.delete();
+ BufferedReader bw = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
+ StringBuilder sb = new StringBuilder();
+ String line = bw.readLine();
+ while (line != null) {
+ sb.append(line);
+ line = bw.readLine();
+ }
+ String[] info = sb.toString().split(":");
+ int code = Integer.parseInt(info[0].trim());
+ rc.onFinish(code == 104, 0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ rc.onError(e);
+ } finally {
+ IOUtils.close(fis);
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ });
}
- 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();
+
+ @Override
+ public void backupError(@NonNull String msg) {
+ ToastUtils.showError(msg);
+ rc.onFinish(false, 0);
}
- }
+ }, false);
+
}
/**
* 网络恢复
* @return
*/
- public static boolean webRestore(){
+ public static void webRestore(ResultCallback rc){
Map userInfo = readConfig();
if (userInfo == null){
- return false;
+ rc.onFinish(false, 0);
}
FileOutputStream fos = null;
File zipFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup.zip");
@@ -274,16 +289,25 @@ public class UserService {
fos.flush();
if (zipFile.length() == 0){
zipFile.delete();
- return false;
+ rc.onFinish(false, 0);
}
ZipUtils.unzipFile(zipFile.getAbsolutePath(), APPCONST.FILE_DIR);
- BackupAndRestore bar = new BackupAndRestore();
- bar.restore("webBackup");
- zipFile.delete();
- return true;
+ Restore.INSTANCE.restore(APPCONST.FILE_DIR + "webBackup/", new Restore.CallBack() {
+ @Override
+ public void restoreSuccess() {
+ zipFile.delete();
+ rc.onFinish(true, 0);
+ }
+
+ @Override
+ public void restoreError(@NonNull String msg) {
+ ToastUtils.showError(msg);
+ rc.onFinish(false, 0);
+ }
+ });
} catch (Exception e) {
e.printStackTrace();
- return false;
+ rc.onError(e);
}finally {
IOUtils.close(fos);
if (conn != null) {
diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt
new file mode 100644
index 0000000..5193400
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt
@@ -0,0 +1,178 @@
+package xyz.fycz.myreader.model.storage
+
+import android.content.Context
+import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
+
+import io.reactivex.Single
+import io.reactivex.SingleOnSubscribe
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import xyz.fycz.myreader.application.MyApplication
+import xyz.fycz.myreader.application.SysManager
+import xyz.fycz.myreader.base.observer.MySingleObserver
+import xyz.fycz.myreader.common.APPCONST
+import xyz.fycz.myreader.greendao.GreenDaoManager
+import xyz.fycz.myreader.greendao.service.BookMarkService
+import xyz.fycz.myreader.greendao.service.BookService
+import xyz.fycz.myreader.greendao.service.SearchHistoryService
+import xyz.fycz.myreader.util.SharedPreUtils
+import xyz.fycz.myreader.util.utils.DocumentUtil
+import xyz.fycz.myreader.util.utils.FileUtils
+import xyz.fycz.myreader.util.utils.GSON
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+
+object Backup {
+
+ val backupPath = MyApplication.getApplication().filesDir.absolutePath + File.separator + "backup"
+
+ val defaultPath by lazy {
+ APPCONST.BACKUP_FILE_DIR
+ }
+
+ val backupFileNames by lazy {
+ arrayOf(
+ "myBooks.json",
+ "mySearchHistory.json",
+ "myBookMark.json",
+ "myBookGroup.json",
+ "setting.json",
+ "config.xml"
+ )
+ }
+
+ fun autoBack() {
+ val lastBackup = SharedPreUtils.getInstance().getLong("lastBackup", 0)
+ if (System.currentTimeMillis() - lastBackup < TimeUnit.DAYS.toMillis(1)) {
+ return
+ }
+ val path = SharedPreUtils.getInstance().getString("backupPath", defaultPath)
+ if (path == null) {
+ backup(MyApplication.getmContext(), defaultPath, null, true)
+ } else {
+ backup(MyApplication.getmContext(), path, null, true)
+ }
+ }
+
+ fun backup(context: Context, path: String, callBack: CallBack?, isAuto: Boolean = false) {
+ SharedPreUtils.getInstance().putLong("lastBackup", System.currentTimeMillis())
+ Single.create(SingleOnSubscribe { e ->
+ BookService.getInstance().allBooks.let {
+ if (it.isNotEmpty()) {
+ val json = GSON.toJson(it)
+ FileUtils.getFile(backupPath + File.separator + "myBooks.json").writeText(json)
+ }
+ }
+ SearchHistoryService.getInstance().findAllSearchHistory().let {
+ if (it.isNotEmpty()) {
+ val json = GSON.toJson(it)
+ FileUtils.getFile(backupPath + File.separator + "mySearchHistory.json")
+ .writeText(json)
+ }
+ }
+ GreenDaoManager.getInstance().session.bookMarkDao.queryBuilder().list().let {
+ if (it.isNotEmpty()) {
+ val json = GSON.toJson(it)
+ FileUtils.getFile(backupPath + File.separator + "myBookMark.json")
+ .writeText(json)
+ }
+ }
+ GreenDaoManager.getInstance().session.bookGroupDao.queryBuilder().list().let {
+ if (it.isNotEmpty()) {
+ val json = GSON.toJson(it)
+ FileUtils.getFile(backupPath + File.separator + "myBookGroup.json")
+ .writeText(json)
+ }
+ }
+ val json = GSON.toJson(SysManager.getSetting())
+ FileUtils.getFile(backupPath + File.separator + "setting.json")
+ .writeText(json)
+ Preferences.getSharedPreferences(context, backupPath, "config")?.let { sp ->
+ val edit = sp.edit()
+ SharedPreUtils.getInstance().all.map {
+ when (val value = it.value) {
+ is Int -> edit.putInt(it.key, value)
+ is Boolean -> edit.putBoolean(it.key, value)
+ is Long -> edit.putLong(it.key, value)
+ is Float -> edit.putFloat(it.key, value)
+ is String -> edit.putString(it.key, value)
+ else -> Unit
+ }
+ }
+ edit.commit()
+ }
+ WebDavHelp.backUpWebDav(backupPath)
+ if (path.isContentPath()) {
+ copyBackup(context, Uri.parse(path), isAuto)
+ } else {
+ copyBackup(path, isAuto)
+ }
+ e.onSuccess(true)
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : MySingleObserver() {
+ override fun onSuccess(t: Boolean) {
+ callBack?.backupSuccess()
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ callBack?.backupError(e.localizedMessage ?: "ERROR")
+ }
+ })
+ }
+
+ @Throws(Exception::class)
+ private fun copyBackup(context: Context, uri: Uri, isAuto: Boolean) {
+ synchronized(this) {
+ DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
+ for (fileName in backupFileNames) {
+ val file = File(backupPath + File.separator + fileName)
+ if (file.exists()) {
+ if (isAuto) {
+ treeDoc.findFile("auto")?.findFile(fileName)?.delete()
+ var autoDoc = treeDoc.findFile("auto")
+ if (autoDoc == null) {
+ autoDoc = treeDoc.createDirectory("auto")
+ }
+ autoDoc?.createFile("", fileName)?.let {
+ DocumentUtil.writeBytes(context, file.readBytes(), it)
+ }
+ } else {
+ treeDoc.findFile(fileName)?.delete()
+ treeDoc.createFile("", fileName)?.let {
+ DocumentUtil.writeBytes(context, file.readBytes(), it)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Throws(java.lang.Exception::class)
+ private fun copyBackup(path: String, isAuto: Boolean) {
+ synchronized(this) {
+ for (fileName in backupFileNames) {
+ if (isAuto) {
+ val file = File(backupPath + File.separator + fileName)
+ if (file.exists()) {
+ file.copyTo(FileUtils.getFile(path + File.separator + "auto" + File.separator + fileName), true)
+ }
+ } else {
+ val file = File(backupPath + File.separator + fileName)
+ if (file.exists()) {
+ file.copyTo(FileUtils.getFile(path + File.separator + fileName), true)
+ }
+ }
+ }
+ }
+ }
+
+ interface CallBack {
+ fun backupSuccess()
+ fun backupError(msg: String)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt
new file mode 100644
index 0000000..f39c9a5
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt
@@ -0,0 +1,239 @@
+package xyz.fycz.myreader.model.storage
+
+import android.app.Activity
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import android.net.Uri
+import android.text.TextUtils
+import androidx.core.content.ContextCompat
+import androidx.documentfile.provider.DocumentFile
+import io.reactivex.Single
+import io.reactivex.SingleEmitter
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import org.jetbrains.anko.alert
+import org.jetbrains.anko.toast
+import xyz.fycz.myreader.base.observer.MySingleObserver
+import xyz.fycz.myreader.common.APPCONST
+import xyz.fycz.myreader.model.storage.WebDavHelp.getWebDavFileNames
+import xyz.fycz.myreader.model.storage.WebDavHelp.showRestoreDialog
+import xyz.fycz.myreader.util.SharedPreUtils
+import xyz.fycz.myreader.util.ToastUtils
+import java.util.*
+
+object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
+
+ private const val backupSelectRequestCode = 22
+ private const val restoreSelectRequestCode = 33
+
+ private fun getBackupPath(): String? {
+ return SharedPreUtils.getInstance().getString("backupPath", APPCONST.BACKUP_FILE_DIR)
+ }
+
+ private fun setBackupPath(path: String?) {
+ if (path.isNullOrEmpty()) {
+ SharedPreUtils.getInstance().remove("backupPath")
+ } else {
+ SharedPreUtils.getInstance().putString("backupPath", path)
+ }
+ }
+
+ override fun backupSuccess() {
+ ToastUtils.showSuccess("备份成功")
+ }
+
+ override fun backupError(msg: String) {
+ ToastUtils.showError(msg)
+ }
+
+ override fun restoreSuccess() {
+ ToastUtils.showSuccess("恢复成功")
+ }
+
+ override fun restoreError(msg: String) {
+ ToastUtils.showError(msg)
+ }
+
+ fun backup(activity: Activity) {
+ val backupPath = getBackupPath()
+ if (backupPath.isNullOrEmpty()) {
+// selectBackupFolder(activity)
+ ToastUtils.showError("backupPath.isNullOrEmpty")
+ } else {
+ if (backupPath.isContentPath()) {
+ val uri = Uri.parse(backupPath)
+ val doc = DocumentFile.fromTreeUri(activity, uri)
+ if (doc?.canWrite() == true) {
+ Backup.backup(activity, backupPath, this)
+ } else {
+// selectBackupFolder(activity)
+ ToastUtils.showError("doc?.canWrite() != true")
+ }
+ } else {
+// backupUsePermission(activity)
+ ToastUtils.showError("backupPath.isNotContentPath")
+ }
+ }
+ }
+
+ /*private fun backupUsePermission(activity: Activity, path: String = Backup.defaultPath) {
+ PermissionsCompat.Builder(activity)
+ .addPermissions(*Permissions.Group.STORAGE)
+ .rationale(R.string.get_storage_per)
+ .onGranted {
+ setBackupPath(path)
+ Backup.backup(activity, path, this)
+ }
+ .request()
+ }
+
+ fun selectBackupFolder(activity: Activity) {
+ activity.alert {
+ titleResource = R.string.select_folder
+ items(activity.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
+ when (index) {
+ 0 -> {
+ setBackupPath(Backup.defaultPath)
+ backupUsePermission(activity)
+ }
+ 1 -> {
+ try {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ activity.startActivityForResult(intent, backupSelectRequestCode)
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ activity.toast(e.localizedMessage ?: "ERROR")
+ }
+ }
+ 2 -> {
+ PermissionsCompat.Builder(activity)
+ .addPermissions(*Permissions.Group.STORAGE)
+ .rationale(R.string.get_storage_per)
+ .onGranted {
+ selectBackupFolderApp(activity, false)
+ }
+ .request()
+ }
+ }
+ }
+ }.show()
+ }
+
+ private fun selectBackupFolderApp(activity: Activity, isRestore: Boolean) {
+ val picker = FilePicker(activity, FilePicker.DIRECTORY)
+ picker.setBackgroundColor(ContextCompat.getColor(activity, R.color.background))
+ picker.setTopBackgroundColor(ContextCompat.getColor(activity, R.color.background))
+ picker.setItemHeight(30)
+ picker.setOnFilePickListener { currentPath: String ->
+ setBackupPath(currentPath)
+ if (isRestore) {
+ Restore.restore(currentPath, this)
+ } else {
+ Backup.backup(activity, currentPath, this)
+ }
+ }
+ picker.show()
+ }*/
+
+ fun restore(activity: Activity) {
+ Single.create { emitter: SingleEmitter?> ->
+ emitter.onSuccess(getWebDavFileNames())
+ }.subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : MySingleObserver?>() {
+ override fun onSuccess(strings: ArrayList) {
+ if (!showRestoreDialog(activity, strings, this@BackupRestoreUi)) {
+ val path = getBackupPath()
+ if (TextUtils.isEmpty(path)) {
+ //selectRestoreFolder(activity)
+ ToastUtils.showError("TextUtils.isEmpty(path)")
+ } else {
+ if (path.isContentPath()) {
+ val uri = Uri.parse(path)
+ val doc = DocumentFile.fromTreeUri(activity, uri)
+ if (doc?.canWrite() == true) {
+ Restore.restore(activity, Uri.parse(path), this@BackupRestoreUi)
+ } else {
+// selectRestoreFolder(activity)
+ ToastUtils.showError("doc?.canWrite() != true")
+ }
+ } else {
+// restoreUsePermission(activity)
+ ToastUtils.showError("path.isNotContentPath")
+ }
+ }
+ }
+ }
+ })
+ }
+
+ /*private fun restoreUsePermission(activity: Activity, path: String = Backup.defaultPath) {
+ PermissionsCompat.Builder(activity)
+ .addPermissions(*Permissions.Group.STORAGE)
+ .rationale(R.string.get_storage_per)
+ .onGranted {
+ setBackupPath(path)
+ Restore.restore(path, this)
+ }
+ .request()
+ }
+
+ private fun selectRestoreFolder(activity: Activity) {
+ activity.alert {
+ titleResource = R.string.select_folder
+ items(activity.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
+ when (index) {
+ 0 -> restoreUsePermission(activity)
+ 1 -> {
+ try {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ activity.startActivityForResult(intent, restoreSelectRequestCode)
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ activity.toast(e.localizedMessage ?: "ERROR")
+ }
+ }
+ 2 -> {
+ PermissionsCompat.Builder(activity)
+ .addPermissions(*Permissions.Group.STORAGE)
+ .rationale(R.string.get_storage_per)
+ .onGranted {
+ selectBackupFolderApp(activity, true)
+ }
+ .request()
+ }
+ }
+ }
+ }.show()
+ }*/
+
+ /*fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ when (requestCode) {
+ backupSelectRequestCode -> if (resultCode == RESULT_OK) {
+ data?.data?.let { uri ->
+ MApplication.getInstance().contentResolver.takePersistableUriPermission(
+ uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ setBackupPath(uri.toString())
+ Backup.backup(MApplication.getInstance(), uri.toString(), this)
+ }
+ }
+ restoreSelectRequestCode -> if (resultCode == RESULT_OK) {
+ data?.data?.let { uri ->
+ MApplication.getInstance().contentResolver.takePersistableUriPermission(
+ uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ )
+ setBackupPath(uri.toString())
+ Restore.restore(MApplication.getInstance(), uri, this)
+ }
+ }
+ }
+ }*/
+
+}
+
+fun String?.isContentPath(): Boolean = this?.startsWith("content://") == true
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/Preferences.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/Preferences.kt
new file mode 100644
index 0000000..9cd354c
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/model/storage/Preferences.kt
@@ -0,0 +1,48 @@
+package xyz.fycz.myreader.model.storage
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.SharedPreferences
+import java.io.File
+
+object Preferences {
+
+ /**
+ * 用反射生成 SharedPreferences
+ * @param context
+ * @param dir
+ * @param fileName 文件名,不需要 '.xml' 后缀
+ * @return
+ */
+ fun getSharedPreferences(
+ context: Context,
+ dir: String,
+ fileName: String
+ ): SharedPreferences? {
+ try {
+ // 获取 ContextWrapper对象中的mBase变量。该变量保存了 ContextImpl 对象
+ val fieldMBase = ContextWrapper::class.java.getDeclaredField("mBase")
+ fieldMBase.isAccessible = true
+ // 获取 mBase变量
+ val objMBase = fieldMBase.get(context)
+ // 获取 ContextImpl.mPreferencesDir变量,该变量保存了数据文件的保存路径
+ val fieldMPreferencesDir = objMBase.javaClass.getDeclaredField("mPreferencesDir")
+ fieldMPreferencesDir.isAccessible = true
+ // 创建自定义路径
+ // String FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Android";
+ val file = File(dir)
+ // 修改mPreferencesDir变量的值
+ fieldMPreferencesDir.set(objMBase, file)
+ // 返回修改路径以后的 SharedPreferences :%FILE_PATH%/%fileName%.xml
+ return context.getSharedPreferences(fileName, Activity.MODE_PRIVATE)
+ } catch (e: NoSuchFieldException) {
+ e.printStackTrace()
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt
new file mode 100644
index 0000000..5215a73
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt
@@ -0,0 +1,137 @@
+package xyz.fycz.myreader.model.storage
+
+import android.content.Context
+import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
+import io.reactivex.Single
+import io.reactivex.SingleOnSubscribe
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import xyz.fycz.myreader.application.MyApplication
+import xyz.fycz.myreader.application.SysManager
+import xyz.fycz.myreader.base.observer.MySingleObserver
+import xyz.fycz.myreader.entity.Setting
+import xyz.fycz.myreader.greendao.GreenDaoManager
+import xyz.fycz.myreader.greendao.entity.Book
+import xyz.fycz.myreader.greendao.entity.BookGroup
+import xyz.fycz.myreader.greendao.entity.BookMark
+import xyz.fycz.myreader.greendao.entity.SearchHistory
+import xyz.fycz.myreader.util.SharedPreUtils
+import xyz.fycz.myreader.util.utils.*
+import java.io.File
+
+object Restore {
+
+ fun restore(context: Context, uri: Uri, callBack: CallBack?) {
+ Single.create(SingleOnSubscribe { e ->
+ DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach { doc ->
+ for (fileName in Backup.backupFileNames) {
+ if (doc.name == fileName) {
+ DocumentUtil.readBytes(context, doc.uri)?.let {
+ FileUtils.getFile(Backup.backupPath + File.separator + fileName)
+ .writeBytes(it)
+ }
+ }
+ }
+ }
+ e.onSuccess(true)
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : MySingleObserver() {
+ override fun onSuccess(t: Boolean) {
+ restore(Backup.backupPath, callBack)
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ callBack?.restoreError(e.localizedMessage ?: "ERROR")
+ }
+ })
+ }
+
+ fun restore(path: String, callBack: CallBack?) {
+ Single.create(SingleOnSubscribe { e ->
+ try {
+ val file = FileUtils.getFile(path + File.separator + "myBooks.json")
+ val json = file.readText()
+ GSON.fromJsonArray(json)?.forEach { bookshelf ->
+ /*if (bookshelf.noteUrl != null) {
+ DbHelper.getDaoSession().bookShelfBeanDao.insertOrReplace(bookshelf)
+ }
+ if (bookshelf.bookInfoBean.noteUrl != null) {
+ DbHelper.getDaoSession().bookInfoBeanDao.insertOrReplace(bookshelf.bookInfoBean)
+ }*/
+ GreenDaoManager.getInstance().session.bookDao.insertOrReplace(bookshelf)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ try {
+ val file = FileUtils.getFile(path + File.separator + "mySearchHistory.json")
+ val json = file.readText()
+ GSON.fromJsonArray(json)?.let {
+ GreenDaoManager.getInstance().session.searchHistoryDao.insertOrReplaceInTx(it)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ try {
+ val file = FileUtils.getFile(path + File.separator + "myBookMark.json")
+ val json = file.readText()
+ GSON.fromJsonArray(json)?.let {
+ GreenDaoManager.getInstance().session.bookMarkDao.insertOrReplaceInTx(it)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ try {
+ val file = FileUtils.getFile(path + File.separator + "myBookGroup.json")
+ val json = file.readText()
+ GSON.fromJsonArray(json)?.let {
+ GreenDaoManager.getInstance().session.bookGroupDao.insertOrReplaceInTx(it)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ try {
+ val file = FileUtils.getFile(path + File.separator + "setting.json")
+ val json = file.readText()
+ SysManager.saveSetting(GSON.fromJsonObject(json))
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ Preferences.getSharedPreferences(MyApplication.getmContext(), path, "config")?.all?.map {
+ val edit = SharedPreUtils.getInstance()
+ when (val value = it.value) {
+ is Int -> edit.putInt(it.key, value)
+ is Boolean -> edit.putBoolean(it.key, value)
+ is Long -> edit.putLong(it.key, value)
+ is Float -> edit.putFloat(it.key, value)
+ is String -> edit.putString(it.key, value)
+ else -> Unit
+ }
+ edit.putInt("versionCode", MyApplication.getVersionCode())
+ }
+ e.onSuccess(true)
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : MySingleObserver() {
+ override fun onSuccess(t: Boolean) {
+ MyApplication.getApplication().initNightTheme()
+ callBack?.restoreSuccess()
+ }
+
+ override fun onError(e: Throwable) {
+ e.printStackTrace()
+ callBack?.restoreError(e.localizedMessage ?: "ERROR")
+ }
+ })
+ }
+
+
+ interface CallBack {
+ fun restoreSuccess()
+ fun restoreError(msg: String)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt b/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt
new file mode 100644
index 0000000..ba2e8d0
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/model/storage/WebDavHelp.kt
@@ -0,0 +1,121 @@
+package xyz.fycz.myreader.model.storage
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import io.reactivex.Single
+import io.reactivex.SingleOnSubscribe
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import org.jetbrains.anko.selector
+import xyz.fycz.myreader.base.observer.MySingleObserver
+import xyz.fycz.myreader.common.APPCONST
+import xyz.fycz.myreader.util.SharedPreUtils
+import xyz.fycz.myreader.util.ToastUtils
+import xyz.fycz.myreader.util.ZipUtils
+import xyz.fycz.myreader.util.utils.FileUtils
+import xyz.fycz.myreader.util.webdav.WebDav
+import xyz.fycz.myreader.util.webdav.http.HttpAuth
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.math.min
+
+object WebDavHelp {
+ private val zipFilePath = FileUtils.getCachePath() + "/backup" + ".zip"
+ private val unzipFilesPath by lazy {
+ FileUtils.getCachePath()
+ }
+
+ private fun getWebDavUrl(): String {
+ var url = SharedPreUtils.getInstance().getString("webdavUrl", APPCONST.DEFAULT_WEB_DAV_URL)
+ if (url.isNullOrEmpty()) {
+ url = APPCONST.DEFAULT_WEB_DAV_URL
+ }
+ if (!url.endsWith("/")) url += "/"
+ return url
+ }
+
+ private fun initWebDav(): Boolean {
+ val account = SharedPreUtils.getInstance().getString("webdavAccount", "")
+ val password = SharedPreUtils.getInstance().getString("webdavPassword", "")
+ if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
+ HttpAuth.auth = HttpAuth.Auth(account, password)
+ return true
+ }
+ return false
+ }
+
+ fun getWebDavFileNames(): ArrayList {
+ val url = getWebDavUrl()
+ val names = arrayListOf()
+ try {
+ if (initWebDav()) {
+ var files = WebDav(url + "FYReader/").listFiles()
+ files = files.reversed()
+ for (index: Int in 0 until min(10, files.size)) {
+ files[index].displayName?.let {
+ names.add(it)
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return names
+ }
+
+ fun showRestoreDialog(context: Context, names: ArrayList, callBack: Restore.CallBack?): Boolean {
+ return if (names.isNotEmpty()) {
+ context.selector(title = "选择恢复文件", items = names) { _, index ->
+ if (index in 0 until names.size) {
+ restoreWebDav(names[index], callBack)
+ }
+ }
+ true
+ } else {
+ false
+ }
+ }
+
+ private fun restoreWebDav(name: String, callBack: Restore.CallBack?) {
+ Single.create(SingleOnSubscribe { e ->
+ getWebDavUrl().let {
+ val file = WebDav(it + "FYReader/" + name)
+ file.downloadTo(zipFilePath, true)
+ @Suppress("BlockingMethodInNonBlockingContext")
+ ZipUtils.unzipFile(zipFilePath, unzipFilesPath)
+ }
+ e.onSuccess(true)
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : MySingleObserver() {
+ override fun onSuccess(t: Boolean) {
+ Restore.restore(unzipFilesPath, callBack)
+ }
+ })
+ }
+
+ fun backUpWebDav(path: String) {
+ try {
+ if (initWebDav()) {
+ val paths = arrayListOf(*Backup.backupFileNames)
+ for (i in 0 until paths.size) {
+ paths[i] = path + File.separator + paths[i]
+ }
+ FileUtils.deleteFile(zipFilePath)
+ if (ZipUtils.zipFiles(paths, zipFilePath)) {
+ WebDav(getWebDavUrl() + "FYReader").makeAsDir()
+ val putUrl = getWebDavUrl() + "FYReader/backup" +
+ SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ .format(Date(System.currentTimeMillis())) + ".zip"
+ WebDav(putUrl).upload(zipFilePath)
+ }
+ }
+ } catch (e: Exception) {
+ Handler(Looper.getMainLooper()).post {
+ ToastUtils.showError("WebDav\n${e.localizedMessage}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
index 68c4ea4..b1fdec1 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
@@ -12,6 +12,7 @@ import butterknife.BindView;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.base.BaseActivity2;
+import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.util.ShareUtils;
import xyz.fycz.myreader.util.SharedPreUtils;
@@ -71,7 +72,7 @@ public class AboutActivity extends BaseActivity2 {
ToastUtils.showSuccess("邮箱复制成功!");
});
vmShare.setOnClickListener(v -> ShareUtils.share(this, getString(R.string.share_text) +
- SharedPreUtils.getInstance().getString("downloadLink")));
+ SharedPreUtils.getInstance().getString(getString(R.string.downloadLink, URLCONST.LAN_ZOUS_URL))));
vmUpdate.setOnClickListener(v -> MyApplication.checkVersionByServer(this, true, null));
vmUpdateLog.setOnClickListener(v -> DialogCreator.createAssetTipDialog(this, "更新日志", "updatelog.fy"));
vmQQ.setOnClickListener(v -> {
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
index 33f4eb8..326baa1 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
@@ -42,6 +42,7 @@ import xyz.fycz.myreader.ui.adapter.DetailCatalogAdapter;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.webapi.CommonApi;
+import xyz.fycz.myreader.widget.CoverImageView;
import java.util.ArrayList;
@@ -51,7 +52,7 @@ import java.util.ArrayList;
*/
public class BookDetailedActivity extends BaseActivity2 {
@BindView(R.id.book_detail_iv_cover)
- ImageView mIvCover;
+ CoverImageView mIvCover;
/* @BindView(R.id.book_detail_iv_blur_cover)
ImageView mIvBlurCover;*/
@BindView(R.id.book_detail_tv_author)
@@ -323,13 +324,7 @@ public class BookDetailedActivity extends BaseActivity2 {
mTvDesc.setText("\t\t\t\t" + mBook.getDesc());
mTvType.setText(mBook.getType());
if (!MyApplication.isDestroy(this)) {
- Glide.with(this)
- .load(mBook.getImgUrl())
- .error(R.mipmap.no_image)
- .placeholder(R.mipmap.no_image)
- //设置圆角
- .apply(RequestOptions.bitmapTransform(new RoundedCorners(8)))
- .into(mIvCover);
+ mIvCover.load(mBook.getImgUrl(), mBook.getName(), mBook.getAuthor());
/*Glide.with(this)
.load(mBook.getImgUrl())
.transition(DrawableTransitionOptions.withCrossFade(1500))
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java
index 092dc91..4ebbc5e 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java
@@ -204,11 +204,11 @@ public class BookstoreActivity extends BaseActivity2 {
getData();
if (findCrawler.hasImg()) {
SharedPreUtils spu = SharedPreUtils.getInstance();
- boolean isReadTopTip = spu.getBoolean("isReadTopTip", false);
+ boolean isReadTopTip = spu.getBoolean(getString(R.string.isReadTopTip), false);
if (!isReadTopTip) {
DialogCreator.createCommonDialog(this, "提示", getResources().getString(R.string.top_sort_tip, title),
true, "知道了", "不再提示", null,
- (dialog, which) -> spu.putBoolean("isReadTopTip", true));
+ (dialog, which) -> spu.putBoolean(getString(R.string.isReadTopTip), true));
}
}
}
@@ -219,11 +219,11 @@ public class BookstoreActivity extends BaseActivity2 {
private void getData() {
if (findCrawler instanceof QiDianMobileRank) {
SharedPreUtils spu = SharedPreUtils.getInstance();
- if (spu.getString("qdCookie", "").equals("")) {
+ if (spu.getString(getString(R.string.qdCookie), "").equals("")) {
((QiDianMobileRank) findCrawler).initCookie(this, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
- spu.putString("qdCookie", (String) o);
+ spu.putString(getString(R.string.qdCookie), (String) o);
mBookTypes = ((QiDianMobileRank) findCrawler).getRankTypes();
curType = mBookTypes.get(0);
mHandler.sendMessage(mHandler.obtainMessage(1));
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java
index c879720..436afb7 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java
@@ -76,7 +76,7 @@ public class MainActivity extends BaseActivity2 {
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
- groupName = SharedPreUtils.getInstance().getString("curBookGroupName", "");
+ groupName = SharedPreUtils.getInstance().getString(getString(R.string.curBookGroupName), "");
titles = new String[]{"书架", "发现", "我的"};
mBookcaseFragment = new BookcaseFragment();
mFindFragment = new FindFragment();
@@ -235,7 +235,7 @@ public class MainActivity extends BaseActivity2 {
case R.id.action_change_group: case R.id.action_group_man:
if (!mBookcaseFragment.getmBookcasePresenter().hasOnGroupChangeListener()) {
mBookcaseFragment.getmBookcasePresenter().addOnGroupChangeListener(() -> {
- groupName = SharedPreUtils.getInstance().getString("curBookGroupName", "所有书籍");
+ groupName = SharedPreUtils.getInstance().getString(getString(R.string.curBookGroupName), "所有书籍");
getSupportActionBar().setSubtitle(groupName);
});
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java
index 3e30e14..319e56b 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java
@@ -3,6 +3,8 @@ package xyz.fycz.myreader.ui.activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.*;
@@ -11,11 +13,19 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import butterknife.BindView;
+import com.google.android.material.textfield.TextInputLayout;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity2;
+import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.model.storage.BackupRestoreUi;
+import xyz.fycz.myreader.model.storage.WebDavHelp;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
@@ -23,6 +33,7 @@ import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.service.BookService;
+import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
@@ -41,6 +52,8 @@ import static xyz.fycz.myreader.common.APPCONST.BOOK_CACHE_PATH;
*/
public class MoreSettingActivity extends BaseActivity2 {
+ @BindView(R.id.more_setting_ll_webdav)
+ LinearLayout mLlWebdav;
@BindView(R.id.more_setting_rl_volume)
RelativeLayout mRlVolume;
@BindView(R.id.more_setting_sc_volume)
@@ -83,6 +96,8 @@ public class MoreSettingActivity extends BaseActivity2 {
RelativeLayout mRlBookstore;
@BindView(R.id.more_setting_sc_bookstore)
SwitchCompat mScBookstore;*/
+
+
private Setting mSetting;
private boolean isVolumeTurnPage;
private int resetScreenTime;
@@ -125,7 +140,7 @@ public class MoreSettingActivity extends BaseActivity2 {
catheCap = mSetting.getCatheGap();
autoRefresh = mSetting.isRefreshWhenStart();
openBookStore = mSetting.isOpenBookStore();
- threadNum = SharedPreUtils.getInstance().getInt("threadNum", 8);
+ threadNum = SharedPreUtils.getInstance().getInt(getString(R.string.threadNum), 8);
}
@Override
@@ -147,6 +162,7 @@ public class MoreSettingActivity extends BaseActivity2 {
mTvThreadNum.setText(getString(R.string.cur_thread_num, threadNum));
}
+
private void initSwitchStatus() {
mScVolume.setChecked(isVolumeTurnPage);
mScMatchChapter.setChecked(isMatchChapter);
@@ -157,6 +173,8 @@ public class MoreSettingActivity extends BaseActivity2 {
@Override
protected void initClick() {
super.initClick();
+ mLlWebdav.setOnClickListener(v -> startActivity(WebDavSettingActivity.class));
+
mRlVolume.setOnClickListener(
(v) -> {
if (isVolumeTurnPage) {
@@ -257,7 +275,7 @@ public class MoreSettingActivity extends BaseActivity2 {
}
}
if (sb.lastIndexOf(",") >= 0) sb.deleteCharAt(sb.lastIndexOf(","));
- spu.putString("searchSource", sb.toString());
+ spu.putString(getString(R.string.searchSource), sb.toString());
}, null, new DialogCreator.OnMultiDialogListener() {
@Override
public void onItemClick(DialogInterface dialog, int which, boolean isChecked) {
@@ -287,7 +305,7 @@ public class MoreSettingActivity extends BaseActivity2 {
.setView(view)
.setPositiveButton("确定", (dialog, which) -> {
threadNum = threadPick.getValue();
- SharedPreUtils.getInstance().putInt("threadNum", threadNum);
+ SharedPreUtils.getInstance().putInt(getString(R.string.threadNum), threadNum);
mTvThreadNum.setText(getString(R.string.cur_thread_num, threadNum));
}).setNegativeButton("取消", null)
.show();
@@ -371,7 +389,7 @@ public class MoreSettingActivity extends BaseActivity2 {
String eCatheFileSize;
if (eCatheFile.exists() && eCatheFile.isDirectory()) {
eCatheFileSize = FileUtils.getFileSize(FileUtils.getDirSize(eCatheFile));
- }else {
+ } else {
eCatheFileSize = "0";
}
CharSequence[] cathes = {"章节缓存:" + eCatheFileSize, "图片缓存:" + catheFileSize};
@@ -397,6 +415,7 @@ public class MoreSettingActivity extends BaseActivity2 {
}
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -533,4 +552,6 @@ public class MoreSettingActivity extends BaseActivity2 {
mBooksName[i] = book.getName();
}
}
+
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
index c2c42dc..efb5cbb 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
@@ -20,6 +20,7 @@ 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.model.storage.Backup;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.presenter.ReadPresenter;
import xyz.fycz.myreader.widget.page.PageView;
@@ -121,6 +122,7 @@ public class ReadActivity extends BaseActivity {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
+ Backup.INSTANCE.autoBack();
super.onBackPressed();
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
index 3580cf0..f0de8e5 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
@@ -119,9 +119,9 @@ public class SearchBookActivity extends BaseActivity2 {
if (srlSearchBookList != null) {
srlSearchBookList.finishRefresh();
}
- if (curThreadCount == 0 && !isStopSearch) {
+ /*if (curThreadCount == 0 && !isStopSearch) {
rpb.setIsAutoLoading(false);
- }
+ }*/
break;
case 3:
fabSearchStop.setVisibility(View.GONE);
@@ -158,7 +158,7 @@ public class SearchBookActivity extends BaseActivity2 {
if (rpb != null) {
rpb.setIsAutoLoading(false);
}
- mHandler.sendEmptyMessage(3);
+ fabSearchStop.setVisibility(View.GONE);
}
@Override
@@ -357,7 +357,7 @@ public class SearchBookActivity extends BaseActivity2 {
} else {
isStopSearch = false;
- mSearchBookAdapter = new SearchBookAdapter(mBooks);
+ mSearchBookAdapter = new SearchBookAdapter(mBooks, searchEngine);
rvSearchBooksList.setAdapter(mSearchBookAdapter);
//进入书籍详情页
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/WebDavSettingActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/WebDavSettingActivity.java
new file mode 100644
index 0000000..fa4ebb8
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/WebDavSettingActivity.java
@@ -0,0 +1,131 @@
+package xyz.fycz.myreader.ui.activity;
+
+import android.os.Bundle;
+import android.text.InputType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.appcompat.widget.Toolbar;
+import butterknife.BindView;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.base.BaseActivity2;
+import xyz.fycz.myreader.base.observer.MySingleObserver;
+import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.model.storage.BackupRestoreUi;
+import xyz.fycz.myreader.model.storage.WebDavHelp;
+import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
+import xyz.fycz.myreader.util.SharedPreUtils;
+import xyz.fycz.myreader.util.StringHelper;
+import xyz.fycz.myreader.util.ToastUtils;
+
+import java.util.ArrayList;
+
+/**
+ * @author fengyue
+ * @date 2020/10/4 20:44
+ */
+public class WebDavSettingActivity extends BaseActivity2 {
+ @BindView(R.id.webdav_setting_webdav_url)
+ LinearLayout llWebdavUrl;
+ @BindView(R.id.tv_webdav_url)
+ TextView tvWebdavUrl;
+ @BindView(R.id.webdav_setting_webdav_account)
+ LinearLayout llWebdavAccount;
+ @BindView(R.id.tv_webdav_account)
+ TextView tvWebdavAccount;
+ @BindView(R.id.webdav_setting_webdav_password)
+ LinearLayout llWebdavPassword;
+ @BindView(R.id.tv_webdav_password)
+ TextView tvWebdavPassword;
+ @BindView(R.id.webdav_setting_webdav_restore)
+ LinearLayout llWebdavRestore;
+
+ private String webdavUrl;
+ private String webdavAccount;
+ private String webdavPassword;
+ @Override
+ protected int getContentId() {
+ return R.layout.activity_webdav_setting;
+ }
+
+ @Override
+ protected void setUpToolbar(Toolbar toolbar) {
+ super.setUpToolbar(toolbar);
+ setStatusBarColor(R.color.colorPrimary, true);
+ getSupportActionBar().setTitle(getString(R.string.webdav_setting));
+ }
+
+ @Override
+ protected void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ webdavUrl = SharedPreUtils.getInstance().getString("webdavUrl", APPCONST.DEFAULT_WEB_DAV_URL);
+ webdavAccount = SharedPreUtils.getInstance().getString("webdavAccount", "");
+ webdavPassword = SharedPreUtils.getInstance().getString("webdavPassword", "");
+ }
+
+ @Override
+ protected void initWidget() {
+ super.initWidget();
+ tvWebdavUrl.setText(webdavUrl);
+ tvWebdavAccount.setText(StringHelper.isEmpty(webdavAccount) ? "请输入WebDav账号" : webdavAccount);
+ tvWebdavPassword.setText(StringHelper.isEmpty(webdavPassword) ? "请输入WebDav授权密码" : "************");
+ }
+
+ @Override
+ protected void initClick() {
+ super.initClick();
+ final String[] webdavTexts = new String[3];
+ llWebdavUrl.setOnClickListener(v -> {
+ MyAlertDialog.createInputDia(this, getString(R.string.webdav_url),
+ "", webdavUrl.equals(APPCONST.DEFAULT_WEB_DAV_URL) ?
+ "" : webdavUrl, true, 100,
+ text -> webdavTexts[0] = text,
+ (dialog, which) -> {
+ webdavUrl = webdavTexts[0];
+ tvWebdavUrl.setText(webdavUrl);
+ SharedPreUtils.getInstance().putString("webdavUrl", webdavUrl);
+ dialog.dismiss();
+ });
+ });
+ llWebdavAccount.setOnClickListener(v -> {
+ MyAlertDialog.createInputDia(this, getString(R.string.webdav_account),
+ "", webdavAccount, true, 100,
+ text -> webdavTexts[1] = text,
+ (dialog, which) -> {
+ webdavAccount = webdavTexts[1];
+ tvWebdavAccount.setText(webdavAccount);
+ SharedPreUtils.getInstance().putString("webdavAccount", webdavAccount);
+ dialog.dismiss();
+ });
+ });
+ llWebdavPassword.setOnClickListener(v -> {
+ MyAlertDialog.createInputDia(this, getString(R.string.webdav_password),
+ "", webdavPassword, InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD,
+ true, 100,
+ text -> webdavTexts[2] = text,
+ (dialog, which) -> {
+ webdavPassword = webdavTexts[2];
+ tvWebdavPassword.setText("************");
+ SharedPreUtils.getInstance().putString("webdavPassword", webdavPassword);
+ dialog.dismiss();
+ });
+ });
+ llWebdavRestore.setOnClickListener(v -> {
+ Single.create((SingleOnSubscribe>) emitter -> {
+ emitter.onSuccess(WebDavHelp.INSTANCE.getWebDavFileNames());
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new MySingleObserver>() {
+ @Override
+ public void onSuccess(ArrayList strings) {
+ if (!WebDavHelp.INSTANCE.showRestoreDialog(WebDavSettingActivity.this, strings, BackupRestoreUi.INSTANCE)) {
+ ToastUtils.showWarring("没有备份");
+ }
+ }
+ });
+ });
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseAdapter.java
index 0e1c52b..f31ab34 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseAdapter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseAdapter.java
@@ -17,6 +17,7 @@ import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
+import xyz.fycz.myreader.widget.CoverImageView;
import xyz.fycz.myreader.widget.custom.DragAdapter;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
@@ -311,7 +312,7 @@ public abstract class BookcaseAdapter extends DragAdapter {
static class ViewHolder {
CheckBox cbBookChecked;
- ImageView ivBookImg;
+ CoverImageView ivBookImg;
TextView tvBookName;
BadgeView tvNoReadNum;
ProgressBar pbLoading;
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java
index 64d1df5..eaeece1 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java
@@ -70,13 +70,7 @@ public class BookcaseDetailedAdapter extends BookcaseAdapter {
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.ivBookImg.load(book.getImgUrl(), book.getName(),book.getAuthor());
viewHolder.tvBookName.setText(book.getName());
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java
index 017ece3..85051d9 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java
@@ -67,13 +67,7 @@ public class BookcaseDragAdapter extends BookcaseAdapter {
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.ivBookImg.load(book.getImgUrl(), book.getName(),book.getAuthor());
viewHolder.tvBookName.setText(book.getName());
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java
index b70c0a1..f31dce3 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java
@@ -7,6 +7,7 @@ import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.greendao.entity.Book;
+import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.ui.adapter.holder.SearchBookHolder;
@@ -19,6 +20,12 @@ import java.util.List;
*/
public class SearchBookAdapter extends BaseListAdapter {
private ConcurrentMultiValueMap mBooks;
+ private SearchEngine searchEngine;
+
+ public SearchBookAdapter(ConcurrentMultiValueMap mBooks, SearchEngine searchEngine) {
+ this.mBooks = mBooks;
+ this.searchEngine = searchEngine;
+ }
public SearchBookAdapter(ConcurrentMultiValueMap mBooks) {
this.mBooks = mBooks;
@@ -26,7 +33,7 @@ public class SearchBookAdapter extends BaseListAdapter {
@Override
protected IViewHolder createViewHolder(int viewType) {
- return new SearchBookHolder(mBooks);
+ return new SearchBookHolder(mBooks, searchEngine);
}
public synchronized void addAll(List newDataS, String keyWord) {
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java
index 7a6a9e4..7fed604 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java
@@ -16,6 +16,7 @@ import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.ui.activity.BookDetailedActivity;
+import xyz.fycz.myreader.widget.CoverImageView;
/**
* @author fengyue
@@ -23,7 +24,7 @@ import xyz.fycz.myreader.ui.activity.BookDetailedActivity;
*/
public class BookStoreBookHolder extends ViewHolderImpl {
- private ImageView tvBookImg;
+ private CoverImageView tvBookImg;
private TextView tvBookName;
private TextView tvBookAuthor;
private TextView tvBookTime;
@@ -63,13 +64,7 @@ public class BookStoreBookHolder extends ViewHolderImpl {
if (hasImg){
tvBookImg.setVisibility(View.VISIBLE);
if (!MyApplication.isDestroy(mActivity)) {
- Glide.with(getContext())
- .load(data.getImgUrl())
- .error(R.mipmap.no_image)
- .placeholder(R.mipmap.no_image)
- //设置圆角
- .apply(RequestOptions.bitmapTransform(new RoundedCorners(8)))
- .into(tvBookImg);
+ tvBookImg.load(data.getImgUrl(), data.getName(), data.getAuthor());
}
}
if (data.getSource() != null) {
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
index 6718720..81c3052 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
@@ -1,5 +1,6 @@
package xyz.fycz.myreader.ui.adapter.holder;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Handler;
import android.util.Log;
@@ -11,6 +12,7 @@ import com.bumptech.glide.request.RequestOptions;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
+import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.enums.BookSource;
@@ -21,6 +23,7 @@ import xyz.fycz.myreader.webapi.CommonApi;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+import xyz.fycz.myreader.widget.CoverImageView;
import java.util.List;
@@ -30,22 +33,25 @@ import java.util.List;
*/
public class SearchBookHolder extends ViewHolderImpl {
private ConcurrentMultiValueMap mBooks;
+ private SearchEngine searchEngine;
private Handler mHandle = new Handler(message -> {
switch (message.what) {
case 1:
Book book = (Book) message.obj;
- initOtherInfo(book);
+ int pos = message.arg1;
+ initOtherInfo(book, pos);
break;
}
return false;
});
- public SearchBookHolder(ConcurrentMultiValueMap mBooks) {
+ public SearchBookHolder(ConcurrentMultiValueMap mBooks, SearchEngine searchEngine) {
this.mBooks = mBooks;
+ this.searchEngine = searchEngine;
}
- ImageView ivBookImg;
+ CoverImageView ivBookImg;
TextView tvBookName;
TextView tvDesc;
TextView tvAuthor;
@@ -69,6 +75,7 @@ public class SearchBookHolder extends ViewHolderImpl {
}
+ @SuppressLint("SetTextI18n")
@Override
public void onBind(SearchBookBean data, int pos) {
List aBooks = mBooks.getValues(data);
@@ -77,56 +84,61 @@ public class SearchBookHolder extends ViewHolderImpl {
if (StringHelper.isEmpty(book.getImgUrl())){
book.setImgUrl("");
}
+
+ if (!StringHelper.isEmpty(book.getDesc())) {
+ tvDesc.setText("简介:" + book.getDesc());
+ }
+ if (!StringHelper.isEmpty(book.getType())) {
+ tvType.setText(book.getType());
+ }
+ if (!StringHelper.isEmpty(book.getNewestChapterTitle())) {
+ tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
+ }
+ if (!StringHelper.isEmpty(book.getAuthor())) {
+ tvAuthor.setText(book.getAuthor());
+ }
tvBookName.setText(book.getName());
- tvAuthor.setText(book.getAuthor());
tvSource.setText(getContext().getString(R.string.source_title_num, BookSource.fromString(book.getSource()).text, bookCount));
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(book.getSource());
if (rc instanceof BookInfoCrawler){
if (tvBookName.getTag() == null || !(Boolean) tvBookName.getTag()) {
tvBookName.setTag(true);
} else {
- initOtherInfo(book);
+ initOtherInfo(book, pos);
return;
}
Log.i(book.getName(), "initOtherInfo");
BookInfoCrawler bic = (BookInfoCrawler) rc;
- CommonApi.getBookInfo(book, bic, new ResultCallback() {
- @Override
- public void onFinish(Object o, int code) {
+ searchEngine.getBookInfo(book, bic, isSuccess -> {
+ if (isSuccess)
mHandle.sendMessage(mHandle.obtainMessage(1, pos, 0, book));
- }
-
- @Override
- public void onError(Exception e) {
+ else
tvBookName.setTag(false);
- }
});
}else {
- initOtherInfo(book);
+ initOtherInfo(book, pos);
}
}
- private void initOtherInfo(Book book){
+ private void initOtherInfo(Book book, int pos){
//简介
- if (book.getDesc() == null) {
- tvDesc.setText("");
- }else {
+ if (StringHelper.isEmpty(tvDesc.getText().toString())) {
tvDesc.setText("简介:" + book.getDesc());
}
- tvType.setText(book.getType());
- tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
- tvAuthor.setText(book.getAuthor());
+ if (StringHelper.isEmpty(tvType.getText().toString())) {
+ tvType.setText(book.getType());
+ }
+ if (StringHelper.isEmpty(tvNewestChapter.getText().toString())) {
+ tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
+ }
+ if (StringHelper.isEmpty(tvAuthor.getText().toString())) {
+ tvAuthor.setText(book.getAuthor());
+ }
//图片
if (!MyApplication.isDestroy((Activity) getContext())) {
- 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(ivBookImg);
+ ivBookImg.load(book.getImgUrl(), book.getName(), book.getAuthor());
}
}
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java b/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java
index ad9e444..8562a5a 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java
@@ -1,15 +1,89 @@
package xyz.fycz.myreader.ui.dialog;
import android.content.Context;
+import android.content.DialogInterface;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
+import com.google.android.material.textfield.TextInputLayout;
import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.util.StringHelper;
/**
* @author fengyue
* @date 2020/9/20 9:48
*/
public class MyAlertDialog {
- public static AlertDialog.Builder build(Context context){
+ public static AlertDialog.Builder build(Context context) {
return new AlertDialog.Builder(context, R.style.alertDialogTheme);
}
+
+ public static AlertDialog createInputDia(Context context, String title, String hint, String initText,
+ Integer inputType, boolean cancelable, int maxLen, onInputChangeListener oic,
+ DialogInterface.OnClickListener posListener) {
+ View view = LayoutInflater.from(context).inflate(R.layout.edit_dialog, null);
+ TextInputLayout textInputLayout = view.findViewById(R.id.text_input_lay);
+
+ textInputLayout.setCounterMaxLength(maxLen);
+ EditText editText = textInputLayout.getEditText();
+ editText.setHint(hint);
+ if (inputType != null) editText.setInputType(inputType);
+ if (!StringHelper.isEmpty(initText)) editText.setText(initText);
+ editText.requestFocus();
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ MyApplication.getHandler().postDelayed(() -> imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED), 220);
+ AlertDialog inputDia = build(context)
+ .setTitle(title)
+ .setView(view)
+ .setCancelable(cancelable)
+ .setPositiveButton("确认", (dialog, which) -> {
+ posListener.onClick(dialog, which);
+ imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
+ })
+ .setNegativeButton("取消", (dialog, which) -> {
+ imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
+ })
+ .show();
+ Button posBtn = inputDia.getButton(AlertDialog.BUTTON_POSITIVE);
+ posBtn.setEnabled(false);
+ editText.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) {
+ String text = editText.getText().toString();
+ if (editText.getText().length() > 0 && editText.getText().length() <= maxLen && !text.equals(initText)) {
+ posBtn.setEnabled(true);
+ } else {
+ posBtn.setEnabled(false);
+ }
+ oic.onChange(text);
+ }
+ });
+ return inputDia;
+ }
+ public static AlertDialog createInputDia(Context context, String title, String hint, String initText,
+ boolean cancelable, int maxLen, onInputChangeListener oic,
+ DialogInterface.OnClickListener posListener) {
+ return createInputDia(context, title, hint, initText, InputType.TYPE_CLASS_TEXT, cancelable, maxLen, oic, posListener);
+ }
+
+ public interface onInputChangeListener{
+ void onChange(String text);
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
index 364afe9..4404df8 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
@@ -18,6 +18,9 @@ import xyz.fycz.myreader.model.backup.BackupAndRestore;
import xyz.fycz.myreader.model.backup.UserService;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.common.APPCONST;
+import xyz.fycz.myreader.model.storage.Backup;
+import xyz.fycz.myreader.model.storage.Restore;
+import xyz.fycz.myreader.ui.activity.MainActivity;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.entity.Setting;
@@ -29,6 +32,7 @@ import xyz.fycz.myreader.ui.activity.MoreSettingActivity;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils;
+import xyz.fycz.myreader.webapi.callback.ResultCallback;
import java.io.File;
import java.text.SimpleDateFormat;
@@ -150,15 +154,15 @@ public class MineFragment extends BaseFragment {
AlertDialog bookDialog = MyAlertDialog.build(getContext())
.setTitle(getContext().getResources().getString(R.string.menu_bookcase_backup))
.setItems(backupMenu, (dialog, which) -> {
- switch (which) {
- case 0:
- mHandler.sendMessage(mHandler.obtainMessage(2));
- break;
- case 1:
- mHandler.sendMessage(mHandler.obtainMessage(3));
- break;
- }
- })
+ switch (which) {
+ case 0:
+ mHandler.sendMessage(mHandler.obtainMessage(2));
+ break;
+ case 1:
+ mHandler.sendMessage(mHandler.obtainMessage(3));
+ break;
+ }
+ })
.setNegativeButton(null, null)
.setPositiveButton(null, null)
.create();
@@ -224,15 +228,15 @@ public class MineFragment extends BaseFragment {
themeMode = which;
switch (which) {
case 0:
- SharedPreUtils.getInstance().putBoolean("isNightFS", true);
+ SharedPreUtils.getInstance().putBoolean(getString(R.string.isNightFS), true);
break;
case 1:
- SharedPreUtils.getInstance().putBoolean("isNightFS", false);
+ SharedPreUtils.getInstance().putBoolean(getString(R.string.isNightFS), false);
mSetting.setDayStyle(true);
SysManager.saveSetting(mSetting);
break;
case 2:
- SharedPreUtils.getInstance().putBoolean("isNightFS", false);
+ SharedPreUtils.getInstance().putBoolean(getString(R.string.isNightFS), false);
mSetting.setDayStyle(false);
SysManager.saveSetting(mSetting);
break;
@@ -255,7 +259,7 @@ public class MineFragment extends BaseFragment {
mRlFeedback.setOnClickListener(v -> {
DialogCreator.createCommonDialog(getContext(), "问题反馈", "请加入QQ群(1085028304)反馈问题!", true,
"加入QQ群", "取消", (dialog, which) -> {
- if (!MyApplication.joinQQGroup(getContext(),"8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")){
+ if (!MyApplication.joinQQGroup(getContext(), "8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")) {
ClipboardManager mClipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
//数据
ClipData mClipData = ClipData.newPlainText("Label", "1085028304");
@@ -285,11 +289,22 @@ public class MineFragment extends BaseFragment {
DialogCreator.createCommonDialog(getContext(), "确认备份吗?", "新备份会替换原有备份!", true,
(dialogInterface, i) -> {
dialogInterface.dismiss();
- if (mBackupAndRestore.backup("localBackup")) {
+ /*if (mBackupAndRestore.backup("localBackup")) {
DialogCreator.createTipDialog(getContext(), "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR);
} else {
DialogCreator.createTipDialog(getContext(), "未给予储存权限,备份失败!");
- }
+ }*/
+ Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.BACKUP_FILE_DIR, new Backup.CallBack() {
+ @Override
+ public void backupSuccess() {
+ DialogCreator.createTipDialog(getContext(), "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR);
+ }
+
+ @Override
+ public void backupError(@io.reactivex.annotations.NonNull String msg) {
+ DialogCreator.createTipDialog(getContext(), "未给予储存权限,备份失败!");
+ }
+ }, false);
}, (dialogInterface, i) -> dialogInterface.dismiss());
}
@@ -300,7 +315,7 @@ public class MineFragment extends BaseFragment {
DialogCreator.createCommonDialog(getContext(), "确认恢复吗?", "恢复书架会覆盖原有书架!", true,
(dialogInterface, i) -> {
dialogInterface.dismiss();
- if (mBackupAndRestore.restore("localBackup")) {
+ /*if (mBackupAndRestore.restore("localBackup")) {
mHandler.sendMessage(mHandler.obtainMessage(7));
// DialogCreator.createTipDialog(mMainActivity,
// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");
@@ -308,7 +323,22 @@ public class MineFragment extends BaseFragment {
ToastUtils.showSuccess("书架恢复成功!");
} else {
DialogCreator.createTipDialog(getContext(), "未找到备份文件或未给予储存权限,恢复失败!");
- }
+ }*/
+ Restore.INSTANCE.restore(APPCONST.BACKUP_FILE_DIR, new Restore.CallBack() {
+ @Override
+ public void restoreSuccess() {
+ mHandler.sendMessage(mHandler.obtainMessage(7));
+// DialogCreator.createTipDialog(mMainActivity,
+// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");
+ mSetting = SysManager.getSetting();
+ ToastUtils.showSuccess("书架恢复成功!");
+ }
+
+ @Override
+ public void restoreError(@io.reactivex.annotations.NonNull String msg) {
+ DialogCreator.createTipDialog(getContext(), "未找到备份文件或未给予储存权限,恢复失败!");
+ }
+ });
}, (dialogInterface, i) -> dialogInterface.dismiss());
}
@@ -333,17 +363,27 @@ public class MineFragment extends BaseFragment {
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd");
String nowTimeStr = sdf.format(nowTime);
SharedPreUtils spb = SharedPreUtils.getInstance();
- String synTime = spb.getString("synTime");
+ String synTime = spb.getString(getString(R.string.synTime));
if (!nowTimeStr.equals(synTime) || !isAutoSyn) {
- MyApplication.getApplication().newThread(() -> {
- if (UserService.webBackup()) {
- spb.putString("synTime", nowTimeStr);
- if (!isAutoSyn) {
- DialogCreator.createTipDialog(getContext(), "成功将书架同步至网络!");
+ UserService.webBackup(new ResultCallback() {
+ @Override
+ public void onFinish(Object o, int code) {
+ if ((boolean) o) {
+ spb.putString(getString(R.string.synTime), nowTimeStr);
+ if (!isAutoSyn) {
+ DialogCreator.createTipDialog(getContext(), "成功将书架同步至网络!");
+ }
+ } else {
+ if (!isAutoSyn) {
+ DialogCreator.createTipDialog(getContext(), "同步失败,请重试!");
+ }
}
- } else {
+ }
+
+ @Override
+ public void onError(Exception e) {
if (!isAutoSyn) {
- DialogCreator.createTipDialog(getContext(), "同步失败,请重试!");
+ DialogCreator.createTipDialog(getContext(), "同步失败,请重试!\n" + e.getLocalizedMessage());
}
}
});
@@ -362,7 +402,7 @@ public class MineFragment extends BaseFragment {
(dialogInterface, i) -> {
dialogInterface.dismiss();
MyApplication.getApplication().newThread(() -> {
- if (UserService.webRestore()) {
+ /*if (UserService.webRestore()) {
mHandler.sendMessage(mHandler.obtainMessage(7));
// DialogCreator.createTipDialog(mMainActivity,
// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");、
@@ -370,7 +410,26 @@ public class MineFragment extends BaseFragment {
ToastUtils.showSuccess("成功将书架从网络同步至本地!");
} else {
DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!");
- }
+ }*/
+ UserService.webRestore(new ResultCallback() {
+ @Override
+ public void onFinish(Object o, int code) {
+ if ((boolean) o) {
+ mHandler.sendMessage(mHandler.obtainMessage(7));
+// DialogCreator.createTipDialog(mMainActivity,
+// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");、
+ mSetting = SysManager.getSetting();
+ ToastUtils.showSuccess("成功将书架从网络同步至本地!");
+ } else {
+ DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!");
+ }
+ }
+
+ @Override
+ public void onError(Exception e) {
+ DialogCreator.createTipDialog(getContext(), "未找到同步文件,同步失败!\n" + e.getLocalizedMessage());
+ }
+ });
});
}, (dialogInterface, i) -> dialogInterface.dismiss());
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
index 687daac..fb3ae2e 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
@@ -336,7 +336,7 @@ public class BookcasePresenter implements BasePresenter {
//初始化书籍
private void initBook() {
mBooks.clear();
- String curBookGroupId = SharedPreUtils.getInstance().getString("curBookGroupId", "");
+ String curBookGroupId = SharedPreUtils.getInstance().getString(mMainActivity.getString(R.string.curBookGroupId), "");
isGroup = !"".equals(curBookGroupId);
if (mBookcaseAdapter != null) {
mBookcaseAdapter.setGroup(isGroup);
@@ -481,12 +481,12 @@ public class BookcasePresenter implements BasePresenter {
mMainActivity.startActivity(fileSystemIntent);
break;
case R.id.action_download_all:
- if (!SharedPreUtils.getInstance().getBoolean("isReadDownloadAllTip")) {
+ if (!SharedPreUtils.getInstance().getBoolean(mMainActivity.getString(R.string.isReadDownloadAllTip), false)) {
DialogCreator.createCommonDialog(mMainActivity, "一键缓存",
mMainActivity.getString(R.string.all_cathe_tip), true,
(dialog, which) -> {
downloadAll(true);
- SharedPreUtils.getInstance().putBoolean("isReadDownloadAllTip", true);
+ SharedPreUtils.getInstance().putBoolean(mMainActivity.getString(R.string.isReadDownloadAllTip), true);
}, null);
} else {
downloadAll(true);
@@ -515,8 +515,8 @@ public class BookcasePresenter implements BasePresenter {
curBookGroupId = mBookGroups.get(menuItem.getOrder() - 1).getId();
curBookGroupName = mBookGroups.get(menuItem.getOrder() - 1).getName();
}
- SharedPreUtils.getInstance().putString("curBookGroupId", curBookGroupId);
- SharedPreUtils.getInstance().putString("curBookGroupName", curBookGroupName);
+ SharedPreUtils.getInstance().putString(mMainActivity.getString(R.string.curBookGroupId), curBookGroupId);
+ SharedPreUtils.getInstance().putString(mMainActivity.getString(R.string.curBookGroupName), curBookGroupName);
ogcl.onChange();
init();
return true;
@@ -587,6 +587,7 @@ public class BookcasePresenter implements BasePresenter {
private void showAddOrRenameGroupDia(boolean isRename, boolean isAddGroup, int groupNum){
View view = LayoutInflater.from(mMainActivity).inflate(R.layout.edit_dialog, null);
TextInputLayout textInputLayout = view.findViewById(R.id.text_input_lay);
+ textInputLayout.setCounterMaxLength(10);
EditText editText = textInputLayout.getEditText();
editText.setHint("请输入分组名");
BookGroup bookGroup = !isRename ? new BookGroup() : mBookGroups.get(groupNum);
@@ -622,8 +623,8 @@ public class BookcasePresenter implements BasePresenter {
}else {
mBookGroupService.updateEntity(bookGroup);
SharedPreUtils spu = SharedPreUtils.getInstance();
- if (spu.getString("curBookGroupName", "").equals(oldName)){
- spu.putString("curBookGroupName", newGroupName.toString());
+ if (spu.getString(mMainActivity.getString(R.string.curBookGroupName), "").equals(oldName)){
+ spu.putString(mMainActivity.getString(R.string.curBookGroupName), newGroupName.toString());
ogcl.onChange();
}
}
@@ -677,9 +678,9 @@ public class BookcasePresenter implements BasePresenter {
sb.deleteCharAt(sb.lastIndexOf("、"));
}
SharedPreUtils spu = SharedPreUtils.getInstance();
- if (mBookGroupService.getGroupById(spu.getString("curBookGroupId", "")) == null){
- spu.putString("curBookGroupId", "");
- spu.putString("curBookGroupName", "");
+ if (mBookGroupService.getGroupById(spu.getString(mMainActivity.getString(R.string.curBookGroupId), "")) == null){
+ spu.putString(mMainActivity.getString(R.string.curBookGroupId), "");
+ spu.putString(mMainActivity.getString(R.string.curBookGroupName), "");
ogcl.onChange();
init();
}
@@ -959,20 +960,30 @@ public class BookcasePresenter implements BasePresenter {
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd");
String nowTimeStr = sdf.format(nowTime);
SharedPreUtils spb = SharedPreUtils.getInstance();
- String synTime = spb.getString("synTime");
+ String synTime = spb.getString(mMainActivity.getString(R.string.synTime));
if (!nowTimeStr.equals(synTime) || !isAutoSyn) {
- MyApplication.getApplication().newThread(() -> {
- if (UserService.webBackup()) {
- spb.putString("synTime", nowTimeStr);
- if (!isAutoSyn) {
- DialogCreator.createTipDialog(mMainActivity, "成功将书架同步至网络!");
+ UserService.webBackup(new ResultCallback() {
+ @Override
+ public void onFinish(Object o, int code) {
+ if ((boolean) o){
+ spb.putString(mMainActivity.getString(R.string.synTime), nowTimeStr);
+ if (!isAutoSyn) {
+ DialogCreator.createTipDialog(mMainActivity, "成功将书架同步至网络!");
+ }
+ }else {
+ if (!isAutoSyn) {
+ DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!");
+ }
+ }
}
- } else {
- if (!isAutoSyn) {
- DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!");
+
+ @Override
+ public void onError(Exception e) {
+ if (!isAutoSyn) {
+ DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!\n" + e.getLocalizedMessage());
+ }
}
- }
- });
+ });
}
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/ReadPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/ReadPresenter.java
index 42ef7d5..10873dc 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/ReadPresenter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/ReadPresenter.java
@@ -216,7 +216,7 @@ public class ReadPresenter implements BasePresenter {
@Override
public void start() {
- if (SharedPreUtils.getInstance().getBoolean("isNightFS", false)) {
+ if (SharedPreUtils.getInstance().getBoolean(mReadActivity.getString(R.string.isNightFS), false)) {
mSetting.setDayStyle(!ColorUtil.isColorLight(mReadActivity.getColor(R.color.textPrimary)));
}
@@ -256,7 +256,7 @@ public class ReadPresenter implements BasePresenter {
//当书籍Collected且书籍id不为空的时候保存上次阅读信息
if (isCollected && !StringHelper.isEmpty(mBook.getId())) {
//保存上次阅读信息
- SharedPreUtils.getInstance().putString("lastRead", mBook.getId());
+ SharedPreUtils.getInstance().putString(mReadActivity.getString(R.string.lastRead), mBook.getId());
}
@@ -325,7 +325,7 @@ public class ReadPresenter implements BasePresenter {
mBook = (Book) mReadActivity.getIntent().getSerializableExtra(APPCONST.BOOK);
//mBook为空,说明是从快捷方式启动
if (mBook == null) {
- String bookId = SharedPreUtils.getInstance().getString("lastRead", "");
+ String bookId = SharedPreUtils.getInstance().getString(mReadActivity.getString(R.string.lastRead), "");
if ("".equals(bookId)) {//没有上次阅读信息
ToastUtils.showWarring("当前没有阅读任何书籍,无法加载上次阅读书籍!");
mReadActivity.finish();
diff --git a/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java
index fdb169f..57c9f98 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java
@@ -1,11 +1,13 @@
package xyz.fycz.myreader.util;
+import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.util.Base64;
import android.util.Log;
import com.google.gson.Gson;
import okhttp3.*;
+import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.TrustAllCerts;
import xyz.fycz.myreader.webapi.callback.HttpCallback;
@@ -53,14 +55,51 @@ public class HttpUtil {
return ssfFactory;
}
+ public static X509TrustManager createTrustAllManager() {
+ X509TrustManager tm = null;
+ try {
+ tm = new X509TrustManager() {
+ @SuppressLint("TrustAllX509TrustManager")
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ //do nothing,接受任意客户端证书
+ }
+
+ @SuppressLint("TrustAllX509TrustManager")
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ //do nothing,接受任意服务端证书
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ };
+ } catch (Exception ignored) {
+ }
+ return tm;
+ }
+
+ private static Interceptor getHeaderInterceptor() {
+ return chain -> {
+ Request request = chain.request()
+ .newBuilder()
+ .addHeader("Keep-Alive", "300")
+ .addHeader("Connection", "Keep-Alive")
+ .addHeader("Cache-Control", "no-cache")
+ .build();
+ return chain.proceed(request);
+ };
+ }
+
public static synchronized OkHttpClient getOkHttpClient() {
if (mClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
- .writeTimeout(15, TimeUnit.SECONDS);
- builder.sslSocketFactory(createSSLSocketFactory());
- builder.hostnameVerifier((hostname, session) -> true);
+ .writeTimeout(15, TimeUnit.SECONDS)
+ .sslSocketFactory(createSSLSocketFactory(), createTrustAllManager())
+ .hostnameVerifier((hostname, session) -> true)
+ .protocols(Collections.singletonList(Protocol.HTTP_1_1))
+ .addInterceptor(getHeaderInterceptor());
mClient = builder
.build();
}
@@ -271,10 +310,10 @@ public class HttpUtil {
.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36");
if (address.contains("qidian.com")) {
SharedPreUtils spu = SharedPreUtils.getInstance();
- String cookie = spu.getString("qdCookie", "");
+ String cookie = spu.getString(MyApplication.getmContext().getString(R.string.qdCookie), "");
if (cookie.equals("")) {
requestBuilder.addHeader("cookie", "_csrfToken=eXRDlZxmRDLvFAmdgzqvwWAASrxxp2WkVlH4ZM7e; newstatisticUUID=1595991935_2026387981");
- }else {
+ } else {
requestBuilder.addHeader("cookie", cookie);
}
}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java b/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java
index b10afb8..7774671 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
import xyz.fycz.myreader.application.MyApplication;
+import java.util.Map;
+
/**
* SharedPreferences工具类
*/
@@ -38,17 +40,27 @@ public class SharedPreUtils {
public void putString(String key,String value){
sharedWritable.putString(key,value);
- sharedWritable.commit();
+ sharedWritable.apply();
}
public void putInt(String key,int value){
sharedWritable.putInt(key, value);
- sharedWritable.commit();
+ sharedWritable.apply();
}
public void putBoolean(String key,boolean value){
sharedWritable.putBoolean(key, value);
- sharedWritable.commit();
+ sharedWritable.apply();
+ }
+
+ public void putFloat(String key,float value){
+ sharedWritable.putFloat(key, value);
+ sharedWritable.apply();
+ }
+
+ public void putLong(String key, long value){
+ sharedWritable.putLong(key, value);
+ sharedWritable.apply();
}
public String getString(String key){
@@ -74,4 +86,26 @@ public class SharedPreUtils {
public boolean getBoolean(String key,boolean def){
return sharedReadable.getBoolean(key, false);
}
+
+ public float getFloat(String key){
+ return getFloat(key, 0);
+ }
+ public float getFloat(String key, float def){
+ return sharedReadable.getFloat(key, def);
+ }
+ public long getLong(String key){
+ return getLong(key, 0);
+ }
+ public long getLong(String key, long def){
+ return sharedReadable.getLong(key, def);
+ }
+
+ public void remove(String key){
+ sharedWritable.remove(key).apply();
+ }
+
+ public Map getAll(){
+ return sharedReadable.getAll();
+ }
+
}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java b/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java
index ca2da97..f1bc149 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/StringHelper.java
@@ -1,5 +1,7 @@
package xyz.fycz.myreader.util;
+import xyz.fycz.myreader.util.utils.StringUtils;
+
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Random;
@@ -118,9 +120,9 @@ public class StringHelper {
public static boolean isEmpty(String str){
if (str != null){
- str = str.replace(" ","");
+ str = StringUtils.deleteWhitespace(str);
}
- return str == null || str.equals("");
+ return str == null || str.equals("") || str.equals("null");
}
/**
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/DocumentUtil.java b/app/src/main/java/xyz/fycz/myreader/util/utils/DocumentUtil.java
new file mode 100644
index 0000000..a9e8ab7
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/DocumentUtil.java
@@ -0,0 +1,387 @@
+package xyz.fycz.myreader.util.utils;
+
+import android.content.Context;
+import android.net.Uri;
+import androidx.annotation.NonNull;
+import androidx.documentfile.provider.DocumentFile;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.regex.Pattern;
+
+/**
+ * Created by PureDark on 2016/9/24.
+ */
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class DocumentUtil {
+
+ private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");
+
+ public static boolean isFileExist(Context context, String fileName, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return isFileExist(context, fileName, rootUri, subDirs);
+ }
+
+ public static boolean isFileExist(Context context, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile root;
+ if ("content".equals(rootUri.getScheme()))
+ root = DocumentFile.fromTreeUri(context, rootUri);
+ else
+ root = DocumentFile.fromFile(new File(rootUri.getPath()));
+ return isFileExist(fileName, root, subDirs);
+ }
+
+ public static boolean isFileExist(String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return false;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ if (file != null && file.exists())
+ return true;
+ return false;
+ }
+
+ public static DocumentFile createDirIfNotExist(Context context, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return createDirIfNotExist(context, rootUri, subDirs);
+ }
+
+ public static DocumentFile createDirIfNotExist(Context context, Uri rootUri, String... subDirs) {
+ DocumentFile root;
+ if ("content".equals(rootUri.getScheme()))
+ root = DocumentFile.fromTreeUri(context, rootUri);
+ else
+ root = DocumentFile.fromFile(new File(rootUri.getPath()));
+ return createDirIfNotExist(root, subDirs);
+ }
+
+ public static DocumentFile createDirIfNotExist(@NonNull DocumentFile root, String... subDirs) {
+ DocumentFile parent = root;
+ try {
+ for (String subDir1 : subDirs) {
+ String subDirName = filenameFilter(Uri.decode(subDir1));
+ DocumentFile subDir = parent.findFile(subDirName);
+ if (subDir == null) {
+ subDir = parent.createDirectory(subDirName);
+ }
+ parent = subDir;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ return parent;
+ }
+
+ public static DocumentFile createFileIfNotExist(Context context, String fileName, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return createFileIfNotExist(context, "", fileName, rootUri, subDirs);
+ }
+
+ public static DocumentFile createFileIfNotExist(Context context, String fileName, Uri rootUri, String... subDirs) {
+ return createFileIfNotExist(context, "", fileName, rootUri, subDirs);
+ }
+
+ public static DocumentFile createFileIfNotExist(Context context, String mimeType, String fileName, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return createFileIfNotExist(context, mimeType, fileName, rootUri, subDirs);
+ }
+
+ public static DocumentFile createFileIfNotExist(Context context, String mimeType, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile parent = createDirIfNotExist(context, rootUri, subDirs);
+ if (parent == null)
+ return null;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null) {
+ file = parent.createFile(mimeType, fileName);
+ }
+ return file;
+ }
+
+ public static boolean deleteFile(Context context, String fileName, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return deleteFile(context, fileName, rootUri, subDirs);
+ }
+
+ public static boolean deleteFile(Context context, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile root;
+ if ("content".equals(rootUri.getScheme()))
+ root = DocumentFile.fromTreeUri(context, rootUri);
+ else
+ root = DocumentFile.fromFile(new File(rootUri.getPath()));
+ return deleteFile(fileName, root, subDirs);
+ }
+
+ public static boolean deleteFile(String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return false;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ return file != null && file.exists() && file.delete();
+ }
+
+ public static boolean writeBytes(Context context, byte[] data, String fileName, String rootPath, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootPath, subDirs);
+ if (parent == null)
+ return false;
+ DocumentFile file = parent.findFile(fileName);
+ return writeBytes(context, data, file.getUri());
+ }
+
+ public static boolean writeBytes(Context context, byte[] data, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootUri, subDirs);
+ if (parent == null)
+ return false;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ return writeBytes(context, data, file.getUri());
+ }
+
+ public static boolean writeBytes(Context context, byte[] data, String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return false;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ return writeBytes(context, data, file.getUri());
+ }
+
+ public static boolean writeBytes(Context context, byte[] data, DocumentFile file) {
+ return writeBytes(context, data, file.getUri());
+ }
+
+ public static boolean writeBytes(Context context, byte[] data, Uri fileUri) {
+ try {
+ OutputStream out = context.getContentResolver().openOutputStream(fileUri, "wt"); //Write file need open with truncate mode, the mode truncate file upon opening (to zero bytes)
+ out.write(data);
+ out.close();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public static boolean writeFromInputStream(Context context, InputStream inStream, DocumentFile file) {
+ return writeFromInputStream(context, inStream, file.getUri());
+ }
+
+ public static boolean writeFromInputStream(Context context, InputStream inStream, Uri fileUri) {
+ try {
+ OutputStream out = context.getContentResolver().openOutputStream(fileUri);
+ int byteread;
+ byte[] buffer = new byte[1024];
+ while ((byteread = inStream.read(buffer)) > 0) {
+ out.write(buffer, 0, byteread);
+ }
+ inStream.close();
+ out.close();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public static byte[] readBytes(Context context, String fileName, String rootPath, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootPath, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return readBytes(context, file.getUri());
+ }
+
+ public static byte[] readBytes(Context context, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootUri, subDirs);
+ if (parent == null)
+ return null;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return readBytes(context, file.getUri());
+ }
+
+ public static byte[] readBytes(Context context, String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return null;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return readBytes(context, file.getUri());
+ }
+
+ public static byte[] readBytes(Context context, DocumentFile file) {
+ if (file == null)
+ return null;
+ return readBytes(context, file.getUri());
+ }
+
+ public static byte[] readBytes(Context context, Uri fileUri) {
+ try {
+ InputStream fis = context.getContentResolver().openInputStream(fileUri);
+ int len = fis.available();
+ byte[] buffer = new byte[len];
+ fis.read(buffer);
+ fis.close();
+ return buffer;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static DocumentFile getDirDocument(Context context, String rootPath, String... subDirs) {
+ Uri rootUri;
+ if (rootPath.startsWith("content"))
+ rootUri = Uri.parse(rootPath);
+ else
+ rootUri = Uri.parse(Uri.decode(rootPath));
+ return getDirDocument(context, rootUri, subDirs);
+ }
+
+ public static DocumentFile getDirDocument(Context context, Uri rootUri, String... subDirs) {
+ DocumentFile root;
+ if ("content".equals(rootUri.getScheme()))
+ root = DocumentFile.fromTreeUri(context, rootUri);
+ else
+ root = DocumentFile.fromFile(new File(rootUri.getPath()));
+ return getDirDocument(root, subDirs);
+ }
+
+ public static DocumentFile getDirDocument(DocumentFile root, String... subDirs) {
+ DocumentFile parent = root;
+ for (int i = 0; i < subDirs.length; i++) {
+ String subDirName = Uri.decode(subDirs[i]);
+ DocumentFile subDir = parent.findFile(subDirName);
+ if (subDir != null)
+ parent = subDir;
+ else
+ return null;
+ }
+ return parent;
+ }
+
+ public static OutputStream getFileOutputSteam(Context context, String fileName, String rootPath, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootPath, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileOutputSteam(context, file.getUri());
+ }
+
+ public static OutputStream getFileOutputSteam(Context context, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootUri, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileOutputSteam(context, file.getUri());
+ }
+
+ public static OutputStream getFileOutputSteam(Context context, String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileOutputSteam(context, file.getUri());
+ }
+
+ public static OutputStream getFileOutputSteam(Context context, DocumentFile file) {
+ return getFileOutputSteam(context, file.getUri());
+ }
+
+ public static OutputStream getFileOutputSteam(Context context, Uri fileUri) {
+ try {
+ OutputStream out = context.getContentResolver().openOutputStream(fileUri);
+ return out;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static InputStream getFileInputSteam(Context context, String fileName, String rootPath, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootPath, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileInputSteam(context, file.getUri());
+ }
+
+ public static InputStream getFileInputSteam(Context context, String fileName, Uri rootUri, String... subDirs) {
+ DocumentFile parent = getDirDocument(context, rootUri, subDirs);
+ if (parent == null)
+ return null;
+ fileName = filenameFilter(Uri.decode(fileName));
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileInputSteam(context, file.getUri());
+ }
+
+ public static InputStream getFileInputSteam(Context context, String fileName, DocumentFile root, String... subDirs) {
+ DocumentFile parent = getDirDocument(root, subDirs);
+ if (parent == null)
+ return null;
+ DocumentFile file = parent.findFile(fileName);
+ if (file == null)
+ return null;
+ return getFileInputSteam(context, file.getUri());
+ }
+
+ public static InputStream getFileInputSteam(Context context, DocumentFile file) {
+ return getFileInputSteam(context, file.getUri());
+ }
+
+ public static InputStream getFileInputSteam(Context context, Uri fileUri) {
+ try {
+ InputStream in = context.getContentResolver().openInputStream(fileUri);
+ return in;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static String filenameFilter(String str) {
+ return str == null ? null : FilePattern.matcher(str).replaceAll("_");
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
index 5f299fd..6f7653d 100644
--- a/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/FileUtils.java
@@ -1,21 +1,28 @@
package xyz.fycz.myreader.util.utils;
import android.app.Application;
+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 info.monitorenter.cpdetector.io.*;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import xyz.fycz.myreader.application.MyApplication;
+import xyz.fycz.myreader.util.StringHelper;
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 {
//采用自己的格式去设置文件,防止文件被系统文件查询到
@@ -234,4 +241,592 @@ public class FileUtils {
return charsetName;
}
+
+
+ /**
+ * 写文本文件 在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;
+ }
+
+
+ /**
+ * 向手机写图片
+ *
+ * @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);
+ }
+
+
+ /**
+ * 获取目录文件个数
+ *
+ * @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;
+ }
+
+
+
+ /**
+ * 删除空目录
+ *
+ * 返回 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 xyz.fycz.myreader.util.FileUtils.PathStatus createPath(String newPath) {
+ File path = new File(newPath);
+ if (path.exists()) {
+ return xyz.fycz.myreader.util.FileUtils.PathStatus.EXITS;
+ }
+ if (path.mkdir()) {
+ return xyz.fycz.myreader.util.FileUtils.PathStatus.SUCCESS;
+ } else {
+ return xyz.fycz.myreader.util.FileUtils.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();
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/GsonExtensions.kt b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonExtensions.kt
new file mode 100644
index 0000000..784586b
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/GsonExtensions.kt
@@ -0,0 +1,43 @@
+package xyz.fycz.myreader.util.utils
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonSyntaxException
+import com.google.gson.reflect.TypeToken
+import org.jetbrains.anko.attempt
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+val GSON: Gson by lazy {
+ GsonBuilder()
+ .disableHtmlEscaping()
+ .setPrettyPrinting()
+ .create()
+}
+
+inline fun genericType() = object : TypeToken() {}.type
+
+
+@Throws(JsonSyntaxException::class)
+inline fun Gson.fromJsonObject(json: String?): T? {//可转成任意类型
+ return attempt {
+ val result: T? = fromJson(json, genericType())
+ result
+ }.value
+}
+
+@Throws(JsonSyntaxException::class)
+inline fun Gson.fromJsonArray(json: String?): List? {
+ return attempt {
+ val result: List? = fromJson(json, ParameterizedTypeImpl(T::class.java))
+ result
+ }.value
+}
+
+class ParameterizedTypeImpl(private val clazz: Class<*>) : ParameterizedType {
+ override fun getRawType(): Type = List::class.java
+
+ override fun getOwnerType(): Type? = null
+
+ override fun getActualTypeArguments(): Array = arrayOf(clazz)
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/ImageLoader.kt b/app/src/main/java/xyz/fycz/myreader/util/utils/ImageLoader.kt
new file mode 100644
index 0000000..caf0bbb
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/utils/ImageLoader.kt
@@ -0,0 +1,50 @@
+package xyz.fycz.myreader.util.utils
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.bumptech.glide.Glide
+import com.bumptech.glide.RequestBuilder
+import java.io.File
+
+object ImageLoader {
+
+ fun load(context: Context, path: String?): RequestBuilder {
+ return when {
+ path.isNullOrEmpty() -> Glide.with(context).load(path)
+ path.startsWith("http", true) -> Glide.with(context).load(path)
+ else -> try {
+ Glide.with(context).load(File(path))
+ } catch (e: Exception) {
+ Glide.with(context).load(path)
+ }
+ }
+ }
+
+ fun load(context: Context, @DrawableRes resId: Int?): RequestBuilder {
+ return Glide.with(context).load(resId)
+ }
+
+ fun load(context: Context, file: File?): RequestBuilder {
+ return Glide.with(context).load(file)
+ }
+
+ fun load(context: Context, uri: Uri?): RequestBuilder {
+ return Glide.with(context).load(uri)
+ }
+
+ fun load(context: Context, drawable: Drawable?): RequestBuilder {
+ return Glide.with(context).load(drawable)
+ }
+
+ fun load(context: Context, bitmap: Bitmap?): RequestBuilder {
+ return Glide.with(context).load(bitmap)
+ }
+
+ fun load(context: Context, bytes: ByteArray?): RequestBuilder {
+ return Glide.with(context).load(bytes)
+ }
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/webdav/README.md b/app/src/main/java/xyz/fycz/myreader/util/webdav/README.md
new file mode 100644
index 0000000..f6ac57a
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/webdav/README.md
@@ -0,0 +1 @@
+## 用于网络备份的WebDav
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/util/webdav/WebDav.kt b/app/src/main/java/xyz/fycz/myreader/util/webdav/WebDav.kt
new file mode 100644
index 0000000..d6fbee4
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/webdav/WebDav.kt
@@ -0,0 +1,250 @@
+package xyz.fycz.myreader.util.webdav
+
+import xyz.fycz.myreader.util.webdav.http.Handler
+import xyz.fycz.myreader.util.webdav.http.HttpAuth
+import okhttp3.*
+import org.jsoup.Jsoup
+import xyz.fycz.myreader.util.HttpUtil
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.io.UnsupportedEncodingException
+import java.net.MalformedURLException
+import java.net.URL
+import java.net.URLEncoder
+import java.util.*
+
+class WebDav @Throws(MalformedURLException::class)
+constructor(urlStr: String) {
+ companion object {
+ // 指定返回哪些属性
+ private const val DIR =
+ """
+
+
+
+
+
+
+
+ %s
+
+ """
+ }
+
+ private val url: URL = URL(null, urlStr, Handler)
+ private val httpUrl: String? by lazy {
+ val raw = url.toString().replace("davs://", "https://").replace("dav://", "http://")
+ try {
+ return@lazy URLEncoder.encode(raw, "UTF-8")
+ .replace("\\+".toRegex(), "%20")
+ .replace("%3A".toRegex(), ":")
+ .replace("%2F".toRegex(), "/")
+ } catch (e: UnsupportedEncodingException) {
+ e.printStackTrace()
+ return@lazy null
+ }
+ }
+
+ var displayName: String? = null
+ var size: Long = 0
+ var exists = false
+ var parent = ""
+ var urlName = ""
+ get() {
+ if (field.isEmpty()) {
+ this.urlName = (
+ if (parent.isEmpty()) url.file
+ else url.toString().replace(parent, "")
+ ).replace("/", "")
+ }
+ return field
+ }
+
+ fun getPath() = url.toString()
+
+ fun getHost() = url.host
+
+ /**
+ * 填充文件信息。实例化WebDAVFile对象时,并没有将远程文件的信息填充到实例中。需要手动填充!
+ *
+ * @return 远程文件是否存在
+ */
+ @Throws(IOException::class)
+ fun indexFileInfo(): Boolean {
+ propFindResponse(ArrayList())?.let { response ->
+ if (!response.isSuccessful) {
+ this.exists = false
+ return false
+ }
+ response.body()?.let {
+ if (it.string().isNotEmpty()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * 列出当前路径下的文件
+ *
+ * @param propsList 指定列出文件的哪些属性
+ * @return 文件列表
+ */
+ @Throws(IOException::class)
+ @JvmOverloads
+ fun listFiles(propsList: ArrayList = ArrayList()): List {
+ propFindResponse(propsList)?.let { response ->
+ if (response.isSuccessful) {
+ response.body()?.let { body ->
+ return parseDir(body.string())
+ }
+ }
+ }
+ return ArrayList()
+ }
+
+ @Throws(IOException::class)
+ private fun propFindResponse(propsList: ArrayList, depth: Int = 1): Response? {
+ val requestProps = StringBuilder()
+ for (p in propsList) {
+ requestProps.append("\n")
+ }
+ val requestPropsStr: String
+ requestPropsStr = if (requestProps.toString().isEmpty()) {
+ DIR.replace("%s", "")
+ } else {
+ String.format(DIR, requestProps.toString() + "\n")
+ }
+ httpUrl?.let { url ->
+ val request = Request.Builder()
+ .url(url)
+ // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性
+ // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。
+ .method("PROPFIND", RequestBody.create(MediaType.parse("text/plain"), requestPropsStr))
+
+ HttpAuth.auth?.let {
+ request.header(
+ "Authorization",
+ Credentials.basic(it.user, it.pass)
+ )
+ }
+ request.header("Depth", if (depth < 0) "infinity" else depth.toString())
+ return HttpUtil.getOkHttpClient().newCall(request.build()).execute()
+ }
+ return null
+ }
+
+ private fun parseDir(s: String): List {
+ val list = ArrayList()
+ val document = Jsoup.parse(s)
+ val elements = document.getElementsByTag("d:response")
+ httpUrl?.let { url ->
+ val baseUrl = if (url.endsWith("/")) url else "$url/"
+ for (element in elements) {
+ val href = element.getElementsByTag("d:href")[0].text()
+ if (!href.endsWith("/")) {
+ val fileName = href.substring(href.lastIndexOf("/") + 1)
+ val webDavFile: WebDav
+ try {
+ webDavFile = WebDav(baseUrl + fileName)
+ webDavFile.displayName = fileName
+ webDavFile.urlName = href
+ list.add(webDavFile)
+ } catch (e: MalformedURLException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+ return list
+ }
+
+ /**
+ * 根据自己的URL,在远程处创建对应的文件夹
+ *
+ * @return 是否创建成功
+ */
+ @Throws(IOException::class)
+ fun makeAsDir(): Boolean {
+ httpUrl?.let { url ->
+ val request = Request.Builder()
+ .url(url)
+ .method("MKCOL", null)
+ return execRequest(request)
+ }
+ return false
+ }
+
+ /**
+ * 下载到本地
+ *
+ * @param savedPath 本地的完整路径,包括最后的文件名
+ * @param replaceExisting 是否替换本地的同名文件
+ * @return 下载是否成功
+ */
+ fun downloadTo(savedPath: String, replaceExisting: Boolean): Boolean {
+ if (File(savedPath).exists()) {
+ if (!replaceExisting) return false
+ }
+ val inputS = getInputStream() ?: return false
+ File(savedPath).writeBytes(inputS.readBytes())
+ return true
+ }
+
+ /**
+ * 上传文件
+ */
+ @Throws(IOException::class)
+ @JvmOverloads
+ fun upload(localPath: String, contentType: String? = null): Boolean {
+ val file = File(localPath)
+ if (!file.exists()) return false
+ val mediaType = if (contentType == null) null else MediaType.parse(contentType)
+ // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
+ val fileBody = RequestBody.create(mediaType, file)
+ httpUrl?.let {
+ val request = Request.Builder()
+ .url(it)
+ .put(fileBody)
+ return execRequest(request)
+ }
+ return false
+ }
+
+ /**
+ * 执行请求,获取响应结果
+ * @param requestBuilder 因为还需要追加验证信息,所以此处传递Request.Builder的对象,而不是Request的对象
+ * @return 请求执行的结果
+ */
+ @Throws(IOException::class)
+ private fun execRequest(requestBuilder: Request.Builder): Boolean {
+ HttpAuth.auth?.let {
+ requestBuilder.header(
+ "Authorization",
+ Credentials.basic(it.user, it.pass)
+ )
+ }
+ val response = HttpUtil.getOkHttpClient().newCall(requestBuilder.build()).execute()
+ return response.isSuccessful
+ }
+
+ private fun getInputStream(): InputStream? {
+ httpUrl?.let { url ->
+ val request = Request.Builder().url(url)
+ HttpAuth.auth?.let {
+ request.header("Authorization", Credentials.basic(it.user, it.pass))
+ }
+ try {
+ return HttpUtil.getOkHttpClient().newCall(request.build()).execute().body()?.byteStream()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ }
+ }
+ return null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/util/webdav/http/Handler.kt b/app/src/main/java/xyz/fycz/myreader/util/webdav/http/Handler.kt
new file mode 100644
index 0000000..fed42e9
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/webdav/http/Handler.kt
@@ -0,0 +1,16 @@
+package xyz.fycz.myreader.util.webdav.http
+
+import java.net.URL
+import java.net.URLConnection
+import java.net.URLStreamHandler
+
+object Handler : URLStreamHandler() {
+
+ override fun getDefaultPort(): Int {
+ return 80
+ }
+
+ public override fun openConnection(u: URL): URLConnection? {
+ return null
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/util/webdav/http/HttpAuth.kt b/app/src/main/java/xyz/fycz/myreader/util/webdav/http/HttpAuth.kt
new file mode 100644
index 0000000..b40118d
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/util/webdav/http/HttpAuth.kt
@@ -0,0 +1,9 @@
+package xyz.fycz.myreader.util.webdav.http
+
+object HttpAuth {
+
+ var auth: Auth? = null
+
+ class Auth internal constructor(val user: String, val pass: String)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java
index 2804f90..b48db7f 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/CommonApi.java
@@ -1,10 +1,14 @@
package xyz.fycz.myreader.webapi;
import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+import io.reactivex.ObservableOnSubscribe;
+import io.reactivex.annotations.NonNull;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.entity.SearchBookBean;
+import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
@@ -16,6 +20,7 @@ import xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler;
import xyz.fycz.myreader.webapi.crawler.read.TianLaiReadCrawler;
import java.io.IOException;
+import java.util.List;
public class CommonApi extends BaseApi {
@@ -41,6 +46,27 @@ public class CommonApi extends BaseApi {
});
}
+
+
+ /**
+ * 获取章节列表
+ *
+ * @param url
+ */
+ public static Observable> getBookChapters(String url, final ReadCrawler rc) {
+ String charset = rc.getCharset();
+
+ return Observable.create(emitter -> {
+ try {
+ emitter.onNext(rc.getChaptersFromHtml(OkHttpUtils.getHtml(url, charset)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ emitter.onError(e);
+ }
+ emitter.onComplete();
+ });
+ }
+
/**
* 获取章节正文
*
@@ -75,6 +101,24 @@ public class CommonApi extends BaseApi {
});
}
+ /**
+ * 获取章节正文
+ *
+ * @param url
+ */
+
+ public static Observable getChapterContent(String url, final ReadCrawler rc) {
+ String charset = rc.getCharset();
+ return Observable.create(emitter -> {
+ try {
+ emitter.onNext(rc.getContentFormHtml(OkHttpUtils.getHtml(url, charset)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ emitter.onError(e);
+ }
+ emitter.onComplete();
+ });
+ }
/**
* 搜索小说
@@ -130,11 +174,11 @@ public class CommonApi extends BaseApi {
}else {
emitter.onNext(rc.getBooksFromSearchHtml(OkHttpUtils.getHtml(makeSearchUrl(rc.getSearchLink(), key), finalCharset)));
}
- emitter.onComplete();
- } catch (IOException e) {
+ } catch (Exception e) {
e.printStackTrace();
emitter.onError(e);
}
+ emitter.onComplete();
});
}
@@ -155,7 +199,12 @@ public class CommonApi extends BaseApi {
url = book.getInfoUrl();
}
return Observable.create(emitter -> {
- emitter.onNext(bic.getBookInfo(OkHttpUtils.getHtml(url, bic.getCharset()), book));
+ try {
+ emitter.onNext(bic.getBookInfo(OkHttpUtils.getHtml(url, bic.getCharset()), book));
+ } catch (Exception e) {
+ e.printStackTrace();
+ emitter.onError(e);
+ }
emitter.onComplete();
});
}
@@ -180,7 +229,6 @@ public class CommonApi extends BaseApi {
@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
index 90063fa..edccd43 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/LanZousApi.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/LanZousApi.java
@@ -2,6 +2,7 @@ package xyz.fycz.myreader.webapi;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
+import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.URLCONST;
@@ -93,7 +94,7 @@ public class LanZousApi {
SharedPreUtils spu = SharedPreUtils.getInstance();
String lanzousKeyStart = "var pposturl = '";
try {
- lanzousKeyStart = spu.getString("lanzousKeyStart");
+ lanzousKeyStart = spu.getString(MyApplication.getmContext().getString(R.string.lanzousKeyStart));
}catch (Exception e){
e.printStackTrace();
}
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
index c1a97e6..1716f60 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/ReadCrawlerUtil.java
@@ -1,6 +1,8 @@
package xyz.fycz.myreader.webapi.crawler;
+import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
@@ -20,7 +22,7 @@ public class ReadCrawlerUtil {
public static ArrayList getReadCrawlers() {
SharedPreUtils spu = SharedPreUtils.getInstance();
- String searchSource = spu.getString("searchSource", null);
+ String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null);
ArrayList readCrawlers = new ArrayList<>();
if (searchSource == null) {
StringBuilder sb = new StringBuilder();
@@ -33,7 +35,7 @@ public class ReadCrawlerUtil {
}
sb.deleteCharAt(sb.lastIndexOf(","));
searchSource = sb.toString();
- spu.putString("searchSource", searchSource);
+ spu.putString(MyApplication.getmContext().getString(R.string.searchSource), searchSource);
} else if (!"".equals(searchSource)){
String[] sources = searchSource.split(",");
for (String source : sources) {
@@ -45,7 +47,7 @@ public class ReadCrawlerUtil {
public static HashMap getDisableSources() {
SharedPreUtils spu = SharedPreUtils.getInstance();
- String searchSource = spu.getString("searchSource", null);
+ String searchSource = spu.getString(MyApplication.getmContext().getString(R.string.searchSource), null);
HashMap mSources = new HashMap<>();
if (searchSource == null) {
for (BookSource bookSource : BookSource.values()) {
@@ -79,7 +81,7 @@ public class ReadCrawlerUtil {
sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
- SharedPreUtils.getInstance().putString("searchSource", sb.toString());
+ SharedPreUtils.getInstance().putString(MyApplication.getmContext().getString(R.string.searchSource), sb.toString());
}
public static ReadCrawler getReadCrawler(String bookSource) {
diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
index 1be184a..53263cd 100644
--- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
+++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
@@ -4,6 +4,7 @@ import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.entity.bookstore.BookType;
@@ -136,7 +137,7 @@ public class QiDianMobileRank extends FindCrawler {
}
url = url.replace("{sex}", !isFemale ? sex[0] : sex[1]);
SharedPreUtils spu = SharedPreUtils.getInstance();
- String cookie = spu.getString("qdCookie", "");
+ String cookie = spu.getString(MyApplication.getmContext().getString(R.string.qdCookie), "");
if (!cookie.equals("")) {
url = url.replace("{cookie}", StringHelper.getSubString(cookie, "_csrfToken=", ";"));
} else {
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/CoverImageView.kt b/app/src/main/java/xyz/fycz/myreader/widget/CoverImageView.kt
new file mode 100644
index 0000000..702e37e
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/CoverImageView.kt
@@ -0,0 +1,158 @@
+package xyz.fycz.myreader.widget
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.text.TextPaint
+import android.util.AttributeSet
+import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.engine.GlideException
+import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.target.Target
+import xyz.fycz.myreader.R
+import xyz.fycz.myreader.util.utils.ImageLoader
+
+
+class CoverImageView : androidx.appcompat.widget.AppCompatImageView {
+ internal var width: Float = 0.toFloat()
+ internal var height: Float = 0.toFloat()
+ private var nameHeight = 0f
+ private var authorHeight = 0f
+ private val namePaint = TextPaint()
+ private val authorPaint = TextPaint()
+ private var name: String? = null
+ private var author: String? = null
+ private var loadFailed = false
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
+ context,
+ attrs,
+ defStyleAttr
+ )
+
+ init {
+ namePaint.typeface = Typeface.DEFAULT_BOLD
+ namePaint.isAntiAlias = true
+ namePaint.textAlign = Paint.Align.CENTER
+ namePaint.textSkewX = -0.2f
+ authorPaint.typeface = Typeface.DEFAULT
+ authorPaint.isAntiAlias = true
+ authorPaint.textAlign = Paint.Align.CENTER
+ authorPaint.textSkewX = -0.1f
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val measuredHeight = measuredWidth * 7 / 5
+ super.onMeasure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
+ )
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ width = getWidth().toFloat()
+ height = getHeight().toFloat()
+ namePaint.textSize = width / 6
+ namePaint.strokeWidth = namePaint.textSize / 10
+ authorPaint.textSize = width / 9
+ authorPaint.strokeWidth = authorPaint.textSize / 10
+ nameHeight = height / 2
+ authorHeight = nameHeight + authorPaint.fontSpacing
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (width >= 10 && height > 10) {
+ @SuppressLint("DrawAllocation")
+ val path = Path()
+ //四个圆角
+ path.moveTo(10f, 0f)
+ path.lineTo(width - 10, 0f)
+ path.quadTo(width, 0f, width, 10f)
+ path.lineTo(width, height - 10)
+ path.quadTo(width, height, width - 10, height)
+ path.lineTo(10f, height)
+ path.quadTo(0f, height, 0f, height - 10)
+ path.lineTo(0f, 10f)
+ path.quadTo(0f, 0f, 10f, 0f)
+
+ canvas.clipPath(path)
+ }
+ super.onDraw(canvas)
+ if (!loadFailed) return
+ name?.let {
+ namePaint.color = Color.WHITE
+ namePaint.style = Paint.Style.STROKE
+ canvas.drawText(it, width / 2, nameHeight, namePaint)
+ namePaint.color = Color.RED
+ namePaint.style = Paint.Style.FILL
+ canvas.drawText(it, width / 2, nameHeight, namePaint)
+ }
+ author?.let {
+ authorPaint.color = Color.WHITE
+ authorPaint.style = Paint.Style.STROKE
+ canvas.drawText(it, width / 2, authorHeight, authorPaint)
+ authorPaint.color = Color.RED
+ authorPaint.style = Paint.Style.FILL
+ canvas.drawText(it, width / 2, authorHeight, authorPaint)
+ }
+ }
+
+ fun setHeight(height: Int) {
+ val width = height * 5 / 7
+ minimumWidth = width
+ }
+
+ private fun setText(name: String?, author: String?) {
+ this.name =
+ when {
+ name == null -> null
+ name.length > 5 -> name.substring(0, 4) + "…"
+ else -> name
+ }
+ this.author =
+ when {
+ author == null -> null
+ author.length > 8 -> author.substring(0, 7) + "…"
+ else -> author
+ }
+ }
+
+ fun load(path: String?, name: String?, author: String?) {
+ setText(name, author)
+ ImageLoader.load(context, path)//Glide自动识别http://和file://
+ .placeholder(R.mipmap.default_cover)
+ .error(R.mipmap.default_cover)
+ .listener(object : RequestListener {
+ override fun onLoadFailed(
+ e: GlideException?,
+ model: Any?,
+ target: Target?,
+ isFirstResource: Boolean
+ ): Boolean {
+ loadFailed = true
+ return false
+ }
+
+ override fun onResourceReady(
+ resource: Drawable?,
+ model: Any?,
+ target: Target?,
+ dataSource: DataSource?,
+ isFirstResource: Boolean
+ ): Boolean {
+ loadFailed = false
+ return false
+ }
+
+ })
+ .centerCrop()
+ .into(this)
+ }
+}
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
index ebbb13e..c48cb16 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java
@@ -2,7 +2,10 @@ package xyz.fycz.myreader.widget.page;
import android.util.Log;
import io.reactivex.*;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
@@ -82,178 +85,197 @@ public class LocalPageLoader extends PageLoader {
* 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);
- }
+ public List loadChapters() throws IOException {
+ mBookFile = new File(mCollBook.getChapterUrl());
+ //获取文件编码
+ mCharset = FileUtils.getFileEncode(mBookFile.getAbsolutePath());
+ List chapters = new ArrayList<>();
+ RandomAccessFile bookStream = null;
+ boolean hasChapter = false;
+ //获取文件流
+ bookStream = new RandomAccessFile(mBookFile, "r");
+ //寻找匹配文章标题的正则表达式,判断是否存在章节名
+ hasChapter = checkChapterType(bookStream);
+ //加载章节
+ byte[] buffer = new byte[BUFFER_SIZE];
+ //获取到的块起始点,在文件中的位置
+ long curOffset = 0;
+ //block的个数
+ int blockPos = 0;
+ //读取的长度
+ int length;
+
+ //获取文件中的数据到buffer,直到没有数据为止
+ while ((length = bookStream.read(buffer, 0, buffer.length)) > 0) {
+ ++blockPos;
+ //如果存在Chapter
+ if (hasChapter) {
+ //将数据转换成String
+ String blockContent = new String(buffer, 0, length, mCharset);
+ //当前Block下使过的String的指针
+ int seekPos = 0;
+ //进行正则匹配
+ Matcher matcher = mChapterPattern.matcher(blockContent);
+ //如果存在相应章节
+ while (matcher.find()) {
+ //获取匹配到的字符在字符串中的起始位置
+ int chapterStart = matcher.start();
+
+ //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容
+ //第一种情况一定是序章 第二种情况可能是上一个章节的内容
+ if (seekPos == 0 && chapterStart != 0) {
+ //获取当前章节的内容
+ String chapterContent = blockContent.substring(seekPos, chapterStart);
+ //设置指针偏移
+ seekPos += chapterContent.length();
+
+ //如果当前对整个文件的偏移位置为0的话,那么就是序章
+ if (curOffset == 0) {
+ //创建序章
+ 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;
+ }
+ }
+ //进行本地虚拟分章
+ 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;
+ //block的偏移点
+ curOffset += length;
- if (hasChapter) {
- //设置上一章的结尾
- TxtChapter lastChapter = chapters.get(chapters.size() - 1);
- lastChapter.end = curOffset;
- }
+ if (hasChapter) {
+ //设置上一章的结尾
+ TxtChapter lastChapter = chapters.get(chapters.size() - 1);
+ lastChapter.end = curOffset;
+ }
- //当添加的block太多的时候,执行GC
- if (blockPos % 15 == 0) {
- System.gc();
- System.runFinalization();
- }
- }
+ //当添加的block太多的时候,执行GC
+ if (blockPos % 15 == 0) {
+ System.gc();
+ System.runFinalization();
+ }
+ }
+
+ IOUtils.close(bookStream);
+ System.gc();
+ System.runFinalization();
+ return chapters;
+ }
+
+ public void loadChapters(final ResultCallback resultCallback) {
+ // 通过RxJava异步处理分章事件
+ Single.create((SingleOnSubscribe>) e -> {
+ e.onSuccess(loadChapters());
+ }).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver>() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ mChapterDisp = d;
+ }
+
+ @Override
+ public void onSuccess(List chapters) {
+ mChapterDisp = null;
+ isChapterListPrepare = true;
List mChapters = new ArrayList<>();
int i = 0;
for (TxtChapter txtChapter : chapters) {
@@ -272,17 +294,14 @@ public class LocalPageLoader extends PageLoader {
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();
}
+ @Override
+ public void onError(Throwable e) {
+ resultCallback.onError((Exception) e);
+ chapterError();
+ Log.d(TAG, "file load error:" + e.toString());
+ }
});
}
@@ -369,59 +388,6 @@ public class LocalPageLoader extends PageLoader {
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
diff --git a/app/src/main/res/layout/activity_more_setting.xml b/app/src/main/res/layout/activity_more_setting.xml
index 72562e8..961a2bc 100644
--- a/app/src/main/res/layout/activity_more_setting.xml
+++ b/app/src/main/res/layout/activity_more_setting.xml
@@ -16,6 +16,28 @@
android:layout_height="match_parent"
android:orientation="vertical">
+
+
+
+
+ android:layout_height="match_parent"
+ android:descendantFocusability="blocksDescendants">
diff --git a/app/src/main/res/layout/activity_webdav_setting.xml b/app/src/main/res/layout/activity_webdav_setting.xml
new file mode 100644
index 0000000..455b969
--- /dev/null
+++ b/app/src/main/res/layout/activity_webdav_setting.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/edit_dialog.xml b/app/src/main/res/layout/edit_dialog.xml
index 2d819ca..a06af11 100644
--- a/app/src/main/res/layout/edit_dialog.xml
+++ b/app/src/main/res/layout/edit_dialog.xml
@@ -6,8 +6,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
- app:counterEnabled="true"
- app:counterMaxLength="10">
+ app:counterEnabled="true">
-
+ app:srcCompat="@mipmap/default_cover" />
-
+
-
+
+
diff --git a/app/src/main/res/layout/listview_book_store_book_item.xml b/app/src/main/res/layout/listview_book_store_book_item.xml
index 61653a3..e64c3c3 100644
--- a/app/src/main/res/layout/listview_book_store_book_item.xml
+++ b/app/src/main/res/layout/listview_book_store_book_item.xml
@@ -2,17 +2,17 @@
-
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="horizontal"
+ android:background="@color/colorBackground"
+ android:padding="5dp">
-
+ app:srcCompat="@mipmap/default_cover" />
@@ -45,7 +45,7 @@
android:layout_height="wrap_content"
android:paddingStart="5dp"
android:layout_marginTop="6dp"
- android:text="author"
+ tools:text="author"
android:maxLines="1"
android:textSize="13sp"
android:textColor="@color/textSecondary"/>
@@ -56,7 +56,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="1dp"
- android:text="newest_chapter"
+ tools:text="newest_chapter"
android:maxLines="1"
android:textSize="13sp"
android:textColor="@color/textSecondary"/>
@@ -65,7 +65,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="2dp"
- android:text="book desc"
+ tools:text="book desc"
android:ellipsize="end"
android:maxLines="1"
android:textSize="13sp"
@@ -77,7 +77,7 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
- android:text="type"
+ tools:text="type"
android:maxLines="1"
android:textSize="13sp"
android:textColor="@color/textSecondary"/>
@@ -88,7 +88,7 @@
android:paddingTop="3dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
- android:text="book source"
+ tools:text="book source"
android:maxLines="1"
android:textSize="13sp"
android:textColor="@color/textSecondary"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 78cbd9f..7ff3058 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -156,6 +156,26 @@
最新章节:%1$s
书源:%1$s 共%2$d个源
+
+ lanzousKeyStart
+ downloadLink
+ qdCookie
+ searchSource
+ synTime
+ curBookGroupId
+ curBookGroupName
+ lastRead
+ threadNum
+ isNightFS
+ isReadTopTip
+ isReadDownloadAllTip
+ WebDav 服务器地址
+ WebDav 账号
+ WebDav 密码
+ 从WebDav恢复
+ WebDav设置
+ WebDav备份与恢复
+
- 常亮
diff --git a/app/version_code.properties b/app/version_code.properties
index 94c95aa..09cb3df 100644
--- a/app/version_code.properties
+++ b/app/version_code.properties
@@ -1,2 +1,2 @@
-#Fri Oct 02 22:23:04 CST 2020
-VERSION_CODE=150
+#Sat Oct 03 16:52:44 CST 2020
+VERSION_CODE=151
diff --git a/build.gradle b/build.gradle
index 9e4877d..4f0e703 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,15 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
+ext {
+ support_library_version = '28.0.0'
+}
buildscript {
+ ext.kotlin_version = '1.3.72'
repositories {
jcenter()
mavenCentral()
google()
+ maven { url 'http://s3.amazonaws.com/fabric-artifacts/public' }
+ maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}