优化书籍详情

pull/5/head
fengyuecanzhu 4 years ago
parent 5f73042209
commit f2a98582f1
  1. BIN
      .idea/caches/build_file_checksums.ser
  2. 3
      app/build.gradle
  3. 10
      app/src/main/AndroidManifest.xml
  4. 6
      app/src/main/assets/updatelog.fy
  5. 33
      app/src/main/java/xyz/fycz/myreader/application/MyApplication.java
  6. 15
      app/src/main/java/xyz/fycz/myreader/backup/BackupAndRestore.java
  7. 1
      app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java
  8. 4
      app/src/main/java/xyz/fycz/myreader/base/adapter/BaseListAdapter.java
  9. 2
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  10. 353
      app/src/main/java/xyz/fycz/myreader/ui/bookinfo/BookDetailedActivity.java
  11. 4
      app/src/main/java/xyz/fycz/myreader/ui/filesys/FileSystemActivity.java
  12. 2
      app/src/main/java/xyz/fycz/myreader/ui/filesys/FileSystemAdapter.java
  13. 8
      app/src/main/java/xyz/fycz/myreader/ui/filesys/LocalBookFragment.java
  14. 9
      app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseAdapter.java
  15. 3
      app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseDetailedAdapter.java
  16. 1
      app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcaseFragment.java
  17. 283
      app/src/main/java/xyz/fycz/myreader/ui/home/bookcase/BookcasePresenter.java
  18. 3
      app/src/main/java/xyz/fycz/myreader/ui/home/bookstore/BookStorePresenter.java
  19. 219
      app/src/main/java/xyz/fycz/myreader/ui/read/ReadPresenter.java
  20. 3
      app/src/main/java/xyz/fycz/myreader/ui/search/SearchBookPrensenter.java
  21. 94
      app/src/main/java/xyz/fycz/myreader/util/HttpUtil.java
  22. 14
      app/src/main/java/xyz/fycz/myreader/util/TextHelper.java
  23. 23
      app/src/main/java/xyz/fycz/myreader/util/notification/NotificationClickReceiver.java
  24. 171
      app/src/main/java/xyz/fycz/myreader/util/notification/NotificationUtil.java
  25. 12
      app/src/main/res/drawable/ic_vector_add_bookcase.xml
  26. 9
      app/src/main/res/drawable/ic_vector_book_read.xml
  27. 6
      app/src/main/res/drawable/selector_pwd.xml
  28. 79
      app/src/main/res/layout/activity_book_detail.xml
  29. 43
      app/src/main/res/layout/layout_book_detail_bottom.xml
  30. 98
      app/src/main/res/layout/layout_book_detail_content.xml
  31. 98
      app/src/main/res/layout/layout_book_detail_header.xml
  32. 19
      app/src/main/res/menu/menu_book_detail.xml
  33. BIN
      app/src/main/res/mipmap-xhdpi/ic_menu_exchange.png
  34. BIN
      app/src/main/res/mipmap-xhdpi/ic_menu_refresh.png
  35. BIN
      app/src/main/res/mipmap-xhdpi/pwd_gone.png
  36. BIN
      app/src/main/res/mipmap-xhdpi/pwd_visiable.png
  37. 4
      app/src/main/res/values/strings.xml
  38. 37
      app/src/main/res/values/styles.xml
  39. 4
      app/version_code.properties

@ -115,7 +115,8 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0'
//Scroller
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
}
greendao {

@ -54,6 +54,7 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppDayTheme"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".ui.home.Splash">
@ -74,11 +75,13 @@
android:resource="@xml/file_paths"/>
</provider>
<activity android:name=".ui.home.MainActivity"/>
<activity android:name=".ui.home.MainActivity"
android:launchMode="singleTask"/>
<activity
android:name=".ui.search.SearchBookActivity"
android:windowSoftInputMode="stateVisible"/>
<activity android:name=".ui.bookinfo.BookInfoActivity"/>
<activity android:name=".ui.bookinfo.BookDetailedActivity"/>
<activity
android:name=".ui.read.ReadActivity"
@ -101,6 +104,11 @@
<activity android:name=".ui.filesys.FileSystemActivity"/>
<receiver android:name=".util.notification.NotificationClickReceiver"/>
<receiver android:name=".ui.home.bookcase.BookcasePresenter$cancelDownloadReceiver"/>
<receiver android:name=".ui.read.ReadPresenter$cancelDownloadReceiver"/>
</application>
</manifest>

@ -1,3 +1,9 @@
2020.08.15
风月读书v1.20.081523
1、修复安卓10(Q)无法访问外部储存空间及其导致的一系列问题(如:无法下载字体、无法使用备份\恢复、无法添加本地书籍、无法同步书架等)
2、修复一键缓存的bug
3、新增缓存时在通知栏提示缓存进度,并可在通知栏停止缓存
2020.08.12
风月读书v1.20.081221
1、新增本地书籍导入页面(不再使用系统文件管理器导入),支持一次性导入多本书籍

@ -13,7 +13,9 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
@ -61,6 +63,9 @@ public class MyApplication extends Application {
application = this;
HttpUtil.trustAllHosts();//信任所有证书
// handleSSLHandshake();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel();
}
mFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());//初始化线程池
BaseActivity.setCloseAntiHijacking(true);
@ -120,10 +125,12 @@ public class MyApplication extends Application {
@TargetApi(26)
private void createNotificationChannel() {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(new NotificationChannel("gxdw_push_and_im", "gxdw", NotificationManager.IMPORTANCE_DEFAULT));
NotificationChannel channel = new NotificationChannel(APPCONST.channelIdDownload, "下载通知", NotificationManager.IMPORTANCE_LOW);
channel.enableLights(true);//是否在桌面icon右上角展示小红点
channel.setLightColor(Color.RED);//小红点颜色
channel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(channel);
}
@ -246,12 +253,12 @@ public class MyApplication extends Application {
Document doc = null;
try {
String url = "https://shimo.im/docs/cqkgjPRRydYYhQKt/read";
if (isApkInDebug(getmContext())){
if (isApkInDebug(getmContext())) {
url = "https://shimo.im/docs/zfzpda7MUGskOC9v/read";
}
doc = Jsoup.connect(url).get();
String content = doc.getElementsByClass("ql-editor").text();
if (StringHelper.isEmpty(content)){
if (StringHelper.isEmpty(content)) {
TextHelper.showText("检查更新失败!");
return;
}
@ -267,9 +274,9 @@ public class MyApplication extends Application {
updateContent = contents[3].substring(contents[3].indexOf(":") + 1);
SharedPreUtils spu = SharedPreUtils.getInstance();
spu.putString("lanzousKeyStart", contents[4].substring(contents[4].indexOf(":") + 1));
if (!StringHelper.isEmpty(downloadLink)){
if (!StringHelper.isEmpty(downloadLink)) {
spu.putString("downloadLink", downloadLink);
}else {
} else {
spu.putString("downloadLink", URLCONST.APP_DIR_UR);
}
String[] updateContents = updateContent.split("/");
@ -307,14 +314,14 @@ public class MyApplication extends Application {
final boolean isForceUpdate, final BookcaseFragment mBookcaseFragment) {
//String version = (versionCode / 100 % 10) + "." + (versionCode / 10 % 10) + "." + (versionCode % 10);
String cancelTitle;
if(isForceUpdate){
if (isForceUpdate) {
cancelTitle = "退出";
}else {
} else {
cancelTitle = "忽略此版本";
}
if (mBookcaseFragment == null){
DialogCreator.createCommonDialog(activity, "发现新版本:", message, true, "取消", "立即更新", null,
(dialog, which) -> goDownload(activity, url));
if (mBookcaseFragment == null) {
DialogCreator.createCommonDialog(activity, "发现新版本:", message, true, "取消", "立即更新", null,
(dialog, which) -> goDownload(activity, url));
return;
}
@ -345,7 +352,7 @@ public class MyApplication extends Application {
});
}
private void goDownload(Activity activity, String url){
private void goDownload(Activity activity, String url) {
String downloadLink = url;
if (url == null || "".equals(url)) {
downloadLink = URLCONST.APP_DIR_UR;

@ -48,20 +48,7 @@ public class BackupAndRestore {
e.printStackTrace();
return false;
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
IOUtils.close(bw, oos);
}
}

@ -126,5 +126,4 @@ public class BaseActivity extends AppCompatActivity {
return mInputMethodManager;
}
}

@ -1,9 +1,11 @@
package xyz.fycz.myreader.base;
package xyz.fycz.myreader.base.adapter;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import xyz.fycz.myreader.base.BaseViewHolder;
import xyz.fycz.myreader.base.IViewHolder;
import java.util.ArrayList;
import java.util.Collections;

@ -71,4 +71,6 @@ public class APPCONST {
public static final int SETTING_VERSION = 1;
public static final String FORMAT_FILE_DATE = "yyyy-MM-dd";
public final static String channelIdDownload = "channel_download";
}

@ -0,0 +1,353 @@
package xyz.fycz.myreader.ui.bookinfo;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseActivity2;
import xyz.fycz.myreader.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.crawler.BookInfoCrawler;
import xyz.fycz.myreader.crawler.ReadCrawler;
import xyz.fycz.myreader.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.creator.ChangeSourceDialog;
import xyz.fycz.myreader.creator.DialogCreator;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.ui.read.ReadActivity;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.TextHelper;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.webapi.CommonApi;
import java.util.ArrayList;
/**
* @author fengyue
* @date 2020/8/17 11:39
*/
public class BookDetailedActivity extends BaseActivity2 {
@BindView(R.id.book_detail_iv_cover)
ImageView mIvCover;
@BindView(R.id.book_detail_tv_author)
TextView mTvAuthor;
@BindView(R.id.book_detail_tv_type)
TextView mTvType;
@BindView(R.id.book_detail_newest_chapter)
TextView mTvNewestChapter;
@BindView(R.id.book_detail_source)
TextView mTvSource;
@BindView(R.id.book_detail_tv_add)
TextView bookDetailTvAdd;
@BindView(R.id.book_detail_tv_open)
TextView bookDetailTvOpen;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.book_detail_tv_desc)
TextView mTvDesc;
@BindView(R.id.tv_disclaimer)
TextView mTvDisclaimer;
@BindView(R.id.fl_add_bookcase)
FrameLayout flAddBookcase;
@BindView(R.id.fl_open_book)
FrameLayout flOpenBook;
@BindView(R.id.book_detail_rv_catalog)
RecyclerView bookDetailRvCatalog;
@BindView(R.id.pb_loading)
ProgressBar pbLoading;
private Book mBook;
private ArrayList<Book> aBooks;
private BookService mBookService;
private ReadCrawler mReadCrawler;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
initBookInfo();
break;
case 2:
createChangeSourceDia();
break;
case 3:
pbLoading.setVisibility(View.GONE);
DialogCreator.createTipDialog(BookDetailedActivity.this, "未搜索到该书籍,书源加载失败!");
break;
case 4:
initOtherInfo();
break;
}
}
};
@Override
protected int getContentId() {
return R.layout.activity_book_detail;
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
mBookService = BookService.getInstance();
aBooks = (ArrayList<Book>) getIntent().getSerializableExtra(APPCONST.SEARCH_BOOK_BEAN);
if (aBooks != null) {
mBook = aBooks.get(0);
} else {
mBook = (Book) getIntent().getSerializableExtra(APPCONST.BOOK);
}
mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
getSupportActionBar().setTitle(mBook.getName());
}
@Override
protected void initWidget() {
super.initWidget();
initBookInfo();
mTvDisclaimer.setOnClickListener(v -> DialogCreator.createAssetTipDialog(this, "免责声明", "disclaimer.fy"));
if (isBookCollected()) {
bookDetailTvAdd.setText("移除书籍");
bookDetailTvOpen.setText("继续阅读");
}
}
@Override
protected void initClick() {
super.initClick();
flAddBookcase.setOnClickListener(view -> {
if (!isBookCollected()) {
mBookService.addBook(mBook);
TextHelper.showText("成功加入书架");
bookDetailTvAdd.setText("移除书籍");
} else {
mBookService.deleteBookById(mBook.getId());
TextHelper.showText("成功移除书籍");
bookDetailTvAdd.setText("加入书架");
bookDetailTvOpen.setText("开始阅读");
}
});
flOpenBook.setOnClickListener(view -> {
final boolean isCollected;
if (isBookCollected()) {
isCollected = true;
} else {
mBookService.addBook(mBook);
isCollected = false;
CommonApi.getBookChapters(mBook.getChapterUrl(), mReadCrawler, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
mBookService.updateEntity(mBook);
}
@Override
public void onError(Exception e) {
}
});
}
Intent intent = new Intent(this, ReadActivity.class);
intent.putExtra(APPCONST.BOOK, mBook);
intent.putExtra("isCollected", isCollected);
startActivityForResult(intent, APPCONST.REQUEST_READ);
});
}
@Override
protected void processLogic() {
super.processLogic();
}
private boolean isBookCollected() {
Book book = mBookService.findBookByAuthorAndName(mBook.getName(), mBook.getAuthor());
if (book == null) {
return false;
} else {
mBook = book;
return true;
}
}
private void initBookInfo() {
mTvAuthor.setText(mBook.getAuthor());
if (StringHelper.isEmpty(mBook.getImgUrl())) {
mBook.setImgUrl("");
}
assert mBook.getNewestChapterTitle() != null;
mTvNewestChapter.setText("最新章节:" + mBook.getNewestChapterTitle().replace("最近更新 ", ""));
mTvDesc.setText("");
mTvType.setText("");
if (!"null".equals(mBook.getSource())) {
mTvSource.setText("书源:" + BookSource.fromString(mBook.getSource()).text);
}
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
if (rc instanceof BookInfoCrawler && StringHelper.isEmpty(mBook.getImgUrl())) {
BookInfoCrawler bic = (BookInfoCrawler) rc;
CommonApi.getBookInfo(mBook, bic, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
mHandler.sendMessage(mHandler.obtainMessage(4));
}
@Override
public void onError(Exception e) {
}
});
} else {
initOtherInfo();
}
}
private void initOtherInfo() {
mTvDesc.setText(mBook.getDesc());
mTvType.setText(mBook.getType());
Glide.with(this)
.load(mBook.getImgUrl())
.error(R.mipmap.no_image)
.placeholder(R.mipmap.no_image)
//设置圆角
.apply(RequestOptions.bitmapTransform(new RoundedCorners(8)))
.into(mIvCover);
}
private void createChangeSourceDia() {
if (aBooks == null){
mHandler.sendMessage(mHandler.obtainMessage(3));
return;
}
pbLoading.setVisibility(View.GONE);
CharSequence[] sources = new CharSequence[aBooks.size()];
int checkedItem = 0;
for (int i = 0; i < sources.length; i++) {
sources[i] = BookSource.fromString(aBooks.get(i).getSource()).text
+ "\n" + aBooks.get(i).getNewestChapterTitle();
if (sources[i].equals(BookSource.fromString(mBook.getSource()).text
+ "\n" + aBooks.get(i).getNewestChapterTitle())) {
checkedItem = i;
}
}
final int finalCheckedItem = checkedItem;
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("切换书源")
.setCancelable(true)
.setSingleChoiceItems(sources, checkedItem, (dialog1, which) -> {
boolean isBookCollected = isBookCollected();
if (finalCheckedItem == which) {
dialog1.dismiss();
return;
}
Book book = aBooks.get(which);
Book bookTem = new Book(mBook);
bookTem.setChapterUrl(book.getChapterUrl());
bookTem.setImgUrl(book.getImgUrl());
bookTem.setType(book.getType());
bookTem.setDesc(book.getDesc());
bookTem.setSource(book.getSource());
if (isBookCollected) {
mBookService.updateBook(mBook, bookTem);
}
mBook = bookTem;
mHandler.sendMessage(mHandler.obtainMessage(1));
if (isBookCollected) {
DialogCreator.createTipDialog(this,
"换源成功,由于不同书源的章节数量不一定相同,故换源后历史章节可能出错!");
}
dialog1.dismiss();
}).create();
dialog.show();
}
/********************************Event***************************************/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_book_detail, menu);
return true;
}
/**
* 导航栏菜单点击事件
*
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_change_source: //换源
if (!NetworkUtils.isNetWorkAvailable()){
TextHelper.showText("无网络连接!");
return true;
}
pbLoading.setVisibility(View.VISIBLE);
if (aBooks == null) {
ChangeSourceDialog csd = new ChangeSourceDialog(this, mBook);
csd.init(new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
aBooks = (ArrayList<Book>) o;
mHandler.sendMessage(mHandler.obtainMessage(2));
}
@Override
public void onError(Exception e) {
mHandler.sendMessage(mHandler.obtainMessage(3));
}
});
} else {
createChangeSourceDia();
}
break;
case R.id.action_reload: //重新加载
initWidget();
processLogic();
break;
case R.id.action_open_link: //打开链接
Uri uri = Uri.parse(mBook.getChapterUrl());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == APPCONST.REQUEST_READ) {
if (data == null) {
return;
}
boolean isCollected = data.getBooleanExtra(APPCONST.RESULT_IS_COLLECTED, false);
if (isCollected) {
bookDetailTvAdd.setText("移除书籍");
bookDetailTvOpen.setText("继续阅读");
}
}
}
}

@ -148,6 +148,10 @@ public class FileSystemActivity extends BaseTabActivity {
true, (dialog, which) -> {
//删除选中的文件
mCurFragment.deleteCheckedFiles();
//改变菜单状态
changeMenuStatus();
//改变是否可以全选
changeCheckedAllStatus();
//提示删除文件成功
TextHelper.showText("删除文件成功");
}, null);

@ -1,7 +1,7 @@
package xyz.fycz.myreader.ui.filesys;
import xyz.fycz.myreader.base.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.IViewHolder;
import xyz.fycz.myreader.greendao.service.BookService;

@ -70,8 +70,12 @@ public class LocalBookFragment extends BaseFileFragment {
protected void processLogic() {
super.processLogic();
//更新媒体库
MediaScannerConnection.scanFile(getContext(), new String[]{Environment
.getExternalStorageDirectory().getAbsolutePath()}, new String[]{"text/plain"}, null);
try {
MediaScannerConnection.scanFile(getContext(), new String[]{Environment
.getExternalStorageDirectory().getAbsolutePath()}, new String[]{"text/plain"}, null);
}catch (Exception e){
e.printStackTrace();
}
MediaStoreHelper.getAllBookFile(getActivity(),
(files) -> {
if (files.isEmpty()) {

@ -186,12 +186,7 @@ public abstract class BookcaseAdapter extends DragAdapter {
public void onClick(DialogInterface dialog, int which) {
selectedIndex = which;
}
}).setNegativeButton("取消", (new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})).setPositiveButton("确定",
}).setNegativeButton("取消", ((dialog, which) -> dialog.dismiss())).setPositiveButton("确定",
(dialog, which) -> {
switch (selectedIndex) {
case 0:
@ -213,7 +208,7 @@ public abstract class BookcaseAdapter extends DragAdapter {
}
Thread downloadThread = new Thread(() -> {
ArrayList<Chapter> chapters = (ArrayList<Chapter>) mChapterService.findBookAllChapterByBookId(book.getId());
mBookcasePresenter.addDownload(book, chapters, begin[0], end[0]);
mBookcasePresenter.addDownload(book, chapters, begin[0], end[0], false);
});
mBookcasePresenter.getEs().submit(downloadThread);
}).show();

@ -24,6 +24,7 @@ import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.creator.DialogCreator;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.ui.bookinfo.BookDetailedActivity;
import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity;
import xyz.fycz.myreader.ui.read.ReadActivity;
import xyz.fycz.myreader.util.StringHelper;
@ -127,7 +128,7 @@ public class BookcaseDetailedAdapter extends BookcaseAdapter {
mContext.startActivity(intent);
});
viewHolder.ivBookImg.setOnClickListener(v -> {
Intent intent = new Intent(mContext, BookInfoActivity.class);
Intent intent = new Intent(mContext, BookDetailedActivity.class);
intent.putExtra(APPCONST.BOOK, book);
mContext.startActivity(intent);
});

@ -64,6 +64,7 @@ public class BookcaseFragment extends Fragment {
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
mBookcasePresenter.destroy();
}
@Override

@ -2,29 +2,25 @@ package xyz.fycz.myreader.ui.home.bookcase;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.*;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.PopupMenu;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -58,11 +54,11 @@ import xyz.fycz.myreader.ui.home.MainActivity;
import xyz.fycz.myreader.ui.search.SearchBookActivity;
import xyz.fycz.myreader.ui.user.LoginActivity;
import xyz.fycz.myreader.util.*;
import xyz.fycz.myreader.util.notification.NotificationClickReceiver;
import xyz.fycz.myreader.util.notification.NotificationUtil;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.webapi.CommonApi;
import static xyz.fycz.myreader.application.MyApplication.checkVersionByServer;
public class BookcasePresenter implements BasePresenter {
@ -85,16 +81,26 @@ public class BookcasePresenter implements BasePresenter {
return es;
}
private String downloadingBook;
private String downloadingChapter;
private boolean isDownloadFinish;
private boolean isStopDownload;
private int downloadProcess;
private PopupMenu pm;
private NotificationUtil notificationUtil;//通知工具类
private String downloadingBook;//正在下载的书名
private String downloadingChapter;//正在下载的章节名
private boolean isDownloadFinish = true;//单本书是否下载完成
private static boolean isStopDownload = true;//是否停止下载
private int curCacheChapterNum;//当前下载的章节数
private int needCacheChapterNum;//需要下载的章节数
private int tempCacheChapterNum;//上次下载的章节数
private int tempCount;//下载超时时间
private int downloadInterval = 150;//下载间隔
private Runnable sendDownloadNotification;//发送通知的线程
private PopupMenu pm;//菜单
public static final String CANCEL_ACTION = "cancelAction";
private final String[] backupMenu = {
MyApplication.getmContext().getResources().getString(R.string.menu_backup_backup),
MyApplication.getmContext().getResources().getString(R.string.menu_backup_restore),
};
private final String[] webSynMenu = {
MyApplication.getmContext().getString(R.string.menu_backup_webBackup),
MyApplication.getmContext().getString(R.string.menu_backup_webRestore),
@ -124,6 +130,9 @@ public class BookcasePresenter implements BasePresenter {
break;
case 4:
showErrorLoadingBooks();
if (MyApplication.isApkInDebug(mMainActivity)) {
downloadAll(false);
}
break;
case 5:
backup();
@ -135,7 +144,7 @@ public class BookcasePresenter implements BasePresenter {
init();
break;
case 8:
sendNotification(downloadingBook);
sendNotification();
break;
case 9:
mBookcaseFragment.getRlDownloadTip().setVisibility(View.GONE);
@ -150,10 +159,8 @@ public class BookcasePresenter implements BasePresenter {
MyApplication.runOnUiThread(() -> createMenu());
break;
case 12:
mBookcaseFragment.getTvStopDownload().setVisibility(View.GONE);
break;
case 13:
mBookcaseFragment.getTvStopDownload().setVisibility(View.VISIBLE);
TextHelper.showText("正在后台缓存书籍,具体进度可查看通知栏!");
notificationUtil.requestNotificationPermissionDialog(mMainActivity);
break;
}
}
@ -178,6 +185,10 @@ public class BookcasePresenter implements BasePresenter {
if (mSetting.isAutoSyn() && UserService.isLogin()) {
synBookcaseToWeb(true);
}
sendDownloadNotification = this::sendNotification;
notificationUtil = NotificationUtil.getInstance();
getData();
//是否启用下拉刷新(默认启用)
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@ -211,16 +222,6 @@ public class BookcasePresenter implements BasePresenter {
});
}
//停止按钮监听器
mBookcaseFragment.getTvStopDownload().setOnClickListener(v -> {
if (downloadProcess == 99) {
TextHelper.showText("开始缓存下一本书籍!");
isDownloadFinish = true;
} else {
isStopDownload = true;
}
});
}
/**
@ -511,9 +512,16 @@ public class BookcasePresenter implements BasePresenter {
.show();
break;
case R.id.action_download_all:
DialogCreator.createCommonDialog(mMainActivity, "一键缓存(实验)",
mMainActivity.getString(R.string.all_cathe_tip), true,
(dialog, which) -> downloadAll(), null);
if (!SharedPreUtils.getInstance().getBoolean("isReadDownloadAllTip")) {
DialogCreator.createCommonDialog(mMainActivity, "一键缓存",
mMainActivity.getString(R.string.all_cathe_tip), true,
(dialog, which) -> {
downloadAll(true);
SharedPreUtils.getInstance().putBoolean("isReadDownloadAllTip", true);
}, null);
}else {
downloadAll(true);
}
return true;
case R.id.action_backup:
@ -628,39 +636,64 @@ public class BookcasePresenter implements BasePresenter {
/**
* 缓存所有书籍
*/
private void downloadAll() {
private void downloadAll(boolean isDownloadAllChapters) {
if (!NetworkUtils.isNetWorkAvailable()) {
TextHelper.showText("无网络连接!");
return;
}
mHandler.sendMessage(mHandler.obtainMessage(13));
isStopDownload = false;
if (isDownloadAllChapters) {
mHandler.sendEmptyMessage(12);
}
MyApplication.getApplication().newThread(() -> {
downloadFor:
for (final Book book : mBooks) {
if (BookSource.pinshu.toString().equals(book.getSource()) || "本地书籍".equals(book.getType())) {
continue;
ArrayList<Book> needDownloadBooks = new ArrayList<>();
for (Book book : mBooks) {
if (!BookSource.pinshu.toString().equals(book.getSource()) && !"本地书籍".equals(book.getType())) {
needDownloadBooks.add(book);
}
}
downloadFor:
for (final Book book : needDownloadBooks) {
isDownloadFinish = false;
Thread downloadThread = new Thread(() -> {
ArrayList<Chapter> chapters = (ArrayList<Chapter>) mChapterService.findBookAllChapterByBookId(book.getId());
int end;
if (isDownloadAllChapters) {
end = chapters.size();
} else {
end = book.getHisttoryChapterNum() + 5;
}
addDownload(book, chapters,
book.getHisttoryChapterNum(), chapters.size());
book.getHisttoryChapterNum(), end, true);
});
es.submit(downloadThread);
do {
try {
Thread.sleep(800);
Thread.sleep(downloadInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isStopDownload) {
DialogCreator.createTipDialog(mMainActivity, "请等待当前书籍缓存完成后缓存任务将全部停止!");
break downloadFor;
}
} while (!isDownloadFinish);
}
mHandler.sendMessage(mHandler.obtainMessage(12));
if (isDownloadAllChapters && !isStopDownload) {
//通知
Intent mainIntent = new Intent(mMainActivity, MainActivity.class);
PendingIntent mainPendingIntent = PendingIntent.getActivity(mMainActivity, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(false)
//点击通知后自动清除
.setAutoCancel(true)
.setContentTitle("缓存完成")
.setContentText("书籍一键缓存完成!")
.setContentIntent(mainPendingIntent)
.build();
notificationUtil.notify(1002, notification);
}
});
}
@ -672,7 +705,7 @@ public class BookcasePresenter implements BasePresenter {
* @param begin
* @param end
*/
public void addDownload(final Book book, final ArrayList<Chapter> mChapters, int begin, int end) {
public void addDownload(final Book book, final ArrayList<Chapter> mChapters, int begin, int end, boolean isDownloadAll) {
if ("本地书籍".equals(book.getType())) {
TextHelper.showText("《" + book.getName() + "》是本地书籍,不能缓存");
return;
@ -681,54 +714,71 @@ public class BookcasePresenter implements BasePresenter {
TextHelper.showText("《" + book.getName() + "》章节目录为空,缓存失败,请刷新后重试");
return;
}
mHandler.sendMessage(mHandler.obtainMessage(10));
//取消之前下载
if (!isDownloadAll) {
if (!isStopDownload) {
isStopDownload = true;
try {
Thread.sleep(2 * downloadInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//mHandler.sendMessage(mHandler.obtainMessage(10));
downloadingBook = book.getName();
final int finalBegin = Math.max(0, begin);
final int finalEnd = Math.min(end, mChapters.size());
final int needCacheChapterNum = finalEnd - finalBegin;
final int[] curCacheChapterNum = {0};
//final boolean[] isDownloadFinish = new boolean[1];
needCacheChapterNum = finalEnd - finalBegin;
curCacheChapterNum = 0;
tempCacheChapterNum = 0;
isStopDownload = false;
ArrayList<Chapter> needDownloadChapters = new ArrayList<>();
for (int i = finalBegin; i < finalEnd; i++) {
//isDownloadFinish[0] = false;
final Chapter chapter = mChapters.get(i);
if (StringHelper.isEmpty(chapter.getContent())) {
getChapterContent(book, chapter, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
curCacheChapterNum[0]++;
downloadingChapter = chapter.getTitle();
downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum;
mChapterService.saveOrUpdateChapter(chapter, (String) o);
// isDownloadFinish[0] = true;
mHandler.sendMessage(mHandler.obtainMessage(8));
}
needDownloadChapters.add(chapter);
}
}
needCacheChapterNum = needDownloadChapters.size();
if (!isDownloadAll && needCacheChapterNum > 0) {
mHandler.sendEmptyMessage(12);
}
mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval);
for (Chapter chapter : needDownloadChapters) {
getChapterContent(book, chapter, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
downloadingChapter = chapter.getTitle();
mChapterService.saveOrUpdateChapter(chapter, (String) o);
curCacheChapterNum++;
}
@Override
public void onError(Exception e) {
// isDownloadFinish[0] = true;
curCacheChapterNum[0]++;
downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum;
mHandler.sendMessage(mHandler.obtainMessage(8));
}
});
} else {
//isDownloadFinish[0] = true;
curCacheChapterNum[0]++;
downloadProcess = curCacheChapterNum[0] * 100 / needCacheChapterNum;
mHandler.sendMessage(mHandler.obtainMessage(8));
@Override
public void onError(Exception e) {
curCacheChapterNum++;
}
});
try {
Thread.sleep(downloadInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (curCacheChapterNum[0] == needCacheChapterNum) {
if (curCacheChapterNum == needCacheChapterNum) {
if (!isDownloadAll) {
isStopDownload = true;
}
mHandler.sendMessage(mHandler.obtainMessage(9));
}
/*while (true){
if (isDownloadFinish[0]){
break;
}
}*/
if (isStopDownload) {
break;
}
}
if (!isDownloadAll) {
if (curCacheChapterNum == needCacheChapterNum) {
TextHelper.showText("《" + book.getName() + "》" + mMainActivity.getString(R.string.download_already_all_tips));
}
}
}
@ -742,21 +792,55 @@ public class BookcasePresenter implements BasePresenter {
if (StringHelper.isEmpty(chapter.getBookId())) {
chapter.setBookId(mBook.getId());
}
if (!StringHelper.isEmpty(chapter.getContent())) {
if (resultCallback != null) {
resultCallback.onFinish(mChapterService.getChapterCatheContent(chapter), 0);
}
ReadCrawler mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, resultCallback);
}
/**
* 发送通知
*/
private void sendNotification() {
if (curCacheChapterNum == needCacheChapterNum) {
mHandler.sendEmptyMessage(9);
notificationUtil.cancelAll();
return;
} else {
ReadCrawler mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
CommonApi.getChapterContent(chapter.getUrl(), mReadCrawler, resultCallback);
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
//点击通知后自动清除
.setAutoCancel(true)
.setContentTitle("正在下载:" + downloadingBook +
"[" + curCacheChapterNum + "/" + needCacheChapterNum + "]")
.setContentText(downloadingChapter == null ? " " : downloadingChapter)
.addAction(R.drawable.ic_stop_black_24dp, "停止",
notificationUtil.getChancelPendingIntent(cancelDownloadReceiver.class))
.build();
notificationUtil.notify(1000, notification);
}
if (tempCacheChapterNum < curCacheChapterNum) {
tempCount = 1500 / downloadInterval;
tempCacheChapterNum = curCacheChapterNum;
} else if (tempCacheChapterNum == curCacheChapterNum) {
tempCount--;
if (tempCount == 0) {
isDownloadFinish = true;
notificationUtil.cancel(1000);
return;
}
}
mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval);
}
private void sendNotification(String book) {
mBookcaseFragment.getPbDownload().setProgress(downloadProcess);
mBookcaseFragment.getTvDownloadTip().setText("正在缓存:" + book + "[" + downloadProcess + "%]");
if (downloadProcess == 100) {
mHandler.sendMessage(mHandler.obtainMessage(9));
public static class cancelDownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//todo 跳转之前要处理的逻辑
if (CANCEL_ACTION.equals(intent.getAction())) {
isStopDownload = true;
}
}
}
@ -841,6 +925,19 @@ public class BookcasePresenter implements BasePresenter {
public void cancelEdit() {
editBookcase(false);
}
/**
*
*/
public void destroy() {
notificationUtil.cancelAll();
mHandler.removeCallbacks(sendDownloadNotification);
for (int i = 0; i < 13; i++) {
mHandler.removeMessages(i + 1);
}
}
/*class NotificationService extends Service{
@Nullable

@ -15,6 +15,7 @@ import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.entity.bookstore.BookType;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.ui.bookinfo.BookDetailedActivity;
import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity;
import xyz.fycz.myreader.ui.home.MainActivity;
import xyz.fycz.myreader.util.TextHelper;
@ -175,7 +176,7 @@ public class BookStorePresenter implements BasePresenter {
mBookStoreBookAdapter.setOnItemClickListener(new BookStoreBookAdapter.OnItemClickListener() {
@Override
public void onClick(int pos, View view) {
Intent intent = new Intent(mBookStoreFragment.getActivity(), BookInfoActivity.class);
Intent intent = new Intent(mBookStoreFragment.getActivity(), BookDetailedActivity.class);
intent.putExtra(APPCONST.BOOK, bookList.get(pos));
mBookStoreFragment.getActivity().startActivity(intent);
}

@ -2,11 +2,13 @@ package xyz.fycz.myreader.ui.read;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
@ -43,11 +45,14 @@ import xyz.fycz.myreader.greendao.service.BookMarkService;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.greendao.service.ChapterService;
import xyz.fycz.myreader.ui.font.FontsActivity;
import xyz.fycz.myreader.ui.home.bookcase.BookcasePresenter;
import xyz.fycz.myreader.ui.read.catalog.CatalogActivity;
import xyz.fycz.myreader.util.BrightUtil;
import xyz.fycz.myreader.util.ScreenHelper;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.TextHelper;
import xyz.fycz.myreader.util.notification.NotificationClickReceiver;
import xyz.fycz.myreader.util.notification.NotificationUtil;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.webapi.CommonApi;
import xyz.fycz.myreader.widget.page.LocalPageLoader;
@ -68,6 +73,7 @@ public class ReadPresenter implements BasePresenter {
private ChapterService mChapterService;
private BookService mBookService;
private BookMarkService mBookMarkService;
private NotificationUtil notificationUtil;
private Setting mSetting;
private boolean settingChange;//是否是设置改变
@ -95,6 +101,12 @@ public class ReadPresenter implements BasePresenter {
private Runnable keepScreenRunnable;//息屏线程
private Runnable autoPageRunnable;//自动翻页
private Runnable upHpbNextPage;//更新自动翻页进度条
private Runnable sendDownloadNotification;
private static boolean isStopDownload = true;
private int tempCacheChapterNum;
private int tempCount;
private String downloadingChapter;
private ReadCrawler mReadCrawler;
@ -104,6 +116,8 @@ public class ReadPresenter implements BasePresenter {
private int upHpbInterval = 30;//更新翻页进度速度
private int downloadInterval = 150;
private final CharSequence[] pageMode = {
"覆盖", "仿真", "滑动", "滚动", "无动画"
};
@ -156,6 +170,10 @@ public class ReadPresenter implements BasePresenter {
case 8:
mReadActivity.getPbLoading().setVisibility(View.GONE);
break;
case 9:
TextHelper.showText("正在后台缓存书籍,具体进度可查看通知栏!");
notificationUtil.requestNotificationPermissionDialog(mReadActivity);
break;
}
}
};
@ -168,20 +186,26 @@ public class ReadPresenter implements BasePresenter {
int level = intent.getIntExtra("level", 0);
try {
mPageLoader.updateBattery(level);
}catch (Exception e){
} catch (Exception e) {
e.printStackTrace();
}
}
// 监听分钟的变化
else if (Intent.ACTION_TIME_TICK.equals(intent.getAction())) {
mPageLoader.updateTime();
try {
mPageLoader.updateTime();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
public ReadPresenter(ReadActivity readActivity) {
mReadActivity = readActivity;
mBookService = BookService.getInstance();;
mBookService = BookService.getInstance();
;
mChapterService = ChapterService.getInstance();
mBookMarkService = BookMarkService.getInstance();
mSetting = SysManager.getSetting();
@ -197,6 +221,9 @@ public class ReadPresenter implements BasePresenter {
upHpbNextPage = this::upHpbNextPage;
sendDownloadNotification = this::sendNotification;
notificationUtil = NotificationUtil.getInstance();
//注册广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
@ -393,7 +420,7 @@ public class ReadPresenter implements BasePresenter {
Intent intent = new Intent(mReadActivity, FontsActivity.class);
mReadActivity.startActivityForResult(intent, APPCONST.REQUEST_FONT);
}, this::showPageModeDialog, v -> {
if (mSetting.getPageMode() == PageMode.SCROLL){
if (mSetting.getPageMode() == PageMode.SCROLL) {
TextHelper.showText("滚动暂时不支持自动翻页");
return;
}
@ -509,7 +536,7 @@ public class ReadPresenter implements BasePresenter {
/**
* 初始化监听器
*/
private void initListener(){
private void initListener() {
mReadActivity.getSrlContent().setTouchListener(new PageView.TouchListener() {
@Override
@ -523,7 +550,7 @@ public class ReadPresenter implements BasePresenter {
if (mPageLoader.getPageStatus() == PageLoader.STATUS_FINISH) {
showSettingView();
}
if (autoPage){
if (autoPage) {
autoPageStop();
}
}
@ -539,7 +566,7 @@ public class ReadPresenter implements BasePresenter {
if (!hasNextPage && endPageTipCount == 3) {
mReadActivity.getTvEndPageTip().setVisibility(View.VISIBLE);
mHandler.sendMessage(mHandler.obtainMessage(5));
if (autoPage){
if (autoPage) {
autoPageStop();
}
}
@ -604,8 +631,6 @@ public class ReadPresenter implements BasePresenter {
}
/**
* 章节数据网络同步
*/
@ -614,7 +639,7 @@ public class ReadPresenter implements BasePresenter {
if (!isCollected || mChapters.size() == 0 || ("本地书籍".equals(mBook.getType()) &&
!ChapterService.isChapterCached(mBook.getId(), mChapters.get(0).getTitle()))) {
if ("本地书籍".equals(mBook.getType())) {
if (!new File(mBook.getChapterUrl()).exists()){
if (!new File(mBook.getChapterUrl()).exists()) {
TextHelper.showText("书籍缓存为空且源文件不存在,书籍加载失败!");
mReadActivity.finish();
return;
@ -729,65 +754,81 @@ public class ReadPresenter implements BasePresenter {
TextHelper.showText("无网络连接!");
return;
}
new AlertDialog.Builder(mReadActivity)
.setTitle("缓存书籍")
.setSingleChoiceItems(APPCONST.DIALOG_DOWNLOAD, selectedIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
selectedIndex = which;
}
}).setNegativeButton("取消", (new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})).setPositiveButton("确定",
(dialog, which) -> {
switch (selectedIndex) {
case 0:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mPageLoader.getChapterPos() + 50);
break;
case 1:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos() - 50, mPageLoader.getChapterPos() + 50);
break;
case 2:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mChapters.size());
break;
case 3:
addDownload(tvDownloadProgress, 0, mChapters.size());
break;
}
}).show();
MyApplication.runOnUiThread(() ->{
new AlertDialog.Builder(mReadActivity)
.setTitle("缓存书籍")
.setSingleChoiceItems(APPCONST.DIALOG_DOWNLOAD, selectedIndex, (dialog, which) -> selectedIndex = which).setNegativeButton("取消", ((dialog, which) -> dialog.dismiss())).setPositiveButton("确定",
(dialog, which) -> {
switch (selectedIndex) {
case 0:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mPageLoader.getChapterPos() + 50);
break;
case 1:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos() - 50, mPageLoader.getChapterPos() + 50);
break;
case 2:
addDownload(tvDownloadProgress, mPageLoader.getChapterPos(), mChapters.size());
break;
case 3:
addDownload(tvDownloadProgress, 0, mChapters.size());
break;
}
}).show();
});
}
private void addDownload(final TextView tvDownloadProgress, int begin, int end) {
/*//取消之前下载
if (!isStopDownload) {
isStopDownload = true;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
//计算断点章节
final int finalBegin = Math.max(0, begin);
final int finalEnd = Math.min(end, mChapters.size());
needCacheChapterNum = finalEnd - finalBegin;
curCacheChapterNum = 0;
isStopDownload = false;
ArrayList<Chapter> needDownloadChapters = new ArrayList<>();
for (int i = finalBegin; i < finalEnd; i++) {
final Chapter chapter = mChapters.get(i);
if (StringHelper.isEmpty(chapter.getContent())) {
needDownloadChapters.add(chapter);
}
}
needCacheChapterNum = needDownloadChapters.size();
if (needCacheChapterNum > 0) {
mHandler.sendEmptyMessage(9);
mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval);
}
MyApplication.getApplication().newThread(() -> {
for (int i = finalBegin; i < finalEnd; i++) {
final Chapter chapter = mChapters.get(i);
if (StringHelper.isEmpty(chapter.getContent())) {
getChapterContent(chapter, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
// chapter.setContent((String) o);
mChapterService.saveOrUpdateChapter(chapter, (String) o);
curCacheChapterNum++;
mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress));
}
for (Chapter chapter : needDownloadChapters) {
getChapterContent(chapter, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
downloadingChapter = chapter.getTitle();
mChapterService.saveOrUpdateChapter(chapter, (String) o);
curCacheChapterNum++;
mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress));
}
@Override
public void onError(Exception e) {
curCacheChapterNum++;
mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress));
}
});
} else {
curCacheChapterNum++;
mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress));
@Override
public void onError(Exception e) {
curCacheChapterNum++;
mHandler.sendMessage(mHandler.obtainMessage(3, tvDownloadProgress));
}
});
try {
Thread.sleep(downloadInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isStopDownload) {
break;
}
}
if (curCacheChapterNum == needCacheChapterNum) {
@ -805,6 +846,52 @@ public class ReadPresenter implements BasePresenter {
}
}
/**
* 发送通知
*/
private void sendNotification() {
if (curCacheChapterNum == needCacheChapterNum) {
notificationUtil.cancel(1001);
return;
} else {
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
//点击通知后自动清除
.setAutoCancel(true)
.setContentTitle("正在下载:" + mBook.getName() +
"[" + curCacheChapterNum + "/" + needCacheChapterNum + "]")
.setContentText(downloadingChapter == null ? " " : downloadingChapter)
.addAction(R.drawable.ic_stop_black_24dp, "停止",
notificationUtil.getChancelPendingIntent(cancelDownloadReceiver.class))
.build();
notificationUtil.notify(1001, notification);
}
if (tempCacheChapterNum < curCacheChapterNum) {
tempCount = 1500 / downloadInterval;
tempCacheChapterNum = curCacheChapterNum;
} else if (tempCacheChapterNum == curCacheChapterNum) {
tempCount--;
if (tempCount == 0) {
notificationUtil.cancel(1001);
return;
}
}
mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval);
}
public static class cancelDownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//todo 跳转之前要处理的逻辑
if (NotificationClickReceiver.CANCEL_ACTION.equals(intent.getAction())) {
isStopDownload = true;
}
}
}
/**
* 获取章节内容
*
@ -982,6 +1069,7 @@ public class ReadPresenter implements BasePresenter {
/**
* 添加本地书籍
*
* @param path
*/
private void addLocalBook(String path) {
@ -1001,7 +1089,7 @@ public class ReadPresenter implements BasePresenter {
//判断书籍是否已经添加
Book existsBook = mBookService.findBookByAuthorAndName(book.getName(), book.getAuthor());
if (book.equals(existsBook)){
if (book.equals(existsBook)) {
mBook = existsBook;
return;
}
@ -1012,10 +1100,11 @@ public class ReadPresenter implements BasePresenter {
/**
* 跳转到指定章节的指定页面
*
* @param chapterPos
* @param pagePos
*/
private void skipToChapterAndPage(final int chapterPos, final int pagePos){
private void skipToChapterAndPage(final int chapterPos, final int pagePos) {
isPrev = false;
if (StringHelper.isEmpty(mChapters.get(chapterPos).getContent())) {
if ("本地书籍".equals(mBook.getType())) {
@ -1122,10 +1211,16 @@ public class ReadPresenter implements BasePresenter {
*/
public void onDestroy() {
mReadActivity.unregisterReceiver(mReceiver);
mHandler.removeCallbacks(keepScreenRunnable);
mHandler.removeCallbacks(upHpbNextPage);
mHandler.removeCallbacks(autoPageRunnable);
/*mHandler.removeCallbacks(sendDownloadNotification);
notificationUtil.cancelAll();
MyApplication.getApplication().shutdownThreadPool();*/
if (autoPage) {
autoPageStop();
}
for (int i = 0; i < 8; i++) {
for (int i = 0; i < 9; i++) {
mHandler.removeMessages(i + 1);
}
mPageLoader.closeBook();

@ -22,6 +22,7 @@ import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.crawler.*;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.ui.bookinfo.BookDetailedActivity;
import xyz.fycz.myreader.ui.bookinfo.BookInfoActivity;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BasePresenter;
@ -141,7 +142,7 @@ public class SearchBookPrensenter implements BasePresenter {
});
//进入书籍详情页
mSearchBookActivity.getGvSearchBooksList().setOnItemClickListener((adapterView, view, i, l) -> {
Intent intent = new Intent(mSearchBookActivity, BookInfoActivity.class);
Intent intent = new Intent(mSearchBookActivity, BookDetailedActivity.class);
intent.putExtra(APPCONST.SEARCH_BOOK_BEAN, new ArrayList<>(mBooks.getValues(mBooksBean.get(i))));
mSearchBookActivity.startActivity(intent);
});

@ -219,55 +219,51 @@ public class HttpUtil {
}
public static void sendGetRequest_okHttp(final String address, final HttpCallback callback) {
MyApplication.getApplication().newThread(new Runnable() {
@Override
public void run() {
/* HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-type", "text/html");
connection.setRequestProperty("Accept-Charset", "gbk");
connection.setRequestProperty("contentType", "gbk");
connection.setConnectTimeout(5 * 1000);
connection.setReadTimeout(5 * 1000);
connection.setDoInput(true);
connection.setDoOutput(true);
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.e("Http", "网络错误异常!!!!");
}
InputStream in = connection.getInputStream();
Log.d("Http", "connection success");
if (callback != null) {
callback.onFinish(in);
}
} catch (Exception e) {
e.printStackTrace();
Log.e("Http", e.toString());
if (callback != null) {
callback.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}*/
try{
OkHttpClient client = getOkHttpClient();
Request request = new Request.Builder()
.addHeader("User-Agent","Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)")
.url(address)
.build();
Response response = client.newCall(request).execute();
callback.onFinish(response.body().byteStream());
}catch(Exception e){
e.printStackTrace();
callback.onError(e);
}
}
});
MyApplication.getApplication().newThread(() -> {
/* HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-type", "text/html");
connection.setRequestProperty("Accept-Charset", "gbk");
connection.setRequestProperty("contentType", "gbk");
connection.setConnectTimeout(5 * 1000);
connection.setReadTimeout(5 * 1000);
connection.setDoInput(true);
connection.setDoOutput(true);
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.e("Http", "网络错误异常!!!!");
}
InputStream in = connection.getInputStream();
Log.d("Http", "connection success");
if (callback != null) {
callback.onFinish(in);
}
} catch (Exception e) {
e.printStackTrace();
Log.e("Http", e.toString());
if (callback != null) {
callback.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}*/
try{
OkHttpClient client = getOkHttpClient();
Request request = new Request.Builder()
.addHeader("User-Agent","Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)")
.url(address)
.build();
Response response = client.newCall(request).execute();
callback.onFinish(response.body().byteStream());
}catch(Exception e){
e.printStackTrace();
callback.onError(e);
}
});
}
/**

@ -11,21 +11,11 @@ public class TextHelper {
public static void showText(final String text){
MyApplication.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_SHORT).show();
}
});
MyApplication.runOnUiThread(() -> Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_SHORT).show());
}
public static void showLongText(final String text){
MyApplication.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_LONG).show();
}
});
MyApplication.runOnUiThread(() -> Toast.makeText(MyApplication.getApplication(),text, Toast.LENGTH_LONG).show());
}
}

@ -0,0 +1,23 @@
package xyz.fycz.myreader.util.notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import xyz.fycz.myreader.application.MyApplication;
/**
* @author fengyue
* @date 2020/8/14 22:04
*/
public class NotificationClickReceiver extends BroadcastReceiver {
public static final String CANCEL_ACTION = "cancelAction";
@Override
public void onReceive(Context context, Intent intent) {
//todo 跳转之前要处理的逻辑
if (CANCEL_ACTION.equals(intent.getAction())){
MyApplication.getApplication().shutdownThreadPool();
}
}
}

@ -0,0 +1,171 @@
package xyz.fycz.myreader.util.notification;
import android.annotation.TargetApi;
import android.app.*;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.creator.DialogCreator;
import xyz.fycz.myreader.greendao.service.BookMarkService;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static xyz.fycz.myreader.util.notification.NotificationClickReceiver.CANCEL_ACTION;
/**
* @author fengyue
* @date 2020/8/14 22:07
*/
public class NotificationUtil {
private static volatile NotificationUtil sInstance;
private NotificationManager notificationManager;
public static NotificationUtil getInstance() {
if (sInstance == null){
synchronized (NotificationUtil.class){
if (sInstance == null){
sInstance = new NotificationUtil();
}
}
}
return sInstance;
}
public NotificationUtil() {
notificationManager = (NotificationManager) MyApplication.getmContext().getSystemService(Context.NOTIFICATION_SERVICE);
}
public NotificationCompat.Builder createBuilder(Context context, String channelId){
return new NotificationCompat.Builder(context, channelId);
}
@TargetApi(26)
public void createNotificationChannel(String channelId, String channelName) {
NotificationManager notificationManager = (NotificationManager) MyApplication.getApplication().getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
channel.enableLights(true);//是否在桌面icon右上角展示小红点
channel.setLightColor(Color.RED);//小红点颜色
channel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(channel);
}
public NotificationCompat.Builder build(String channelId){
return new NotificationCompat.Builder(MyApplication.getmContext(), channelId);
}
public void sendDownloadNotification(String title, String text, PendingIntent pendingIntent){
NotificationCompat.Builder builder = build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
//点击通知后自动清除
.setAutoCancel(true)
.setContentTitle(title)
.setContentText(text);
if (pendingIntent == null) {
pendingIntent = getChancelPendingIntent(NotificationClickReceiver.class);
}
builder.addAction(R.drawable.ic_stop_black_24dp, "停止", pendingIntent);
notificationManager.notify(1000, builder.build());
}
public PendingIntent getChancelPendingIntent(Class<?> clz) {
Intent intent = new Intent(MyApplication.getmContext(), clz);
intent.setAction(CANCEL_ACTION);
return PendingIntent.getBroadcast(MyApplication.getmContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public void cancel(int id){
notificationManager.cancel(id);
}
public void cancelAll(){
notificationManager.cancelAll();
}
public void notify(int id, Notification notification){
notificationManager.notify(id, notification);
}
/**
* 跳到通知栏设置界面
* @param context
*/
public void requestNotificationPermission(Context context){
if (!isNotificationEnabled(context)) {
try {
// 根据isOpened结果,判断是否需要提醒用户跳转AppInfo页面,去打开App通知权限
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
//这种方案适用于 API 26, 即8.0(含8.0)以上可以用
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.getApplicationInfo().uid);
//这种方案适用于 API21——25,即 5.0——7.1 之间的版本可以使用
intent.putExtra("app_package", context.getPackageName());
intent.putExtra("app_uid", context.getApplicationInfo().uid);
// 小米6 -MIUI9.6-8.0.0系统,是个特例,通知设置界面只能控制"允许使用通知圆点"——然而这个玩意并没有卵用,我想对雷布斯说:I'm not ok!!!
if ("MI 6".equals(Build.MODEL)) {
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", context.getPackageName(), null);
intent.setData(uri);
intent.setAction("com.android.settings/.SubSettings");
}
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
// 出现异常则跳转到应用设置界面:锤子坚果3——OC105 API25
Intent intent = new Intent();
//下面这种方案是直接跳转到当前应用的设置界面。
//https://blog.csdn.net/ysy950803/article/details/71910806
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri = Uri.fromParts("package", context.getPackageName(), null);
intent.setData(uri);
context.startActivity(intent);
}
}
}
public void requestNotificationPermissionDialog(Context context){
if (!isNotificationEnabled(context)) {
new AlertDialog.Builder(context)
.setTitle("开启通知")
.setMessage("检测到未开启通知权限,无法在通知栏查看缓存进度,是否前往开启?")
.setCancelable(true)
.setPositiveButton("确定", (dialog, which) -> requestNotificationPermission(context))
.setNegativeButton("取消", null)
.show();
}
}
/**
* 获取通知权限
* @param context
*/
public boolean isNotificationEnabled(Context context) {
boolean isOpened = false;
try {
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
isOpened = manager.areNotificationsEnabled();
} catch (Exception e) {
e.printStackTrace();
}
return isOpened;
}
}

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1065.0"
android:viewportHeight="1024.0">
<path
android:pathData="M486.4,1024h-102.4a76.9,76.9 0,0 1,-76.8 -76.8V76.8A76.9,76.9 0,0 1,384 0h102.4a76.9,76.9 0,0 1,76.8 76.8v870.4a76.9,76.9 0,0 1,-76.8 76.8zM384,51.2A25.6,25.6 0,0 0,358.4 76.8v870.4a25.6,25.6 0,0 0,25.6 25.6h102.4a25.6,25.6 0,0 0,25.6 -25.6V76.8a25.6,25.6 0,0 0,-25.6 -25.6zM179.2,1024H76.8A76.9,76.9 0,0 1,0 947.2V128a76.9,76.9 0,0 1,76.8 -76.8H179.2a76.9,76.9 0,0 1,76.8 76.8v819.2A76.9,76.9 0,0 1,179.2 1024zM76.8,102.4a25.6,25.6 0,0 0,-25.6 25.6v819.2a25.6,25.6 0,0 0,25.6 25.6H179.2a25.6,25.6 0,0 0,25.6 -25.6V128A25.6,25.6 0,0 0,179.2 102.4z"
android:fillColor="@color/black"/>
<path
android:pathData="M799.4,636.8l-130.9,-488.6a25.6,25.6 0,0 1,18.1 -31.4l98.9,-26.5a25.3,25.3 0,0 1,6.7 -0.9,25.6 25.6,0 0,1 24.7,19l141.6,528.4h53L866.3,95.2a76.7,76.7 0,0 0,-94.1 -54.3l-98.9,26.5a76.9,76.9 0,0 0,-54.3 94.1l127.4,475.3zM1065.6,841.6h-128v-128h-51.2v128h-128v51.2h128v128h51.2v-128h128v-51.2z"
android:fillColor="@color/black"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024.0"
android:viewportHeight="1024.0">
<path
android:pathData="M911.8,44L663.3,44c-63.7,0 -119.6,33.4 -150.9,83.4 -31.3,-50.1 -87.1,-83.4 -150.8,-83.4L111.3,44c-49,0 -88.7,39.5 -88.7,88.2v706.8c0,48.7 39.7,88.2 88.7,88.2h298.3c22.4,32 59.7,52.9 101.9,52.9 42.2,0 79.5,-20.9 101.9,-52.9h298.3c49,0 88.7,-39.5 88.7,-88.2L1000.6,132.2c0,-48.7 -39.7,-88.2 -88.7,-88.2zM947.3,839c0,9.3 -3.7,18.3 -10.4,24.9a35.8,35.8 0,0 1,-25.1 10.3L580.3,874.3c-7.9,30.4 -35.7,52.9 -68.8,52.9 -33.1,0 -60.9,-22.5 -68.8,-52.9L111.3,874.3c-9.4,0 -18.5,-3.7 -25.1,-10.3a35.2,35.2 0,0 1,-10.4 -24.9L75.8,132.2a35.2,35.2 0,0 1,10.4 -24.9c6.6,-6.6 15.7,-10.3 25.1,-10.3h250.4c68.6,0 124.2,55.3 124.2,123.4v473.5h0.1c1.1,13.8 12.6,24.4 26.5,24.4 13.9,0 25.5,-10.6 26.5,-24.4h0.1L539.1,220.4c0,-68.2 55.6,-123.4 124.2,-123.4h248.5a35.7,35.7 0,0 1,25.1 10.3,35.2 35.2,0 0,1 10.4,24.9v706.8h0z"
android:fillColor="@color/white"/>
</vector>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/pwd_visiable" android:state_checked="true"/>
<item android:drawable="@mipmap/pwd_gone" android:state_checked="false"/>
<item android:drawable="@mipmap/pwd_gone" />
</selector>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="top"
app:expandedTitleMarginStart="114dp"
app:expandedTitleMarginTop="66dp"
app:expandedTitleTextAppearance="@style/Base.TextAppearance.AppCompat.Title"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<include layout="@layout/layout_book_detail_header"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.ToolBar.Menu"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<!--详情简介-->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include layout="@layout/layout_book_detail_content"/>
</androidx.core.widget.NestedScrollView>
<!--底部button-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical">
<include layout="@layout/layout_book_detail_bottom"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="top"
android:background="#140000"/>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/BottomNavigation.GroupView"
android:background="@color/white">
<FrameLayout
android:id="@+id/fl_add_bookcase"
style="@style/BottomNavigation.ItemView">
<androidx.appcompat.widget.AppCompatImageView
style="@style/BottomNavigation.ItemView.Icon"
android:background="@drawable/ic_vector_add_bookcase"/>
<TextView
android:id="@+id/book_detail_tv_add"
style="@style/BottomNavigation.ItemView.Title"
android:text="加入书架"
android:textColor="@color/black"/>
</FrameLayout>
<FrameLayout
android:id="@+id/fl_open_book"
style="@style/BottomNavigation.ItemView"
android:background="?colorPrimary">
<androidx.appcompat.widget.AppCompatImageView
style="@style/BottomNavigation.ItemView.Icon"
android:background="@drawable/ic_vector_book_read"/>
<TextView
android:id="@+id/book_detail_tv_open"
style="@style/BottomNavigation.ItemView.Title"
android:text="开始阅读"
android:textColor="@color/white"/>
</FrameLayout>
</LinearLayout>

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--简介-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="简介:"
android:textColor="@color/black"
android:textSize="18sp"/>
<TextView
android:id="@+id/book_detail_tv_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-10dp"
android:ellipsize="end"
android:maxLines="5"
android:padding="15dp"
android:lineSpacingMultiplier="1.2"
android:text="简介: "
android:textColor="@color/title_black"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="vertical">
<TextView
android:layout_marginTop="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.2"
android:textSize="14sp"
android:text="@string/statement"
android:textColor="@color/title_black"
/>
<TextView
android:id="@+id/tv_disclaimer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/disclaimer"
android:textColor="#2196F3"
android:textSize="14sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/sys_window_back"
android:visibility="gone"/>
<!--书籍目录-->
<RelativeLayout
android:id="@+id/book_detail_rl_catalog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView
android:id="@+id/book_detail_tv_catalog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:text="最新章节"
android:textColor="@color/black"
android:textSize="15sp" />
<TextView
android:id="@+id/book_detail_tv_catalog_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:padding="15dp"
android:text="更多"
android:textColor="@color/title_black"
android:textSize="15sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/book_detail_rv_catalog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/book_detail_tv_catalog"
android:foregroundGravity="center" />
</RelativeLayout>
<!--底部空白,给底部bottom预留位置-->
<!--<View-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="56dp"-->
<!--android:background="@color/divider_wide" />-->
</LinearLayout>

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="64.0dp"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="16.0dp"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7">
<androidx.cardview.widget.CardView
android:id="@+id/cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="0dp"
app:cardElevation="2dp">
<ImageView
android:id="@+id/book_detail_iv_cover"
android:layout_width="82dp"
android:layout_height="110dp"
android:scaleType="centerCrop"
android:background="@color/colorPrimary"
tools:src="@mipmap/no_image" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/book_detail_author_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/cover"
android:layout_marginLeft="14dp"
android:layout_marginTop="30dp"
android:layout_toRightOf="@+id/cover">
<TextView
android:id="@+id/book_detail_tv_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
tools:text=" 茶叶蛋" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" | "
android:textColor="@color/little_black_white"/>
<TextView
android:id="@+id/book_detail_tv_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
tools:text="仙侠" />
</LinearLayout>
<TextView
android:id="@+id/book_detail_newest_chapter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/book_detail_author_type"
android:layout_alignLeft="@id/book_detail_author_type"
android:layout_marginTop="6dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
tools:text="最新章节" />
<TextView
android:id="@+id/book_detail_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/book_detail_newest_chapter"
android:layout_alignLeft="@id/book_detail_newest_chapter"
android:layout_marginTop="6dp"
android:textColor="@color/white"
tools:text="书源" />
<!--<TextView-->
<!--android:id="@+id/book_detail_tv_lately_update"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_below="@id/book_detail_tv_author"-->
<!--android:layout_marginTop="10dp"-->
<!--android:layout_toRightOf="@id/cover"-->
<!--android:ellipsize="end"-->
<!--android:singleLine="true"-->
<!--android:textColor="@color/textAssist"-->
<!--android:textSize="13sp"-->
<!--tools:text="4月前"/>-->
</RelativeLayout>

@ -0,0 +1,19 @@
<?xml version ="1.0" encoding ="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_change_source"
android:icon="@mipmap/ic_menu_exchange"
android:title="更换书源"
app:showAsAction="always" />
<item
android:id="@+id/action_reload"
android:icon="@mipmap/ic_menu_refresh"
android:title="重新加载"/>
<item
android:id="@+id/action_open_link"
android:title="打开链接" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@ -38,7 +38,7 @@
<string name="disclaimer">免责声明</string>
<string name="statement">声明:小说数据全部从网络获取,软件不提供或保存任何小说,详情请参考免责声明:</string>
<string name="all_cathe_tip">"说明:本功能属于实验功能,书籍缓存的章节是从历史阅读章节到最新章节!\n\n存在的问题:\n1、书源为品书网的书籍因网站原因无法一键缓存!\n\n2、部分书籍缓存时会卡在99%的位置\n解决方案:点击停止按钮直接进入下一本书籍缓存/重新点击一键缓存!\n\n3、书籍缓存时进入阅读时章节加载可能变慢!\n\n确定要一键缓存?"</string>
<string name="all_cathe_tip">"说明:1、书籍缓存的章节是从历史阅读章节到最新章节!\n\n2、书源为品书网的书籍因网站原因无法一键缓存!\n\n确定要一键缓存?"</string>
<!--menu-->
<string name="menu_book_Top">移至顶部</string>
@ -50,7 +50,7 @@
<string name="menu_bookcase_style">切换布局</string>
<string name="menu_bookcase_add">添加本地</string>
<string name="menu_bookcase_syn">同步书架</string>
<string name="menu_bookcase_download_all">一键缓存(实验)</string>
<string name="menu_bookcase_download_all">一键缓存</string>
<string name="menu_bookcase_backup">备份/恢复</string>
<string name="menu_bookcase_about">关于软件</string>

@ -91,7 +91,44 @@
<item name="android:divider">@color/divider_common</item>
<item name="android:dividerHeight">0.5dp</item>
</style>
<!--BookDetail-->
<style name="BottomNavigationViewTheme" />
<style name="BottomNavigation" />
<style name="BottomNavigation.GroupView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">56dp</item>
<item name="android:layout_gravity">bottom</item>
<item name="android:elevation">4dp</item>
<item name="android:orientation">horizontal</item>
</style>
<style name="BottomNavigation.ItemView">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:clickable">true</item>
</style>
<style name="BottomNavigation.ItemView.Icon" parent="">
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_width">24dp</item>
<item name="android:layout_height">24dp</item>
<item name="android:layout_gravity">top|center_horizontal</item>
<item name="android:layout_centerHorizontal">true</item>
<item name="android:layout_alignParentTop">true</item>
</style>
<style name="BottomNavigation.ItemView.Title" parent="">
<item name="android:layout_marginBottom">8dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">bottom|center_horizontal</item>
<item name="android:layout_centerHorizontal">true</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:textSize">12sp</item>
</style>
</resources>

@ -1,2 +1,2 @@
#Wed Aug 12 21:27:50 CST 2020
VERSION_CODE=141
#Sat Aug 15 23:51:09 CST 2020
VERSION_CODE=142

Loading…
Cancel
Save