From 0fe7b599898832e3f4a3d8f8fcfe4c32b1564ccb Mon Sep 17 00:00:00 2001 From: fengyuecanzhu <1021300691@qq.com> Date: Sun, 13 Jun 2021 22:57:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81epub=E9=98=85=E8=AF=BB(?= =?UTF-8?q?=E4=BB=85=E6=94=AF=E6=8C=81=E6=96=87=E5=AD=97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/assetWizardSettings.xml | 4 +- app/build.gradle | 5 + app/src/main/AndroidManifest.xml | 1 + .../xyz/fycz/myreader/common/APPCONST.java | 9 +- .../xyz/fycz/myreader/common/URLCONST.java | 2 + .../xyz/fycz/myreader/entity/Quotation.java | 31 ++ .../ui/activity/AdSettingActivity.java | 13 + .../myreader/ui/activity/DonateActivity.java | 25 +- .../ui/activity/FileSystemActivity.java | 3 +- .../myreader/ui/activity/ReadActivity.java | 203 +++-------- .../ui/activity/SearchBookActivity.java | 6 +- .../ui/fragment/FileCategoryFragment.java | 14 +- .../myreader/ui/fragment/FindFragment.java | 46 ++- .../ui/fragment/LocalBookFragment.java | 2 +- .../myreader/util/media/LocalFileLoader.java | 18 +- .../xyz/fycz/myreader/util/utils/AdUtils.java | 8 +- .../fycz/myreader/util/utils/FileUtils.java | 6 +- .../myreader/widget/page/BookChapterBean.java | 56 --- .../myreader/widget/page/EpubPageLoader.java | 328 ++++++++++++++++++ .../myreader/widget/page/LocalPageLoader.java | 122 +++---- .../myreader/widget/page/NetPageLoader.java | 76 ++-- .../fycz/myreader/widget/page/PageLoader.java | 16 +- .../fycz/myreader/widget/page/PageView.java | 7 +- app/src/main/res/drawable/ic_edit.xml | 4 +- app/src/main/res/drawable/ic_quotations.xml | 27 ++ ...{menu_book_detail.xml => shape_runway.xml} | 0 .../main/res/layout/activity_ad_setting.xml | 10 + app/src/main/res/layout/activity_read.xml | 8 - app/src/main/res/layout/activity_splash.xml | 4 +- app/src/main/res/layout/fragment_find.xml | 328 +++++++++++------- app/src/main/res/layout/menu_book.xml | 2 +- app/src/main/res/layout/menu_book_local.xml | 2 +- 32 files changed, 873 insertions(+), 513 deletions(-) create mode 100644 app/src/main/java/xyz/fycz/myreader/entity/Quotation.java delete mode 100644 app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java create mode 100644 app/src/main/java/xyz/fycz/myreader/widget/page/EpubPageLoader.java create mode 100644 app/src/main/res/drawable/ic_quotations.xml rename app/src/main/res/drawable/{menu_book_detail.xml => shape_runway.xml} (100%) diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 46bf8ba..ca8bf99 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -14,8 +14,8 @@ diff --git a/app/build.gradle b/app/build.gradle index 074096d..d3c6af9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -213,6 +213,11 @@ dependencies { //XXPermissions implementation 'com.github.getActivity:XXPermissions:11.2' + //epub + implementation('com.positiondev.epublib:epublib-core:3.1') { + exclude group: 'org.slf4j' + exclude group: 'xmlpull' + } } greendao { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 28d8283..ad3c638 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -109,6 +109,7 @@ + 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 0353c42..1df4798 100644 --- a/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java +++ b/app/src/main/java/xyz/fycz/myreader/common/APPCONST.java @@ -31,6 +31,7 @@ public class APPCONST { public static final String FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/"; public static final String SHARE_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/share/"; public static String LOG_DIR = FileUtils.getCachePath() + "/log/"; + public static String COVER_DIR = FileUtils.getCachePath() + "/covers/"; public static final String BG_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/bg/"; public static String TEM_FILE_DIR = FileUtils.getCachePath() + "/tem/"; public static final String BACKUP_FILE_DIR = Environment.getExternalStorageDirectory() + "/FYReader/backup/"; @@ -124,12 +125,4 @@ public class APPCONST { public static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4168.3 Safari/537.36"; - private static Boolean hasStoragePermission = null; - - public static boolean hasStoragePermission() { - if (hasStoragePermission == null) { - hasStoragePermission = XXPermissions.isGranted(App.getmContext(), STORAGE_PERMISSIONS); - } - return hasStoragePermission; - } } diff --git a/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java index 732bd49..80c2134 100644 --- a/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java +++ b/app/src/main/java/xyz/fycz/myreader/common/URLCONST.java @@ -29,5 +29,7 @@ public class URLCONST { public static final String WX_ZSM = DONATE + "/wx_zsm.png"; public static final String ZFB_SKM = DONATE + "/zfb_skm.jpg"; public static final String QQ_SKM = DONATE + "/qq_skm.png"; + + public static final String QUOTATION = "https://v1.hitokoto.cn/?encode=json&charset=utf-8"; } diff --git a/app/src/main/java/xyz/fycz/myreader/entity/Quotation.java b/app/src/main/java/xyz/fycz/myreader/entity/Quotation.java new file mode 100644 index 0000000..381dba1 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/Quotation.java @@ -0,0 +1,31 @@ +package xyz.fycz.myreader.entity; + +/** + * @author fengyue + * @date 2021/6/12 20:55 + */ +public class Quotation { + private String hitokoto; + private String from; + + public String getHitokoto() { + return hitokoto; + } + + public void setHitokoto(String hitokoto) { + this.hitokoto = hitokoto; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + @Override + public String toString() { + return hitokoto + " --- " + from; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/AdSettingActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/AdSettingActivity.java index f3c4dbc..3f416d0 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/AdSettingActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/AdSettingActivity.java @@ -1,5 +1,6 @@ package xyz.fycz.myreader.ui.activity; +import android.content.DialogInterface; import android.os.Bundle; import android.view.View; @@ -10,10 +11,12 @@ import com.kongzue.dialogx.dialogs.BottomMenu; import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.base.observer.MySingleObserver; import xyz.fycz.myreader.databinding.ActivityAdSettingBinding; import xyz.fycz.myreader.ui.dialog.LoadingDialog; +import xyz.fycz.myreader.ui.dialog.MyAlertDialog; import xyz.fycz.myreader.util.help.DateHelper; import xyz.fycz.myreader.util.SharedPreUtils; import xyz.fycz.myreader.util.ToastUtils; @@ -81,6 +84,7 @@ public class AdSettingActivity extends BaseActivity { binding.scAd.setChecked(aBoolean); if (aBoolean) { binding.llAdSetting.setVisibility(View.VISIBLE); + binding.rlFlowAdCount.setVisibility(App.isDebug() ? View.VISIBLE : View.GONE); } loadingDialog.dismiss(); } @@ -93,6 +97,15 @@ public class AdSettingActivity extends BaseActivity { String curAdTimesStr = getAdTimesStr(curAdTimes); binding.tvSplashCurAdTimes.setText(getString(R.string.splash_cur_ad_times, curAdTimesStr, curAdCount + "次")); binding.scBookDetailAd.setChecked(bookDetailAd); + binding.rlFlowAdCount.setOnClickListener(v -> { + int[] n = new int[1]; + MyAlertDialog.createInputDia(this, "", "", "", + true, 1, (MyAlertDialog.onInputChangeListener) text -> { + n[0] = Integer.parseInt(text); + }, (dialog, which) -> { + SharedPreUtils.getInstance().putInt("flowAdCount", n[0]); + }); + }); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/DonateActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/DonateActivity.java index 3fe4479..b1da373 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/DonateActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/DonateActivity.java @@ -16,10 +16,12 @@ import java.util.List; import io.reactivex.annotations.NonNull; import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.base.observer.MySingleObserver; import xyz.fycz.myreader.common.URLCONST; import xyz.fycz.myreader.databinding.ActivityDonateBinding; +import xyz.fycz.myreader.util.SharedPreUtils; import xyz.fycz.myreader.util.ToastUtils; import xyz.fycz.myreader.util.utils.AdUtils; @@ -60,25 +62,30 @@ public class DonateActivity extends BaseActivity { private void initAd() { binding.llAdSupport.setVisibility(View.VISIBLE); - new DdSdkFlowAd().getFlowViews(DonateActivity.this, 6, 1, new DdSdkFlowAd.FlowCallback() { + int flowAdCount = SharedPreUtils.getInstance().getInt("flowAdCount", 2); + int count = App.isDebug() ? flowAdCount : 1; + new DdSdkFlowAd().getFlowViews(DonateActivity.this, 6, count, new DdSdkFlowAd.FlowCallback() { // 信息流广告拉取完毕后返回的 views @Override public void getFlowViews(List views) { - Log.i(TAG, "信息流广告拉取完毕后返回了" + views.size() + "个view"); - binding.llAdSupport.addView(views.get(0), 2); + Log.d(TAG, "信息流广告拉取完毕后返回了" + views.size() + "个view"); + for (int i = 0; i < views.size(); i++) { + View view = views.get(i); + binding.llAdSupport.addView(view, i + 2); + } } // 信息流广告展示后调用 @Override public void show() { AdUtils.adRecord("flow","adShow"); - Log.i(TAG, "信息流广告展示成功"); + Log.d(TAG, "信息流广告展示成功"); } // 广告拉取失败调用 @Override public void error(String msg) { - Log.i(TAG, "广告拉取失败\n" + msg); + Log.d(TAG, "广告拉取失败\n" + msg); } }); } @@ -92,13 +99,13 @@ public class DonateActivity extends BaseActivity { DdSdkRewardAd.show(this, new DdSdkRewardAd.DdSdkRewardCallback() { @Override public void show() { - Log.i(TAG, "激励视频展示成功"); + Log.d(TAG, "激励视频展示成功"); AdUtils.adRecord("rewardVideo","adShow"); } @Override public void click() { - Log.i(TAG, "激励视频被点击"); + Log.d(TAG, "激励视频被点击"); AdUtils.adRecord("rewardVideo","adClick"); } @@ -108,13 +115,13 @@ public class DonateActivity extends BaseActivity { @Override public void skip() { - Log.i(TAG, "激励视频被跳过"); + Log.d(TAG, "激励视频被跳过"); AdUtils.adRecord("rewardVideo","adSkip"); } @Override public void finishCountdown() { - Log.i(TAG, "激励视频计时完成"); + Log.d(TAG, "激励视频计时完成"); AdUtils.adRecord("rewardVideo","adFinishCount"); } }); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/FileSystemActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/FileSystemActivity.java index 487ed50..a6faa06 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/FileSystemActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/FileSystemActivity.java @@ -21,6 +21,7 @@ import xyz.fycz.myreader.ui.fragment.BaseFileFragment; import xyz.fycz.myreader.ui.fragment.FileCategoryFragment; import xyz.fycz.myreader.ui.fragment.LocalBookFragment; import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.FileUtils; /** @@ -187,7 +188,7 @@ public class FileSystemActivity extends BaseTabActivity { if (!file.exists()) continue; Book book = new Book(); - book.setName(file.getName().replace(".txt", "")); + book.setName(file.getName().replace(FileUtils.SUFFIX_EPUB, "").replace(FileUtils.SUFFIX_TXT, "")); book.setChapterUrl(file.getAbsolutePath()); book.setType("本地书籍"); book.setHistoryChapterId("未开始阅读"); 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 e91a6f6..2227705 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 @@ -15,7 +15,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.speech.tts.TextToSpeech; import android.text.TextUtils; import android.util.Log; @@ -50,7 +49,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; -import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.schedulers.Schedulers; import xyz.fycz.myreader.ActivityManage; @@ -64,8 +62,8 @@ import xyz.fycz.myreader.common.APPCONST; import xyz.fycz.myreader.common.URLCONST; import xyz.fycz.myreader.databinding.ActivityReadBinding; import xyz.fycz.myreader.entity.Setting; -import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.enums.Font; +import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.greendao.DbManager; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.BookMark; @@ -92,26 +90,23 @@ import xyz.fycz.myreader.ui.popmenu.CustomizeComMenu; import xyz.fycz.myreader.ui.popmenu.CustomizeLayoutMenu; import xyz.fycz.myreader.ui.popmenu.ReadSettingMenu; import xyz.fycz.myreader.util.BrightUtil; -import xyz.fycz.myreader.util.help.DateHelper; import xyz.fycz.myreader.util.ShareUtils; import xyz.fycz.myreader.util.SharedPreUtils; -import xyz.fycz.myreader.util.help.StringHelper; import xyz.fycz.myreader.util.SystemUtil; import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.help.DateHelper; +import xyz.fycz.myreader.util.help.StringHelper; import xyz.fycz.myreader.util.notification.NotificationClickReceiver; import xyz.fycz.myreader.util.notification.NotificationUtil; import xyz.fycz.myreader.util.utils.ColorUtil; import xyz.fycz.myreader.util.utils.NetworkUtils; -import xyz.fycz.myreader.util.utils.RxUtils; import xyz.fycz.myreader.util.utils.ScreenUtils; import xyz.fycz.myreader.util.utils.StringUtils; import xyz.fycz.myreader.util.utils.SystemBarUtils; import xyz.fycz.myreader.webapi.BookApi; -import xyz.fycz.myreader.webapi.ResultCallback; import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.widget.BubblePopupView; -import xyz.fycz.myreader.widget.page.LocalPageLoader; import xyz.fycz.myreader.widget.page.PageLoader; import xyz.fycz.myreader.widget.page.PageMode; import xyz.fycz.myreader.widget.page.PageView; @@ -133,7 +128,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe /***************************variable*****************************/ private Book mBook; private List aBooks; - private ArrayList mChapters = new ArrayList<>(); + private List mChapters = new ArrayList<>(); private ChapterService mChapterService; private BookService mBookService; private BookMarkService mBookMarkService; @@ -212,55 +207,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe } }; - @SuppressLint("HandlerLeak") - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case 1: - init(); - break; - case 2: - try { - int chapterPos = msg.arg1; - int pagePos = msg.arg2; - mPageLoader.skipToChapter(chapterPos); - mPageLoader.skipToPage(pagePos); - } catch (Exception e) { - //ToastUtils.showError("章节跳转失败,请截图联系作者。\n" + e.getMessage()); - e.printStackTrace(); - } - break; - case 3: - break; - case 4: - saveLastChapterReadPosition(); - screenOffTimerStart(); - initMenu(); - break; - case 5: - if (mPageLoader != null) { - mPageLoader.refreshUi(); - } - break; - case 6: - mPageLoader.setmStatus(PageLoader.STATUS_LOADING); - break; - case 7: - ToastUtils.showWarring("无网络连接!"); - mPageLoader.chapterError("无网络连接!"); - break; - case 8: - binding.pbLoading.setVisibility(View.GONE); - break; - case 9: - ToastUtils.showInfo("正在后台缓存书籍,具体进度可查看通知栏!"); - notificationUtil.requestNotificationPermissionDialog(ReadActivity.this); - break; - } - } - }; - + private Handler mHandler = new Handler(); @Override protected void bindView() { @@ -335,6 +282,9 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe if (pagePos != -1 && chapterPos != -1) { mBook.setHisttoryChapterNum(chapterPos); mBook.setLastReadPosition(pagePos); + } else { + pagePos = mBook.getLastReadPosition(); + chapterPos = mBook.getHisttoryChapterNum(); } if (SharedPreUtils.getInstance().getBoolean(getString(R.string.isNightFS), false)) { mSetting.setDayStyle(!ColorUtil.isColorLight(getResources().getColor(R.color.textPrimary))); @@ -373,7 +323,6 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe if (!mSetting.isBrightFollowSystem()) { BrightUtil.setBrightness(this, mSetting.getBrightProgress()); } - binding.pbLoading.setVisibility(View.VISIBLE); initEyeView(); initSettingListener(); initTopMenu(); @@ -437,12 +386,26 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe new PageLoader.OnPageChangeListener() { @Override public void onChapterChange(int pos) { + chapterPos = pos; mBook.setHistoryChapterId(mChapters.get(pos).getTitle()); recordReadTime(); } @Override public void onCategoryFinish(List chapters) { + mChapters = chapters; + mBook.setNoReadNum(0); + mBook.setChapterTotalNum(chapters.size()); + mBook.setNewestChapterTitle(chapters.get(chapters.size() - 1).getTitle()); + if (hasChangeSource) { + mBookService.matchHistoryChapterPos(mBook, mChapters); + skipToChapterAndPage(mBook.getHisttoryChapterNum(), mBook.getLastReadPosition()); + hasChangeSource = false; + mChapterService.addChapters(chapters); + } + mBookService.updateEntity(mBook); + loadFinish = true; + invalidateOptionsMenu(); } @Override @@ -452,7 +415,10 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe @Override public void onPageChange(int pos, boolean resetRead) { - mHandler.sendMessage(mHandler.obtainMessage(4)); + pagePos = pos; + saveLastChapterReadPosition(); + screenOffTimerStart(); + initMenu(); recordReadTime(); if (ReadAloudService.running) { if (mPageLoader.hasChapterData(mChapters.get(mPageLoader.getChapterPos()))) { @@ -617,7 +583,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe } //保存最近阅读时间 mBook.setLastReadTime(DateHelper.getLongDate()); - getData(); + init(); } @@ -723,9 +689,6 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe autoPageStop(); } ReadAloudService.stop(this); - for (int i = 0; i < 9; i++) { - mHandler.removeMessages(i + 1); - } if (mPageLoader != null) { mPageLoader.closeBook(); mPageLoader = null; @@ -843,7 +806,9 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe boolean needRefresh = data.getBooleanExtra(APPCONST.RESULT_NEED_REFRESH, false); boolean upMenu = data.getBooleanExtra(APPCONST.RESULT_UP_MENU, false); if (needRefresh) { - mHandler.sendEmptyMessage(5); + if (mPageLoader != null) { + mPageLoader.refreshUi(); + } } if (upMenu) { initTopMenu(); @@ -904,19 +869,16 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe screenOffTimerStart(); mPageLoader.init(); mPageLoader.refreshChapterList(); - loadFinish = true; - invalidateOptionsMenu(); - mHandler.sendMessage(mHandler.obtainMessage(8)); } private void initMenu() { if (mChapters != null && mChapters.size() != 0) { - Chapter curChapter = mChapters.get(mPageLoader.getChapterPos()); + Chapter curChapter = mChapters.get(chapterPos); String url = curChapter.getUrl(); binding.tvChapterTitleTop.setText(curChapter.getTitle()); binding.tvChapterUrl.setText(StringHelper.isEmpty(url) ? curChapter.getId() : NetworkUtils.getAbsoluteURL(mReadCrawler.getNameSpace(), url)); - binding.readSbChapterProgress.setProgress(mPageLoader.getPagePos()); + binding.readSbChapterProgress.setProgress(pagePos); binding.readSbChapterProgress.setMax(mPageLoader.getAllPagePos() - 1); binding.readTvPageTip.setText(String.format("%s/%s", binding.readSbChapterProgress.getProgress() + 1, binding.readSbChapterProgress.getMax() + 1)); @@ -1023,85 +985,6 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe }, (dialog, which) -> dialog.dismiss()); } /************************章节相关*************************/ - /** - * 章节数据网络同步 - */ - private void getData() { - mChapters = (ArrayList) mChapterService.findBookAllChapterByBookId(mBook.getId()); - if (!isCollected || mChapters.size() == 0 || ("本地书籍".equals(mBook.getType()) && !ChapterService.isChapterCached(mBook.getId(), mChapters.get(0).getTitle()) - )) { - if ("本地书籍".equals(mBook.getType())) { - if (!new File(mBook.getChapterUrl()).exists()) { - ToastUtils.showWarring("书籍缓存为空且源文件不存在,书籍加载失败!"); - finish(); - return; - } - if (mChapters.size() != 0 && mChapters.get(0).getEnd() > 0) { - initChapters(); - return; - } - ((LocalPageLoader) mPageLoader).loadChapters(new ResultCallback() { - @Override - public void onFinish(Object o, int code) { - ArrayList chapters = (ArrayList) o; - mBook.setChapterTotalNum(chapters.size()); - mBook.setNewestChapterTitle(chapters.get(chapters.size() - 1).getTitle()); - mBookService.updateEntity(mBook); - if (mChapters.size() == 0) { - updateAllOldChapterData(chapters); - } - initChapters(); - mHandler.sendMessage(mHandler.obtainMessage(1)); - } - - @Override - public void onError(Exception e) { - e.printStackTrace(); - mChapters.clear(); - initChapters(); - mHandler.sendMessage(mHandler.obtainMessage(1)); - } - }); - } else { - mPageLoader.setmStatus(PageLoader.STATUS_LOADING_CHAPTER); - BookApi.getBookChapters(mBook, mReadCrawler).flatMap(chapters -> Observable.create(emitter -> { - updateAllOldChapterData(chapters); - initChapters(); - emitter.onNext(chapters); - emitter.onComplete(); - })).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver() { - - @Override - public void onNext(@NotNull Object o) { - mPageLoader.setmStatus(PageLoader.STATUS_LOADING); - } - - @Override - public void onError(Throwable e) { - mHandler.sendMessage(mHandler.obtainMessage(1)); - if (App.isDebug()) e.printStackTrace(); - } - }); - } - } else { - initChapters(); - } - } - - /** - * 更新所有章节 - * - * @param newChapters - */ - private void updateAllOldChapterData(List newChapters) { - for (Chapter newChapter : newChapters) { - newChapter.setId(StringHelper.getStringRandom(25)); - newChapter.setBookId(mBook.getId()); - mChapters.add(newChapter); -// mChapterService.addChapter(newChapters.get(j)); - } - mChapterService.addChapters(mChapters); - } /** * 初始化章节 @@ -1114,7 +997,6 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe } if (mChapters.size() == 0) { ToastUtils.showWarring("该书查询不到任何章节"); - mHandler.sendMessage(mHandler.obtainMessage(8)); } else { if (mBook.getHisttoryChapterNum() < 0) { mBook.setHisttoryChapterNum(0); @@ -1122,14 +1004,11 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe mBook.setHisttoryChapterNum(mChapters.size() - 1); } if ("本地书籍".equals(mBook.getType())) { - mHandler.sendMessage(mHandler.obtainMessage(1)); return; } if (hasChangeSource) { mBookService.matchHistoryChapterPos(mBook, mChapters); } - mHandler.sendMessage(mHandler.obtainMessage(1)); - mHandler.sendMessage(mHandler.obtainMessage(4)); } } @@ -1140,7 +1019,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe * @param chapterPos * @param pagePos */ - private void skipToChapterAndPage(final int chapterPos, final int pagePos) { + private void skipToChapterAndPage(int chapterPos, int pagePos) { mPageLoader.setPrev(false); if (StringHelper.isEmpty(mChapters.get(chapterPos).getContent())) { if ("本地书籍".equals(mBook.getType())) { @@ -1148,7 +1027,13 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe return; } } - mHandler.sendMessage(mHandler.obtainMessage(2, chapterPos, pagePos)); + try { + mPageLoader.skipToChapter(chapterPos); + mPageLoader.skipToPage(pagePos); + } catch (Exception e) { + //ToastUtils.showError("章节跳转失败,请截图联系作者。\n" + e.getMessage()); + e.printStackTrace(); + } } /** @@ -1156,11 +1041,12 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe */ public void saveLastChapterReadPosition() { if (!StringHelper.isEmpty(mBook.getId()) && mPageLoader.getPageStatus() == PageLoader.STATUS_FINISH) { - mBook.setLastReadPosition(mPageLoader.getPagePos()); - mBook.setHisttoryChapterNum(mPageLoader.getChapterPos()); + mBook.setLastReadPosition(pagePos); + mBook.setHisttoryChapterNum(chapterPos); mBookService.updateEntity(mBook); } } + /********************菜单相关*************************/ /** * 初始化顶部菜单 @@ -1663,7 +1549,8 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe } needCacheChapterNum = needDownloadChapters.size(); if (needCacheChapterNum > 0) { - mHandler.sendEmptyMessage(9); + ToastUtils.showInfo("正在后台缓存书籍,具体进度可查看通知栏!"); + notificationUtil.requestNotificationPermissionDialog(ReadActivity.this); mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval); } App.getApplication().newThread(() -> { 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 6688c7d..7d79f08 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 @@ -427,7 +427,7 @@ public class SearchBookActivity extends BaseActivity { public void onSuccess(@NotNull String s) { parseSuggestionList(s); if (mSuggestions.size() > 0) { - binding.tgSuggestBook.setTags(mSuggestions.subList(0, 5)); + binding.tgSuggestBook.setTags(mSuggestions.subList(0, mSuggestions.size() / 2)); } else { binding.llSuggestBooksView.setVisibility(View.GONE); } @@ -467,9 +467,9 @@ public class SearchBookActivity extends BaseActivity { if (mSuggestions.size() > 0) { String[] s = binding.tgSuggestBook.getTags(); if (s[0].equals(mSuggestions.get(0))) { - binding.tgSuggestBook.setTags(mSuggestions.subList(5, 10)); + binding.tgSuggestBook.setTags(mSuggestions.subList(mSuggestions.size() / 2, mSuggestions.size())); } else { - binding.tgSuggestBook.setTags(mSuggestions.subList(0, 5)); + binding.tgSuggestBook.setTags(mSuggestions.subList(0, mSuggestions.size() / 2)); } } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FileCategoryFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FileCategoryFragment.java index 60c3a27..a0e04d4 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FileCategoryFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FileCategoryFragment.java @@ -160,7 +160,7 @@ public class FileCategoryFragment extends BaseFileFragment { public static class SimpleFileFilter implements FileFilter { @Override public boolean accept(File pathname) { - if (pathname.getName().startsWith(".")){ + if (pathname.getName().startsWith(".")) { return false; } //文件夹内部数量为0 @@ -168,15 +168,11 @@ public class FileCategoryFragment extends BaseFileFragment { return false; } - /** - * 现在只支持TXT文件的显示 - */ //文件内容为空,或者不以txt为开头 - if (!pathname.isDirectory() && - (pathname.length() == 0 || !pathname.getName().endsWith(FileUtils.SUFFIX_TXT))){ - return false; - } - return true; + return pathname.isDirectory() || + (pathname.length() != 0 + && (pathname.getName().toLowerCase().endsWith(FileUtils.SUFFIX_TXT) + || pathname.getName().toLowerCase().endsWith(FileUtils.SUFFIX_EPUB))); } } } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java index e294f33..6d81ebe 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java @@ -1,5 +1,8 @@ package xyz.fycz.myreader.ui.fragment; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -7,12 +10,23 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import org.jetbrains.annotations.NotNull; + +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.base.BaseFragment; +import xyz.fycz.myreader.base.observer.MySingleObserver; import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.common.URLCONST; import xyz.fycz.myreader.databinding.FragmentFindBinding; +import xyz.fycz.myreader.entity.Quotation; import xyz.fycz.myreader.ui.activity.BookstoreActivity; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.GsonExtensionsKt; import xyz.fycz.myreader.util.utils.ImageLoader; +import xyz.fycz.myreader.util.utils.OkHttpUtils; +import xyz.fycz.myreader.util.utils.RxUtils; import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; import xyz.fycz.myreader.webapi.crawler.find.Ben100FindCrawler; import xyz.fycz.myreader.webapi.crawler.find.MiaoBiFindCrawler; @@ -26,6 +40,7 @@ import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; public class FindFragment extends BaseFragment { private FragmentFindBinding binding; + private Quotation quotation; @Override protected View bindView(LayoutInflater inflater, ViewGroup container) { @@ -33,9 +48,24 @@ public class FindFragment extends BaseFragment { return binding.getRoot(); } + @Override + public void onResume() { + super.onResume(); + getQuotation(); + } + @Override protected void initClick() { super.initClick(); + binding.findRlQuotation.setOnClickListener(v -> { + if (quotation == null) return; + ClipboardManager mClipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + //数据 + ClipData mClipData = ClipData.newPlainText("Label", quotation.toString()); + //把数据设置到剪切板上 + mClipboardManager.setPrimaryClip(mClipData); + ToastUtils.showSuccess("语录已复制到剪切板"); + }); binding.findRlQidianTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(false))); binding.findRlQidianNsTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(true))); //binding.findRlXs7Top.setOnClickListener(v -> comeToBookstore(new XS7Rank())); @@ -48,12 +78,26 @@ public class FindFragment extends BaseFragment { } - private void comeToBookstore(FindCrawler findCrawler){ + private void comeToBookstore(FindCrawler findCrawler) { Intent intent = new Intent(getContext(), BookstoreActivity.class); intent.putExtra(APPCONST.FIND_CRAWLER, findCrawler); startActivity(intent); } + private void getQuotation() { + Single.create((SingleOnSubscribe) emitter -> { + String json = OkHttpUtils.getHtml(URLCONST.QUOTATION); + emitter.onSuccess(GsonExtensionsKt.getGSON().fromJson(json, Quotation.class)); + }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() { + @Override + public void onSuccess(@NotNull Quotation q) { + quotation = q; + binding.tvQuotation.setText(q.getHitokoto()); + binding.tvFrom.setText(String.format("--- %s", q.getFrom())); + } + }); + } + public boolean isRecreate() { return binding == null; } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalBookFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalBookFragment.java index a9717bb..d45d4cf 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalBookFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalBookFragment.java @@ -72,7 +72,7 @@ public class LocalBookFragment extends BaseFileFragment { //更新媒体库 try { MediaScannerConnection.scanFile(getContext(), new String[]{Environment - .getExternalStorageDirectory().getAbsolutePath()}, new String[]{"text/plain"}, null); + .getExternalStorageDirectory().getAbsolutePath()}, new String[]{"text/plain", "application/epub+zip"}, null); }catch (Exception e){ e.printStackTrace(); } diff --git a/app/src/main/java/xyz/fycz/myreader/util/media/LocalFileLoader.java b/app/src/main/java/xyz/fycz/myreader/util/media/LocalFileLoader.java index 6a9e357..585a53e 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/media/LocalFileLoader.java +++ b/app/src/main/java/xyz/fycz/myreader/util/media/LocalFileLoader.java @@ -4,8 +4,8 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; - import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.loader.content.CursorLoader; @@ -22,8 +22,8 @@ public class LocalFileLoader extends CursorLoader { private static final String TAG = "LocalFileLoader"; private static final Uri FILE_URI = Uri.parse("content://media/external/file"); - private static final String SELECTION = MediaStore.Files.FileColumns.DATA + " like ?"; - private static final String SEARCH_TYPE = "%.txt"; + private static final String SELECTION = MediaStore.Files.FileColumns.DATA + " like ? or " + MediaStore.Files.FileColumns.DATA + " like ?"; + private static final String[] SEARCH_TYPE = new String[]{"%.txt", "%.epub"}; private static final String SORT_ORDER = MediaStore.Files.FileColumns.DISPLAY_NAME + " DESC"; private static final String[] FILE_PROJECTION = { MediaStore.Files.FileColumns.DATA, @@ -42,7 +42,7 @@ public class LocalFileLoader extends CursorLoader { setUri(FILE_URI); setProjection(FILE_PROJECTION); setSelection(SELECTION); - setSelectionArgs(new String[]{SEARCH_TYPE}); + setSelectionArgs(SEARCH_TYPE); setSortOrder(SORT_ORDER); } @@ -50,7 +50,6 @@ public class LocalFileLoader extends CursorLoader { List files = new ArrayList<>(); // 判断是否存在数据 if (cursor == null) { - // TODO:当媒体库没有数据的时候,需要做相应的处理 // 暂时直接返回空数据 resultCallback.onResultCallback(files); return; @@ -62,14 +61,9 @@ public class LocalFileLoader extends CursorLoader { path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); // 路径无效 - if (TextUtils.isEmpty(path)) { - continue; - } else { + if (!TextUtils.isEmpty(path)) { File file = new File(path); - if (file.isDirectory() || !file.exists()){ - continue; - } - else { + if (!file.isDirectory() && file.exists() && file.length() > 1024) { files.add(file); } } diff --git a/app/src/main/java/xyz/fycz/myreader/util/utils/AdUtils.java b/app/src/main/java/xyz/fycz/myreader/util/utils/AdUtils.java index 5497856..b60165c 100644 --- a/app/src/main/java/xyz/fycz/myreader/util/utils/AdUtils.java +++ b/app/src/main/java/xyz/fycz/myreader/util/utils/AdUtils.java @@ -123,10 +123,10 @@ public class AdUtils { public static void initAd() { if (!hasInitAd) { hasInitAd = true; - DdSdkHelper.init("", "", "", - "", "", - "", "", - App.getApplication(), App.isDebug()); + DdSdkHelper.init("1234", "216", "51716a16fbdf50905704b6575b1b3b60", + "142364", "35ce0efe5f3cc960b116db227498e238", + "8167", "85bd159309c3da1b", + App.getApplication(), true); } } } 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 9eab6e8..dc4436f 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 @@ -451,11 +451,7 @@ public class FileUtils { } catch (Exception e) { e.printStackTrace(); } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } + IOUtils.close(out); } return writeSucc; diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java b/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java deleted file mode 100644 index 16d5281..0000000 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/BookChapterBean.java +++ /dev/null @@ -1,56 +0,0 @@ -package xyz.fycz.myreader.widget.page; - - -public class BookChapterBean { - - //章节所属的小说(网络) - public String bookId; - //章节名 - public String title; - - //章节内容在文章中的起始位置(本地) - public long start; - //章节内容在文章中的终止位置(本地) - public long end; - - public String getBookId() { - return bookId; - } - - public void setBookId(String id) { - this.bookId = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public long getStart() { - return start; - } - - public void setStart(long start) { - this.start = start; - } - - public long getEnd() { - return end; - } - - public void setEnd(long end) { - this.end = end; - } - - @Override - public String toString() { - return "BookChapterBean{" + - "title='" + title + '\'' + - ", start=" + start + - ", end=" + end + - '}'; - } -} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/EpubPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/EpubPageLoader.java new file mode 100644 index 0000000..dc89a16 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/EpubPageLoader.java @@ -0,0 +1,328 @@ +package xyz.fycz.myreader.widget.page; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.text.TextUtils; +import android.util.Log; + + +import net.sf.jazzlib.ZipFile; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.Elements; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.ObservableOnSubscribe; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import nl.siegmann.epublib.domain.Book; +import nl.siegmann.epublib.domain.MediaType; +import nl.siegmann.epublib.domain.Metadata; +import nl.siegmann.epublib.domain.Resource; +import nl.siegmann.epublib.domain.SpineReference; +import nl.siegmann.epublib.domain.TOCReference; +import nl.siegmann.epublib.epub.EpubReader; +import nl.siegmann.epublib.service.MediatypeService; +import xyz.fycz.myreader.base.observer.MyObserver; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.entity.Setting; +import xyz.fycz.myreader.greendao.entity.Chapter; +import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.util.help.StringHelper; +import xyz.fycz.myreader.util.utils.FileUtils; +import xyz.fycz.myreader.util.utils.StringUtils; + +public class EpubPageLoader extends PageLoader { + + //编码类型 + private Charset mCharset; + + private Book epubBook; + + EpubPageLoader(PageView pageView, xyz.fycz.myreader.greendao.entity.Book bookShelfBean, Setting setting) { + super(pageView, bookShelfBean, setting); + } + + @Override + public void refreshChapterList() { + if (mCollBook == null) return; + + Observable.create((ObservableOnSubscribe) e -> { + File bookFile = new File(mCollBook.getChapterUrl()); + epubBook = readBook(bookFile); + + if (epubBook == null) { + e.onError(new Exception("文件解析失败")); + return; + } + if (TextUtils.isEmpty(mCollBook.getInfoUrl())) { + mCollBook.setInfoUrl("UTF-8"); + } + mCharset = Charset.forName(mCollBook.getInfoUrl()); + + if (TextUtils.isEmpty(mCollBook.getImgUrl())){ + saveCover(); + } + e.onNext(mCollBook); + e.onComplete(); + }).subscribeOn(Schedulers.single()) + .flatMap(this::checkChapterList) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + mChapterDis = d; + } + + @Override + public void onNext(xyz.fycz.myreader.greendao.entity.Book bookShelfBean) { + isChapterListPrepare = true; + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Throwable e) { + chapterError(e.getMessage()); + } + }); + } + + + public static Book readBook(File file) { + try { + EpubReader epubReader = new EpubReader(); + MediaType[] lazyTypes = { + MediatypeService.CSS, + MediatypeService.GIF, + MediatypeService.JPG, + MediatypeService.PNG, + MediatypeService.MP3, + MediatypeService.MP4}; + ZipFile zipFile = new ZipFile(file); + return epubReader.readEpubLazy(zipFile, "utf-8", Arrays.asList(lazyTypes)); + } catch (Exception e) { + return null; + } + } + + private void saveCover() throws IOException { + byte[] data = epubBook.getCoverImage().getData(); + FileUtils.writeFile(data, APPCONST.COVER_DIR, mCollBook.getId() + ".jpg"); + mCollBook.setImgUrl(APPCONST.COVER_DIR + mCollBook.getId() + ".jpg"); + } + + /*private void extractScaledCoverImage() { + try { + byte[] data = epubBook.getCoverImage().getData(); + Bitmap rawCover = BitmapFactory.decodeByteArray(data, 0, data.length); + int width = rawCover.getWidth(); + int height = rawCover.getHeight(); + if (width <= mVisibleWidth && width >= 0.8 * mVisibleWidth) { + cover = rawCover; + return; + } + height = Math.round(mVisibleWidth * 1.0f * height / width); + cover = Bitmap.createScaledBitmap(rawCover, mVisibleWidth, height, true); + } catch (Exception ignored) { + } + } + + @Override + public void drawCover(Canvas canvas, float top) { + if (cover == null) { + extractScaledCoverImage(); + } + if (cover == null) + return; + int left = (mDisplayWidth - cover.getWidth()) / 2; + canvas.drawBitmap(cover, left, top, mTextPaint); + }*/ + + private List loadChapters() { + Metadata metadata = epubBook.getMetadata(); + mCollBook.setName(metadata.getFirstTitle()); + if (metadata.getAuthors().size() > 0) { + String author = metadata.getAuthors().get(0).toString().replaceAll("^, |, $", ""); + mCollBook.setAuthor(author); + } + if (metadata.getDescriptions().size() > 0) { + mCollBook.setDesc(Jsoup.parse(metadata.getDescriptions().get(0)).text()); + } + mChapterList = new ArrayList<>(); + List refs = epubBook.getTableOfContents().getTocReferences(); + if (refs == null || refs.isEmpty()) { + List spineReferences = epubBook.getSpine().getSpineReferences(); + for (int i = 0, size = spineReferences.size(); i < size; i++) { + Resource resource = spineReferences.get(i).getResource(); + String title = resource.getTitle(); + if (TextUtils.isEmpty(title)) { + try { + Document doc = Jsoup.parse(new String(resource.getData(), mCharset)); + Elements elements = doc.getElementsByTag("title"); + if (elements.size() > 0) { + title = elements.get(0).text(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + Chapter bean = new Chapter(); + bean.setNumber(i); + bean.setBookId(mCollBook.getId()); + bean.setUrl(resource.getHref()); + if (i == 0 && title.isEmpty()) { + bean.setTitle("封面"); + } else { + bean.setTitle(title); + } + mChapterList.add(bean); + } + } else { + parseMenu(refs, 0); + for (int i = 0; i < mChapterList.size(); i++) { + mChapterList.get(i).setNumber(i); + } + } + + return mChapterList; + } + + private void parseMenu(List refs, int level) { + if (refs == null) return; + for (TOCReference ref : refs) { + if (ref.getResource() != null) { + Chapter chapter = new Chapter(); + chapter.setBookId(mCollBook.getId()); + chapter.setTitle(ref.getTitle()); + chapter.setUrl(ref.getCompleteHref()); + chapter.setId(StringHelper.getStringRandom(15)); + mChapterList.add(chapter); + } + if (ref.getChildren() != null && !ref.getChildren().isEmpty()) { + parseMenu(ref.getChildren(), level + 1); + } + } + } + + protected String getChapterContent(Chapter chapter) throws Exception { + Resource resource = epubBook.getResources().getByHref(chapter.getUrl()); + StringBuilder content = new StringBuilder(); + Document doc = Jsoup.parse(new String(resource.getData(), mCharset)); + Elements elements = doc.getAllElements(); + for (Element element : elements) { + List contentEs = element.textNodes(); + for (int i = 0; i < contentEs.size(); i++) { + String text = contentEs.get(i).text().trim(); + text = StringUtils.formatHtml(text); + if (elements.size() > 1) { + if (text.length() > 0) { + if (content.length() > 0) { + content.append("\r\n"); + } + content.append("\u3000\u3000").append(text); + } + } else { + content.append(text); + } + } + } + return content.toString(); + } + + private Observable checkChapterList(xyz.fycz.myreader.greendao.entity.Book collBook) { + if (mCollBook.getChapterTotalNum() != 0) { + mChapterList = ChapterService.getInstance().findBookAllChapterByBookId(mCollBook.getId()); + mPageChangeListener.onCategoryFinish(mChapterList); + return Observable.just(collBook); + } else { + return Observable.create((ObservableOnSubscribe>) e -> { + List chapterList = loadChapters(); + if (!chapterList.isEmpty()) { + e.onNext(chapterList); + } else { + e.onError(new IllegalAccessException("epubBook sub-chapter failed!")); + } + e.onComplete(); + }) + .flatMap(chapterList -> { + mPageChangeListener.onCategoryFinish(chapterList); + return Observable.just(collBook); + }) + .doOnNext(bookShelfBean -> { + // 存储章节到数据库 + ChapterService.getInstance().addChapters(mChapterList); + }); + } + } + + @Override + protected BufferedReader getChapterReader(Chapter chapter) throws Exception { + Log.d("getChapterReader", chapter.getTitle()); + byte[] content = getChapterContent(chapter).getBytes(); + ByteArrayInputStream bais = new ByteArrayInputStream(content); + BufferedReader br = new BufferedReader(new InputStreamReader(bais)); + return br; + } + + @Override + public boolean hasChapterData(Chapter chapter) { + return true; + } + + /*public void updateChapter() { + mPageView.getActivity().toast("目录更新中"); + Observable.create((ObservableOnSubscribe) e -> { + if (TextUtils.isEmpty(book.getBookInfoBean().getCharset())) { + book.getBookInfoBean().setCharset("UTF-8"); + } + mCharset = Charset.forName(book.getBookInfoBean().getCharset()); + //清除原目录 + BookshelfHelp.delChapterList(book.getNoteUrl()); + callback.getChapterList().clear(); + e.onNext(book); + e.onComplete(); + }).flatMap(this::checkChapterList) + .compose(RxUtils::toSimpleSingle) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + compositeDisposable.add(d); + } + + @Override + public void onNext(BookShelfBean bookShelfBean) { + mPageView.getActivity().toast("更新完成"); + isChapterListPrepare = true; + // 加载并显示当前章节 + skipToChapter(bookShelfBean.getDurChapter(), bookShelfBean.getDurChapterPage()); + } + + @Override + public void onError(Throwable e) { + durDhapterError(e.getMessage()); + } + + @Override + public void onComplete() { + + } + }); + }*/ +} 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 e42659e..f4c750f 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 @@ -1,6 +1,7 @@ package xyz.fycz.myreader.widget.page; import android.util.Log; + import io.reactivex.*; import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.webapi.ResultCallback; @@ -55,8 +56,6 @@ public class LocalPageLoader extends PageLoader { //编码类型 private String mCharset; - private Disposable mChapterDisp = null; - private ChapterService mChapterService; public LocalPageLoader(PageView pageView, Book collBook, ChapterService mChapterService, Setting setting) { @@ -65,6 +64,61 @@ public class LocalPageLoader extends PageLoader { this.mChapterService = mChapterService; } + @Override + public void refreshChapterList() { + // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。 + mBookFile = new File(mCollBook.getChapterUrl()); + + mCharset = mCollBook.getInfoUrl(); + + // 判断文件是否已经加载过,并具有缓存 + if (mCollBook.getChapterTotalNum() != 0) { + + mChapterList = mChapterService.findBookAllChapterByBookId(mCollBook.getId()); + isChapterListPrepare = true; + + //提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + + // 加载并显示当前章节 + openChapter(); + + return; + } + // 通过RxJava异步处理分章事件 + Single.create((SingleOnSubscribe>) e -> { + e.onSuccess(loadChapters()); + }).compose(RxUtils::toSimpleSingle).subscribe(new SingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + mChapterDis = d; + } + + @Override + public void onSuccess(List chapters) { + mChapterDis = null; + isChapterListPrepare = true; + mCollBook.setInfoUrl(mCharset); + mChapterList = chapters; + mChapterService.addChapters(mChapterList); + //提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Throwable e) { + chapterError(e.getLocalizedMessage()); + Log.d(TAG, "file load error:" + e.toString()); + } + }); + } + /** * 未完成的部分: * 1. 序章的添加 @@ -135,7 +189,7 @@ public class LocalPageLoader extends PageLoader { //如果序章大小大于500才添加进去 if (preChapter.getEnd() - preChapter.getStart() > 500) { mChapterList.add(preChapter); - }else { + } else { //加入简介 mCollBook.setDesc(chapterContent); } @@ -259,35 +313,6 @@ public class LocalPageLoader extends PageLoader { return mChapterList; } - 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; - mCollBook.setInfoUrl(mCharset); - if (resultCallback != null) { - resultCallback.onFinish(chapters, 1); - } - } - - @Override - public void onError(Throwable e) { - resultCallback.onError((Exception) e); - chapterError(e.getLocalizedMessage()); - Log.d(TAG, "file load error:" + e.toString()); - } - }); - - } /** * 从文件中提取一章的内容 @@ -341,39 +366,6 @@ public class LocalPageLoader extends PageLoader { } - @Override - public void closeBook() { - super.closeBook(); - if (mChapterDisp != null) { - mChapterDisp.dispose(); - mChapterDisp = null; - } - } - - @Override - public void refreshChapterList() { - // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。 - mBookFile = new File(mCollBook.getChapterUrl()); - - mCharset = mCollBook.getInfoUrl(); - - // 判断文件是否已经加载过,并具有缓存 - if (mCollBook.getChapterTotalNum() != 0) { - - mChapterList = mChapterService.findBookAllChapterByBookId(mCollBook.getId()); - isChapterListPrepare = true; - - //提示目录加载完成 - if (mPageChangeListener != null) { - mPageChangeListener.onCategoryFinish(mChapterList); - } - - // 加载并显示当前章节 - openChapter(); - - return; - } - } @Override protected BufferedReader getChapterReader(Chapter chapter) throws Exception { diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java index 6c69aa7..2ff4916 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java @@ -1,6 +1,8 @@ package xyz.fycz.myreader.widget.page; +import android.util.Log; + import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; @@ -12,6 +14,9 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.base.observer.MyObserver; import xyz.fycz.myreader.common.APPCONST; @@ -38,38 +43,61 @@ public class NetPageLoader extends PageLoader { this.mReadCrawler = mReadCrawler; } - /*private List convertTxtChapter(List bookChapters) { - List txtChapters = new ArrayList<>(bookChapters.size()); - for (Chapter bean : bookChapters) { - BookChapterBean chapter = new BookChapterBean(); - chapter.setBookId(bean.getBookId()); - chapter.setTitle(bean.getTitle()); - chapter.setLink(bean.getUrl()); - txtChapters.add(chapter); - } - return txtChapters; - }*/ @Override public void refreshChapterList() { List chapters = mChapterService.findBookAllChapterByBookId(mCollBook.getId()); - if (chapters == null) return; + if (chapters != null && !chapters.isEmpty()) { + mChapterList = chapters; + isChapterListPrepare = true; - // 将 BookChapter 转换成当前可用的 Chapter -// mChapterList = convertTxtChapter(chapters); - mChapterList = chapters; - isChapterListPrepare = true; + // 目录加载完成,执行回调操作。 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } - // 目录加载完成,执行回调操作。 - if (mPageChangeListener != null) { - mPageChangeListener.onCategoryFinish(mChapterList); + // 如果章节未打开 + if (!isChapterOpen()) { + // 打开章节 + openChapter(); + } + return; } + mStatus = STATUS_LOADING_CHAPTER; + BookApi.getBookChapters(mCollBook, mReadCrawler) + .flatMap((Function, ObservableSource>>) newChapters -> Observable.create(emitter -> { + for (Chapter chapter : newChapters){ + chapter.setId(StringHelper.getStringRandom(25)); + chapter.setBookId(mCollBook.getId()); + } + emitter.onNext(newChapters); + })) + .compose(RxUtils::toSimpleSingle) + .subscribe(new MyObserver>() { + @Override + public void onSubscribe(Disposable d) { + mChapterDis = d; + } - // 如果章节未打开 - if (!isChapterOpen()) { - // 打开章节 - openChapter(); - } + @Override + public void onNext(@NotNull List chapters) { + mChapterDis = null; + isChapterListPrepare = true; + mChapterList = chapters; + //提示目录加载完成 + if (mPageChangeListener != null) { + mPageChangeListener.onCategoryFinish(mChapterList); + } + // 加载并显示当前章节 + openChapter(); + } + + @Override + public void onError(Throwable e) { + chapterError(e.getLocalizedMessage()); + Log.d(TAG, "file load error:" + e.toString()); + } + }); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java index 1195aa3..256efd1 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java @@ -127,11 +127,11 @@ public abstract class PageLoader { // 加载器的颜色主题 // private PageStyle mPageStyle; //书籍绘制区域的宽高 - private int mVisibleWidth; - private int mVisibleHeight; + protected int mVisibleWidth; + protected int mVisibleHeight; //应用的宽高 - private int mDisplayWidth; - private int mDisplayHeight; + protected int mDisplayWidth; + protected int mDisplayHeight; //间距 private int mMarginTop; private int mMarginBottom; @@ -169,6 +169,8 @@ public abstract class PageLoader { private Bitmap bgBitmap; private ChapterContentHelp contentHelper = new ChapterContentHelp(); + protected Disposable mChapterDis = null; + public void resetReadAloudParagraph() { readAloudParagraph = -1; } @@ -806,6 +808,10 @@ public abstract class PageLoader { mNextChapter = null; mPageView = null; mCurPage = null; + if (mChapterDis != null) { + mChapterDis.dispose(); + mChapterDis = null; + } } private void clearList(List list) { @@ -1503,7 +1509,7 @@ public abstract class PageLoader { if (mPageChangeListener != null) { readAloudParagraph = -1; mPageChangeListener.onChapterChange(mCurChapterPos); - mPageChangeListener.onPageChange(0, resetReadAloud); + mPageChangeListener.onPageChange(mCollBook.getLastReadPosition(), resetReadAloud); resetReadAloud = true; mPageChangeListener.onPageCountChange(mCurChapter != null ? mCurChapter.getPageSize() : 0); } diff --git a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java index bc56e26..4e70e1a 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/page/PageView.java @@ -22,6 +22,7 @@ import xyz.fycz.myreader.application.SysManager; import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.service.ChapterService; +import xyz.fycz.myreader.util.utils.FileUtils; import xyz.fycz.myreader.util.utils.SnackbarUtils; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.widget.animation.*; @@ -493,7 +494,11 @@ public class PageView extends View { } // 获取具体的加载器 if ("本地书籍".equals(collBook.getType())) { - mPageLoader = new LocalPageLoader(this, collBook, ChapterService.getInstance(), setting); + if (collBook.getChapterUrl().endsWith(FileUtils.SUFFIX_EPUB)){ + mPageLoader = new EpubPageLoader(this, collBook, setting); + }else { + mPageLoader = new LocalPageLoader(this, collBook, ChapterService.getInstance(), setting); + } } else { mPageLoader = new NetPageLoader(this, collBook, ChapterService.getInstance(), mReadCrawler, setting); } diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml index 2d249dd..4c57af4 100644 --- a/app/src/main/res/drawable/ic_edit.xml +++ b/app/src/main/res/drawable/ic_edit.xml @@ -6,9 +6,9 @@ android:viewportHeight="24"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_quotations.xml b/app/src/main/res/drawable/ic_quotations.xml new file mode 100644 index 0000000..10b33a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_quotations.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/menu_book_detail.xml b/app/src/main/res/drawable/shape_runway.xml similarity index 100% rename from app/src/main/res/drawable/menu_book_detail.xml rename to app/src/main/res/drawable/shape_runway.xml diff --git a/app/src/main/res/layout/activity_ad_setting.xml b/app/src/main/res/layout/activity_ad_setting.xml index 56d87c0..8cf96d0 100644 --- a/app/src/main/res/layout/activity_ad_setting.xml +++ b/app/src/main/res/layout/activity_ad_setting.xml @@ -145,6 +145,16 @@ android:text="@string/book_detail_ad" android:textColor="@color/textPrimary" /> + + + - - - + + + android:orientation="vertical"> - + + + + + + - + + + android:layout_height="wrap_content" + android:gravity="end" + android:text="--- 网易云" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_small_size" /> + + + + + + android:id="@+id/iv_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_leaderboard" /> + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="15dp" + android:layout_toEndOf="@id/iv_top" + android:text="排行榜" + android:textColor="@color/textPrimary" + android:textSize="@dimen/text_small_size" /> + + android:id="@+id/find_rl_qidian_top" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@drawable/selector_common_bg" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:text="@string/find_qidian" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_normal_size" /> + android:id="@+id/find_rl_qidian_ns_top" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@drawable/selector_common_bg" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:text="@string/find_qidian_ns" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_normal_size" /> @@ -94,142 +149,145 @@ --> + android:layout_width="match_parent" + android:layout_height="35dp" + android:clickable="false" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:id="@+id/iv_sort" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_sort" /> + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="15dp" + android:layout_toEndOf="@id/iv_sort" + android:text="分类" + android:textColor="@color/textPrimary" + android:textSize="@dimen/text_small_size" /> + android:id="@+id/find_rl_qidian_sort" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@drawable/selector_common_bg" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:text="@string/find_qidian" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_normal_size" /> + android:id="@+id/find_rl_qidian_ns_sort" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@drawable/selector_common_bg" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:text="@string/find_qidian_ns" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_normal_size" /> + android:layout_width="match_parent" + android:layout_height="35dp" + android:clickable="false" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:id="@+id/iv_store" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_bookstore" /> + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="15dp" + android:layout_toEndOf="@id/iv_store" + android:text="书城" + android:textColor="@color/textPrimary" + android:textSize="@dimen/text_small_size" /> + + android:textSize="@dimen/text_normal_size" /> + + android:id="@+id/find_rl_qb5_store" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@drawable/selector_common_bg" + android:paddingLeft="20dp" + android:paddingRight="20dp"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:text="@string/read_qb5" + android:textColor="@color/textSecondary" + android:textSize="@dimen/text_normal_size" /> + + android:paddingRight="20dp"> + android:textSize="@dimen/text_normal_size" /> diff --git a/app/src/main/res/layout/menu_book.xml b/app/src/main/res/layout/menu_book.xml index 04960cc..07cad59 100644 --- a/app/src/main/res/layout/menu_book.xml +++ b/app/src/main/res/layout/menu_book.xml @@ -66,7 +66,7 @@ android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="5dp" - android:background="@drawable/menu_book_detail" + android:background="@drawable/shape_runway" android:paddingHorizontal="10dp" android:paddingVertical="5dp" android:text="详情" diff --git a/app/src/main/res/layout/menu_book_local.xml b/app/src/main/res/layout/menu_book_local.xml index 5de9f63..22e0a69 100644 --- a/app/src/main/res/layout/menu_book_local.xml +++ b/app/src/main/res/layout/menu_book_local.xml @@ -66,7 +66,7 @@ android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="5dp" - android:background="@drawable/menu_book_detail" + android:background="@drawable/shape_runway" android:paddingHorizontal="10dp" android:paddingVertical="5dp" android:text="详情"