diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2ef7967..8bded9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -238,6 +238,8 @@ dependencies { exclude group: 'org.slf4j' exclude group: 'xmlpull' } + + implementation 'com.nshmura:recyclertablayout:1.5.0' } greendao { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 64f9cdd..4a886b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -118,6 +118,7 @@ + diff --git a/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java b/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java index 88876d0..34def62 100644 --- a/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java @@ -47,7 +47,7 @@ public abstract class BaseActivity extends SwipeBackActivity { protected abstract void bindView(); /************************init area************************************/ - protected void addDisposable(Disposable d) { + public void addDisposable(Disposable d) { if (mDisposable == null) { mDisposable = new CompositeDisposable(); } diff --git a/app/src/main/java/xyz/fycz/myreader/base/LazyFragment.kt b/app/src/main/java/xyz/fycz/myreader/base/LazyFragment.kt new file mode 100644 index 0000000..6af8dfa --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/base/LazyFragment.kt @@ -0,0 +1,86 @@ +package xyz.fycz.myreader.base + +import androidx.fragment.app.Fragment +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +/** + * @author fengyue + * @date 2021/7/22 11:46 + */ +abstract class LazyFragment : Fragment() { + protected var mDisposable: CompositeDisposable? = null + /** + * 是否执行懒加载 + */ + private var isLoaded = false + + /** + * 当前Fragment是否对用户可见 + */ + private var isVisibleToUser = false + + /** + * 当使用ViewPager+Fragment形式会调用该方法时,setUserVisibleHint会优先Fragment生命周期函数调用, + * 所以这个时候就,会导致在setUserVisibleHint方法执行时就执行了懒加载, + * 而不是在onResume方法实际调用的时候执行懒加载。所以需要这个变量 + */ + private var isCallResume = false + + /** + * 是否调用了setUserVisibleHint方法。处理show+add+hide模式下,默认可见 Fragment 不调用 + * onHiddenChanged 方法,进而不执行懒加载方法的问题。 + */ + private var isCallUserVisibleHint = false + + protected open fun addDisposable(d: Disposable?) { + if (mDisposable == null) { + mDisposable = CompositeDisposable() + } + mDisposable!!.add(d!!) + } + + override fun onResume() { + super.onResume() + isCallResume = true + if (!isCallUserVisibleHint) isVisibleToUser = !isHidden + judgeLazyInit() + } + + + private fun judgeLazyInit() { + if (!isLoaded && isVisibleToUser && isCallResume) { + lazyInit() + isLoaded = true + } + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + isVisibleToUser = !hidden + judgeLazyInit() + } + + override fun onDestroyView() { + super.onDestroyView() + isLoaded = false + isVisibleToUser = false + isCallUserVisibleHint = false + isCallResume = false + } + + override fun setUserVisibleHint(isVisibleToUser: Boolean) { + super.setUserVisibleHint(isVisibleToUser) + this.isVisibleToUser = isVisibleToUser + isCallUserVisibleHint = true + judgeLazyInit() + } + + override fun onDetach() { + super.onDetach() + if (mDisposable != null) { + mDisposable!!.clear() + } + } + abstract fun lazyInit() +} \ No newline at end of file diff --git a/app/src/main/java/xyz/fycz/myreader/entity/FindKind.java b/app/src/main/java/xyz/fycz/myreader/entity/FindKind.java new file mode 100644 index 0000000..03b2797 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/entity/FindKind.java @@ -0,0 +1,45 @@ +package xyz.fycz.myreader.entity; + +/** + * @author fengyue + * @date 2021/7/21 20:44 + */ +public class FindKind { + private String tag; + private String name; + private String url; + private int maxPage; + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getMaxPage() { + return maxPage; + } + + public void setMaxPage(int maxPage) { + this.maxPage = maxPage; + } +} + diff --git a/app/src/main/java/xyz/fycz/myreader/entity/StrResponse.java b/app/src/main/java/xyz/fycz/myreader/entity/StrResponse.java index 9ecec56..dad38bd 100644 --- a/app/src/main/java/xyz/fycz/myreader/entity/StrResponse.java +++ b/app/src/main/java/xyz/fycz/myreader/entity/StrResponse.java @@ -46,38 +46,42 @@ public class StrResponse { public String body() { if (body == null) { - ResponseBody body = response.body(); - if (body != null) { - try { - byte[] responseBytes = UTF8BOMFighter.removeUTF8BOM(body.bytes()); - if (!TextUtils.isEmpty(encodeType)) { - try { - this.body = new String((responseBytes), Charset.forName(encodeType)); - Log.d("Http: read finish", this.body); - return this.body; - } catch (Exception ignored) { + if (response != null) { + ResponseBody body = response.body(); + if (body != null) { + try { + byte[] responseBytes = UTF8BOMFighter.removeUTF8BOM(body.bytes()); + if (!TextUtils.isEmpty(encodeType)) { + try { + this.body = new String((responseBytes), Charset.forName(encodeType)); + Log.d("Http: read finish", this.body); + return this.body; + } catch (Exception ignored) { + } } - } - String charsetStr; - MediaType mediaType = body.contentType(); - //根据http头判断 - if (mediaType != null) { - Charset charset = mediaType.charset(); - if (charset != null) { - this.body = new String((responseBytes), charset); - Log.d("Http: read finish", this.body); - return this.body; + String charsetStr; + MediaType mediaType = body.contentType(); + //根据http头判断 + if (mediaType != null) { + Charset charset = mediaType.charset(); + if (charset != null) { + this.body = new String((responseBytes), charset); + Log.d("Http: read finish", this.body); + return this.body; + } } + //根据内容判断 + charsetStr = EncodingDetect.getEncodeInHtml(responseBytes); + this.body = new String(responseBytes, Charset.forName(charsetStr)); + Log.d("Http: read finish", this.body); + return this.body; + } catch (IOException e) { + e.printStackTrace(); + this.body = ""; } - //根据内容判断 - charsetStr = EncodingDetect.getEncodeInHtml(responseBytes); - this.body = new String(responseBytes, Charset.forName(charsetStr)); - Log.d("Http: read finish", this.body); - return this.body; - } catch (IOException e) { - e.printStackTrace(); - this.body = ""; } + } else { + this.body = ""; } } return this.body; diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java index 0b191e5..b3ec45a 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java @@ -86,7 +86,7 @@ public class AboutActivity extends BaseActivity { binding.il.rlOfficialWeb.setOnClickListener(v -> { MyAlertDialog.showFullWebViewDia(this, URLCONST.OFFICIAL_WEB, - true, null); + true, null); }); binding.il.rlGit.setOnClickListener(v -> { MyAlertDialog.showFullWebViewDia(this, getString(R.string.this_github_url), @@ -153,6 +153,7 @@ public class AboutActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { disposable[0] = d; + addDisposable(d); } @Override @@ -191,6 +192,11 @@ public class AboutActivity extends BaseActivity { } emitter.onComplete(); }).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } + @Override public void onNext(@NotNull File file) { ShareUtils.share(AboutActivity.this, file, "分享日志文件", "application/x-zip-compressed"); 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 3f416d0..733270d 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 @@ -77,6 +77,7 @@ public class AdSettingActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { cancelDis = d; + addDisposable(d); } @Override @@ -116,6 +117,7 @@ public class AdSettingActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { cancelDis = d; + addDisposable(d); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java index 920908c..cacc830 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java @@ -372,6 +372,11 @@ public class BookDetailedActivity extends BaseActivity { binding.pbLoading.setVisibility(View.VISIBLE); BookInfoCrawler bic = (BookInfoCrawler) rc; BookApi.getBookInfo(mBook, bic).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } + @Override public void onNext(@NotNull Book book) { if (!App.isDestroy(BookDetailedActivity.this)) { @@ -438,6 +443,7 @@ public class BookDetailedActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { chaptersDis = d; + addDisposable(d); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java index 9e75a69..03e41c4 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java @@ -36,7 +36,7 @@ import xyz.fycz.myreader.util.SharedPreUtils; import xyz.fycz.myreader.util.ToastUtils; import xyz.fycz.myreader.webapi.BookStoreApi; import xyz.fycz.myreader.webapi.ResultCallback; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; /** @@ -46,7 +46,7 @@ import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; public class BookstoreActivity extends BaseActivity { private ActiityBookstoreBinding binding; - private FindCrawler findCrawler; + private FindCrawler3 findCrawler3; private LinearLayoutManager mLinearLayoutManager; private BookStoreBookTypeAdapter mBookStoreBookTypeAdapter; private List mBookTypes; @@ -107,8 +107,8 @@ public class BookstoreActivity extends BaseActivity { super.setUpToolbar(toolbar); setStatusBarColor(R.color.colorPrimary, true); String subTitle = ""; - if (findCrawler != null) { - String name = findCrawler.getFindName(); + if (findCrawler3 != null) { + String name = findCrawler3.getFindName(); title = name.substring(0, name.indexOf("[")); subTitle = name.substring(name.indexOf("[") + 1, name.length() - 1); } @@ -119,7 +119,7 @@ public class BookstoreActivity extends BaseActivity { @Override protected void initData(Bundle savedInstanceState) { super.initData(savedInstanceState); - findCrawler = (FindCrawler) getIntent().getSerializableExtra(APPCONST.FIND_CRAWLER); + findCrawler3 = (FindCrawler3) getIntent().getSerializableExtra(APPCONST.FIND_CRAWLER); } @Override @@ -139,7 +139,7 @@ public class BookstoreActivity extends BaseActivity { getBooksData(); }); - mBookStoreBookAdapter = new BookStoreBookAdapter(findCrawler.hasImg(), this); + mBookStoreBookAdapter = new BookStoreBookAdapter(findCrawler3.hasImg(), this); binding.rvBookList.setLayoutManager(new LinearLayoutManager(this)); binding.rvBookList.setAdapter(mBookStoreBookAdapter); binding.refreshLayout.setOnReloadingListener(this::getData); @@ -150,7 +150,7 @@ public class BookstoreActivity extends BaseActivity { super.initClick(); mBookStoreBookAdapter.setOnItemClickListener((view, pos) -> { Book book = bookList.get(pos); - if (!findCrawler.needSearch()) { + if (!findCrawler3.needSearch()) { goToBookDetail(book); } else { if (BookService.getInstance().isBookCollected(book)) { @@ -174,7 +174,7 @@ public class BookstoreActivity extends BaseActivity { protected void processLogic() { super.processLogic(); getData(); - if (findCrawler.needSearch()) { + if (findCrawler3.needSearch()) { SharedPreUtils spu = SharedPreUtils.getInstance(); boolean isReadTopTip = spu.getBoolean(getString(R.string.isReadTopTip), false); if (!isReadTopTip) { @@ -189,15 +189,15 @@ public class BookstoreActivity extends BaseActivity { * 获取页面数据 */ private void getData() { - if (findCrawler instanceof QiDianMobileRank) { + if (findCrawler3 instanceof QiDianMobileRank) { SharedPreUtils spu = SharedPreUtils.getInstance(); if (spu.getString(getString(R.string.qdCookie), "").equals("")) { - ((QiDianMobileRank) findCrawler).initCookie(this, new ResultCallback() { + ((QiDianMobileRank) findCrawler3).initCookie(this, new ResultCallback() { @Override public void onFinish(Object o, int code) { if (App.isDestroy(BookstoreActivity.this)) return; spu.putString(getString(R.string.qdCookie), (String) o); - mBookTypes = findCrawler.getBookTypes(); + mBookTypes = findCrawler3.getBookTypes(); initBooks(); } @@ -209,13 +209,13 @@ public class BookstoreActivity extends BaseActivity { } }); } else { - mBookTypes = findCrawler.getBookTypes(); + mBookTypes = findCrawler3.getBookTypes(); initBooks(); } - } else if ((mBookTypes = findCrawler.getBookTypes()) != null) { + } else if ((mBookTypes = findCrawler3.getBookTypes()) != null) { initBooks(); } else { - BookStoreApi.getBookTypeList(findCrawler, new ResultCallback() { + BookStoreApi.getBookTypeList(findCrawler3, new ResultCallback() { @Override public void onFinish(Object o, int code) { if (App.isDestroy(BookstoreActivity.this)) return; @@ -244,14 +244,14 @@ public class BookstoreActivity extends BaseActivity { * 获取小数列表数据 */ private void getBooksData() { - if (findCrawler.getTypePage(curType, page)) { + if (findCrawler3.getTypePage(curType, page)) { binding.srlBookList.finishLoadMoreWithNoMoreData(); return; } mHandler.sendEmptyMessage(3); - if (findCrawler instanceof QiDianMobileRank) { - ((QiDianMobileRank) findCrawler).getRankBooks(curType, new ResultCallback() { + if (findCrawler3 instanceof QiDianMobileRank) { + ((QiDianMobileRank) findCrawler3).getRankBooks(curType, new ResultCallback() { @Override public void onFinish(Object o, int code) { if (App.isDestroy(BookstoreActivity.this)) return; @@ -287,7 +287,7 @@ public class BookstoreActivity extends BaseActivity { } }); } else { - BookStoreApi.getBookRankList(curType, findCrawler, new ResultCallback() { + BookStoreApi.getBookRankList(curType, findCrawler3, new ResultCallback() { @Override public void onFinish(Object o, int code) { if (App.isDestroy(BookstoreActivity.this)) return; @@ -381,7 +381,7 @@ public class BookstoreActivity extends BaseActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { - if (findCrawler.needSearch()) { + if (findCrawler3.needSearch()) { menu.findItem(R.id.action_tip).setVisible(true); } return true; @@ -395,14 +395,9 @@ public class BookstoreActivity extends BaseActivity { */ @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_tip: - mHandler.sendEmptyMessage(6); - return true; - case R.id.action_refresh: - binding.refreshLayout.showLoading(); - getData(); - return true; + if (item.getItemId() == R.id.action_tip) { + mHandler.sendEmptyMessage(6); + return true; } return false; } diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/FindBookActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/FindBookActivity.java new file mode 100644 index 0000000..f902bea --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/FindBookActivity.java @@ -0,0 +1,162 @@ +package xyz.fycz.myreader.ui.activity; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BaseActivity; +import xyz.fycz.myreader.base.BitIntentDataManager; +import xyz.fycz.myreader.base.observer.MyObserver; +import xyz.fycz.myreader.databinding.ActivityFindBookBinding; +import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.ui.adapter.TabFragmentPageAdapter; +import xyz.fycz.myreader.ui.dialog.DialogCreator; +import xyz.fycz.myreader.ui.fragment.FindBook1Fragment; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.find.QiDianFindCrawler; +import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler; + +/** + * @author fengyue + * @date 2021/7/21 20:10 + */ +public class FindBookActivity extends BaseActivity { + private ActivityFindBookBinding binding; + private BookSource source; + private FindCrawler findCrawler; + private List groups; + + @Override + protected void bindView() { + binding = ActivityFindBookBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + } + + @Override + protected void initData(Bundle savedInstanceState) { + super.initData(savedInstanceState); + Object obj = BitIntentDataManager.getInstance().getData(getIntent()); + if (obj instanceof BookSource) { + source = (BookSource) obj; + findCrawler = new ThirdFindCrawler(source); + } else if (obj instanceof FindCrawler) { + findCrawler = (FindCrawler) obj; + } + if (findCrawler == null) { + finish(); + return; + } + initData(); + } + + @Override + protected void initWidget() { + binding.loading.setOnReloadingListener(this::initData); + } + + private void initData() { + findCrawler.initData() + .compose(RxUtils::toSimpleSingle) + .subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } + + @Override + public void onNext(@NotNull Boolean aBoolean) { + if (aBoolean) { + groups = findCrawler.getGroups(); + setUpToolbar(); + initFragments(); + } else { + ToastUtils.showError("发现规则语法错误"); + } + binding.loading.showFinish(); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); + binding.loading.showError(); + } + }); + } + + @Override + protected void setUpToolbar(Toolbar toolbar) { + super.setUpToolbar(toolbar); + setStatusBarColor(R.color.colorPrimary, true); + } + + private void setUpToolbar() { + if (groups.size() == 1) { + binding.tabTlIndicator.setVisibility(View.GONE); + getSupportActionBar().setTitle(groups.get(0)); + } else { + binding.tabTlIndicator.setVisibility(View.VISIBLE); + } + } + + private void initFragments() { + TabFragmentPageAdapter adapter = new TabFragmentPageAdapter(getSupportFragmentManager()); + for (String group : groups) { + adapter.addFragment(new FindBook1Fragment(findCrawler.getKindsByKey(group), findCrawler), group); + } + binding.tabVp.setAdapter(adapter); + binding.tabVp.setOffscreenPageLimit(3); + binding.tabTlIndicator.setUpWithViewPager(binding.tabVp); + } +/********************************Event***************************************/ + /** + * 创建菜单 + * + * @param menu + * @return + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_store, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (findCrawler.needSearch()) { + menu.findItem(R.id.action_tip).setVisible(true); + } + return true; + } + + /** + * 导航栏菜单点击事件 + * + * @param item + * @return + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_tip) { + DialogCreator.createTipDialog(this, + getResources().getString(R.string.top_sort_tip, "此发现")); + return true; + } + return false; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java index a334286..4cf585e 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java @@ -21,6 +21,7 @@ import java.util.List; import cn.bingoogolapple.qrcode.core.QRCodeView; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; +import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.base.observer.MySingleObserver; @@ -210,6 +211,10 @@ public class QRCodeScanActivity extends BaseActivity implements QRCodeView.Deleg emitter.onSuccess(bitmap); }).compose(RxUtils::toSimpleSingle) .subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(Bitmap bitmap) { binding.zxingview.decodeQRCode(bitmap); 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 8b3b0bc..5b854d8 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 @@ -51,6 +51,7 @@ import java.util.List; import java.util.Locale; import io.reactivex.Single; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import xyz.fycz.myreader.ActivityManage; import xyz.fycz.myreader.R; @@ -1576,6 +1577,10 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe BookApi.getChapterContent(chapter, mBook, mReadCrawler) .subscribeOn(Schedulers.from(App.getApplication().getmFixedThreadPool())) .subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onNext(@NotNull String s) { downloadingChapter = chapter.getTitle(); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadRecordActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadRecordActivity.java index f87e7e9..56f45b1 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadRecordActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadRecordActivity.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; import xyz.fycz.myreader.base.BaseActivity; import xyz.fycz.myreader.base.BitIntentDataManager; @@ -67,6 +68,10 @@ public class ReadRecordActivity extends BaseActivity { recordService = ReadRecordService.getInstance(); recordService.getAllRecordsByTime().compose(RxUtils::toSimpleSingle) .subscribe(new MySingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NotNull List readRecords) { records = readRecords; @@ -165,6 +170,10 @@ public class ReadRecordActivity extends BaseActivity { case 0: recordService.removeAll().compose(RxUtils::toSimpleSingle) .subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NotNull Boolean aBoolean) { ToastUtils.showSuccess("阅读记录已全部移除"); @@ -183,6 +192,10 @@ public class ReadRecordActivity extends BaseActivity { case 1: recordService.removeAllTime(records).compose(RxUtils::toSimpleSingle) .subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NotNull Boolean aBoolean) { ToastUtils.showSuccess("阅读时长已全部清空"); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java index ca36276..98b34cd 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java @@ -29,6 +29,7 @@ import io.reactivex.Observable; import io.reactivex.ObservableOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import xyz.fycz.myreader.R; import xyz.fycz.myreader.base.BaseActivity; @@ -76,6 +77,10 @@ public class ReplaceRuleActivity extends BaseActivity { protected void initData(Bundle savedInstanceState) { super.initData(savedInstanceState); ReplaceRuleManager.getAll().subscribe(new MySingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull List replaceRuleBeans) { mReplaceRules = replaceRuleBeans; @@ -303,6 +308,10 @@ public class ReplaceRuleActivity extends BaseActivity { Observable observable = ReplaceRuleManager.importReplaceRule(text); if (observable != null) { observable.subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onNext(Boolean aBoolean) { if (aBoolean) { 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 91ef2e6..683acb1 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 @@ -368,6 +368,10 @@ public class SearchBookActivity extends BaseActivity { mSourcesName, isDisables, dSourceCount, (dialog, which) -> { BookSourceManager.saveDatas(sources) .subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull Boolean aBoolean) { if (aBoolean) { @@ -408,6 +412,10 @@ public class SearchBookActivity extends BaseActivity { headers.put("Cookie", cookie); emitter.onSuccess(parseHotKeys(OkHttpUtils.getHtml(url, null, "utf-8", headers))); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NotNull Boolean b) { initSuggestionList(); @@ -448,6 +456,7 @@ public class SearchBookActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { sugDis = d; + addDisposable(d); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java index cead3f2..b3bc7b0 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java @@ -190,6 +190,7 @@ public class SourceDebugActivity extends BaseActivity { @Override public void onSubscribe(Disposable d) { disposable = d; + addDisposable(d); } @Override diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java new file mode 100644 index 0000000..e8e7567 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java @@ -0,0 +1,154 @@ +package xyz.fycz.myreader.ui.adapter.holder; + +import android.app.Activity; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.zhy.view.flowlayout.TagFlowLayout; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.App; +import xyz.fycz.myreader.base.adapter.ViewHolderImpl; +import xyz.fycz.myreader.base.observer.MyObserver; +import xyz.fycz.myreader.entity.SearchBookBean; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager; +import xyz.fycz.myreader.ui.adapter.BookTagAdapter; +import xyz.fycz.myreader.util.help.StringHelper; +import xyz.fycz.myreader.util.utils.KeyWordUtils; +import xyz.fycz.myreader.util.utils.NetworkUtils; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.webapi.BookApi; +import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil; +import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; +import xyz.fycz.myreader.widget.CoverImageView; + +/** + * @author fengyue + * @date 2021/7/22 9:34 + */ +public class FindBookHolder extends ViewHolderImpl { + private List tagList = new ArrayList<>(); + + private CoverImageView ivBookImg; + private TextView tvBookName; + private TagFlowLayout tflBookTag; + private TextView tvDesc; + private TextView tvAuthor; + private TextView tvSource; + private TextView tvNewestChapter; + + @Override + protected int getItemLayoutId() { + return R.layout.listview_search_book_item; + } + + @Override + public void initView() { + ivBookImg = findById(R.id.iv_book_img); + tvBookName = findById(R.id.tv_book_name); + tflBookTag = findById(R.id.tfl_book_tag); + tvAuthor = findById(R.id.tv_book_author); + tvDesc = findById(R.id.tv_book_desc); + tvSource = findById(R.id.tv_book_source); + tvNewestChapter = findById(R.id.tv_book_newest_chapter); + getItemView().setBackgroundColor(getContext().getResources().getColor(R.color.colorForeground)); + } + + @Override + public void onBind(RecyclerView.ViewHolder holder, Book data, int pos) { + BookSource source = BookSourceManager.getBookSourceByStr(data.getSource()); + ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source); + if (StringHelper.isEmpty(data.getImgUrl())) { + data.setImgUrl(""); + } + if (!App.isDestroy((Activity) getContext())) { + ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), data.getImgUrl()), data.getName(), data.getAuthor()); + } + tvBookName.setText(data.getName()); + if (!StringHelper.isEmpty(data.getAuthor())) { + tvAuthor.setText(data.getAuthor()); + }else { + tvAuthor.setText(""); + } + initTagList(data); + if (!StringHelper.isEmpty(data.getNewestChapterTitle())) { + tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, data.getNewestChapterTitle())); + } else { + data.setNewestChapterTitle(""); + tvNewestChapter.setText(""); + } + if (!StringHelper.isEmpty(data.getDesc())) { + tvDesc.setText(String.format("简介:%s", data.getDesc())); + } else { + data.setDesc(""); + tvDesc.setText(""); + } + if (!StringHelper.isEmpty(source.getSourceName()) && !"未知书源".equals(source.getSourceName())) + tvSource.setText(String.format("书源:%s", source.getSourceName())); + if (needGetInfo(data) && rc instanceof BookInfoCrawler) { + Log.i(data.getName(), "initOtherInfo"); + BookInfoCrawler bic = (BookInfoCrawler) rc; + BookApi.getBookInfo(data, bic).compose(RxUtils::toSimpleSingle) + .subscribe(new MyObserver() { + @Override + public void onNext(@NotNull Book book) { + initOtherInfo(book, rc); + } + }); + } + } + + private void initOtherInfo(Book book, ReadCrawler rc) { + //简介 + if (StringHelper.isEmpty(tvDesc.getText().toString())) { + tvDesc.setText(String.format("简介:%s", book.getDesc())); + } + if (StringHelper.isEmpty(tvNewestChapter.getText().toString())) { + tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle())); + } + if (!StringHelper.isEmpty(book.getAuthor())) { + tvAuthor.setText(book.getAuthor()); + } + //图片 + if (!App.isDestroy((Activity) getContext())) { + ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), book.getImgUrl()), book.getName(), book.getAuthor()); + } + } + + private void initTagList(Book data) { + tagList.clear(); + String type = data.getType(); + if (!StringHelper.isEmpty(type)) + tagList.add("0:" + type); + String wordCount = data.getWordCount(); + if (!StringHelper.isEmpty(wordCount)) + tagList.add("1:" + wordCount); + String status = data.getStatus(); + if (!StringHelper.isEmpty(status)) + tagList.add("2:" + status); + if (tagList.size() == 0) { + tflBookTag.setVisibility(View.GONE); + } else { + tflBookTag.setVisibility(View.VISIBLE); + tflBookTag.setAdapter(new BookTagAdapter(getContext(), tagList, 11)); + } + } + private boolean needGetInfo(Book bookBean) { + if (StringHelper.isEmpty(bookBean.getAuthor())) return true; + if (StringHelper.isEmpty(bookBean.getType())) return true; + if (StringHelper.isEmpty(bookBean.getDesc())) return true; + if (StringHelper.isEmpty(bookBean.getNewestChapterTitle())) return true; + return StringHelper.isEmpty(bookBean.getImgUrl()); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindSourceHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindSourceHolder.java new file mode 100644 index 0000000..ee0cac7 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindSourceHolder.java @@ -0,0 +1,36 @@ +package xyz.fycz.myreader.ui.adapter.holder; + +import android.content.Intent; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.base.BitIntentDataManager; +import xyz.fycz.myreader.base.adapter.ViewHolderImpl; +import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.ui.activity.FindBookActivity; + +/** + * @author fengyue + * @date 2021/7/22 22:27 + */ +public class FindSourceHolder extends ViewHolderImpl { + + private TextView tvName; + + @Override + protected int getItemLayoutId() { + return R.layout.item_find_source; + } + + @Override + public void initView() { + tvName = findById(R.id.tv_name); + } + + @Override + public void onBind(RecyclerView.ViewHolder holder, BookSource data, int pos) { + tvName.setText(data.getSourceName()); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java index 5ba1d59..4fe9049 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java @@ -3,6 +3,7 @@ package xyz.fycz.myreader.ui.adapter.holder; import android.annotation.SuppressLint; import android.app.Activity; import android.util.Log; +import android.view.View; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; @@ -79,7 +80,7 @@ public class SearchBookHolder extends ViewHolderImpl { @Override public void onBind(RecyclerView.ViewHolder holder, SearchBookBean data, int pos) { List aBooks = mBooks.getValues(data); - if (aBooks == null || aBooks.size() == 0){ + if (aBooks == null || aBooks.size() == 0) { aBooks = new ArrayList<>(); aBooks.add(searchBookBean2Book(data)); } @@ -88,37 +89,34 @@ public class SearchBookHolder extends ViewHolderImpl { BookSource source = BookSourceManager.getBookSourceByStr(book.getSource()); ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source); books2SearchBookBean(data, aBooks); - if (!StringHelper.isEmpty(data.getImgUrl())) { - if (!App.isDestroy((Activity) getContext())) { - ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), data.getImgUrl()), data.getName(), data.getAuthor()); - } - }else { + if (StringHelper.isEmpty(data.getImgUrl())) { data.setImgUrl(""); } + if (!App.isDestroy((Activity) getContext())) { + ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), data.getImgUrl()), data.getName(), data.getAuthor()); + } KeyWordUtils.setKeyWord(tvBookName, data.getName(), keyWord); if (!StringHelper.isEmpty(data.getAuthor())) { KeyWordUtils.setKeyWord(tvAuthor, data.getAuthor(), keyWord); + } else { + tvAuthor.setText(""); } initTagList(data); if (!StringHelper.isEmpty(data.getLastChapter())) { tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, data.getLastChapter())); - }else { + } else { data.setLastChapter(""); + tvNewestChapter.setText(""); } if (!StringHelper.isEmpty(data.getDesc())) { tvDesc.setText(String.format("简介:%s", data.getDesc())); - }else { + } else { data.setDesc(""); + tvDesc.setText(""); } tvSource.setText(getContext().getString(R.string.source_title_num, source.getSourceName(), bookCount)); App.getHandler().postDelayed(() -> { if (needGetInfo(data) && rc instanceof BookInfoCrawler) { - if (tvBookName.getTag() == null || !(Boolean) tvBookName.getTag()) { - tvBookName.setTag(true); - } else { - initOtherInfo(data, rc); - return; - } Log.i(data.getName(), "initOtherInfo"); BookInfoCrawler bic = (BookInfoCrawler) rc; searchEngine.getBookInfo(book, bic, isSuccess -> { @@ -127,8 +125,6 @@ public class SearchBookHolder extends ViewHolderImpl { books.add(book); books2SearchBookBean(data, books); initOtherInfo(data, rc); - } else { - tvBookName.setTag(false); } }); } @@ -163,7 +159,12 @@ public class SearchBookHolder extends ViewHolderImpl { String status = data.getStatus(); if (!StringHelper.isEmpty(status)) tagList.add("2:" + status); - tflBookTag.setAdapter(new BookTagAdapter(activity, tagList, 11)); + if (tagList.size() == 0) { + tflBookTag.setVisibility(View.GONE); + } else { + tflBookTag.setVisibility(View.VISIBLE); + tflBookTag.setAdapter(new BookTagAdapter(activity, tagList, 11)); + } } private void books2SearchBookBean(SearchBookBean bookBean, List books) { @@ -233,7 +234,7 @@ public class SearchBookHolder extends ViewHolderImpl { } } - private Book searchBookBean2Book(SearchBookBean bean){ + private Book searchBookBean2Book(SearchBookBean bean) { Book book = new Book(); book.setName(bean.getName()); book.setAuthor(bean.getAuthor()); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java index a3b2916..beeb85b 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java @@ -226,6 +226,7 @@ public class DIYSourceFragment extends BaseFragment { @Override public void onSubscribe(Disposable d) { importSourceDis = d; + addDisposable(d); } @Override @@ -281,6 +282,10 @@ public class DIYSourceFragment extends BaseFragment { } emitter.onSuccess(sourceBeanList); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull List sources) { mBookSources = sources; @@ -391,6 +396,10 @@ public class DIYSourceFragment extends BaseFragment { emitter.onSuccess(FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(sources), FileUtils.getFile(APPCONST.FILE_DIR + "BookSources.json"))); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull Boolean aBoolean) { if (aBoolean) { @@ -418,6 +427,10 @@ public class DIYSourceFragment extends BaseFragment { emitter.onError(new Exception("书源文件写出失败")); } }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull File share) { ShareUtils.share(sourceActivity, share, "书源分享", "text/plain"); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook1Fragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook1Fragment.java new file mode 100644 index 0000000..31f0b4d --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook1Fragment.java @@ -0,0 +1,66 @@ +package xyz.fycz.myreader.ui.fragment; + +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.PopupMenu; +import androidx.fragment.app.Fragment; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.base.BaseFragment; +import xyz.fycz.myreader.base.LazyFragment; +import xyz.fycz.myreader.databinding.FragmentFindBook1Binding; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.ui.adapter.TabFragmentPageAdapter; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; + +/** + * @author fengyue + * @date 2021/7/21 23:06 + */ +public class FindBook1Fragment extends LazyFragment { + private FragmentFindBook1Binding binding; + private List kinds; + private FindCrawler findCrawler; + private PopupMenu kindMenu; + + public FindBook1Fragment(List kinds, FindCrawler findCrawler) { + this.kinds = kinds; + this.findCrawler = findCrawler; + } + @Override + public void lazyInit() { + kindMenu = new PopupMenu(getContext(), binding.ivMenu, Gravity.END); + TabFragmentPageAdapter adapter = new TabFragmentPageAdapter(getChildFragmentManager()); + for (int i = 0, kindsSize = kinds.size(); i < kindsSize; i++) { + FindKind kind = kinds.get(i); + adapter.addFragment(new FindBook2Fragment(kind, findCrawler), kind.getName()); + kindMenu.getMenu().add(0, 0, i, kind.getName()); + } + binding.tabVp.setAdapter(adapter); + binding.tabVp.setOffscreenPageLimit(3); + binding.tabTlIndicator.setUpWithViewPager(binding.tabVp); + kindMenu.setOnMenuItemClickListener(item -> { + binding.tabTlIndicator.setCurrentItem(item.getOrder(), true); + return true; + }); + binding.ivMenu.setOnClickListener(v -> kindMenu.show()); + } + + @Nullable + @Override + public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { + binding = FragmentFindBook1Binding.inflate(inflater, container, false); + return binding.getRoot(); + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook2Fragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook2Fragment.java new file mode 100644 index 0000000..fc6f7a9 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook2Fragment.java @@ -0,0 +1,177 @@ +package xyz.fycz.myreader.ui.fragment; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.base.BaseFragment; +import xyz.fycz.myreader.base.BitIntentDataManager; +import xyz.fycz.myreader.base.LazyFragment; +import xyz.fycz.myreader.base.adapter.BaseListAdapter; +import xyz.fycz.myreader.base.adapter.IViewHolder; +import xyz.fycz.myreader.base.observer.MyObserver; +import xyz.fycz.myreader.common.APPCONST; +import xyz.fycz.myreader.databinding.FragmentFindBook2Binding; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.service.BookService; +import xyz.fycz.myreader.ui.activity.BookDetailedActivity; +import xyz.fycz.myreader.ui.activity.BookstoreActivity; +import xyz.fycz.myreader.ui.adapter.holder.FindBookHolder; +import xyz.fycz.myreader.ui.dialog.SourceExchangeDialog; +import xyz.fycz.myreader.util.ToastUtils; +import xyz.fycz.myreader.util.utils.RxUtils; +import xyz.fycz.myreader.webapi.BookApi; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.widget.RefreshLayout; + +/** + * @author fengyue + * @date 2021/7/21 23:06 + */ +public class FindBook2Fragment extends LazyFragment { + private FragmentFindBook2Binding binding; + private FindKind kind; + private FindCrawler findCrawler; + private int page = 1; + private BaseListAdapter findBookAdapter; + private SourceExchangeDialog mSourceDia; + + + public FindBook2Fragment(FindKind kind, FindCrawler findCrawler) { + this.kind = kind; + this.findCrawler = findCrawler; + } + + @Override + public void lazyInit() { + initData(); + initWidget(); + } + + @Nullable + @Override + public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { + binding = FragmentFindBook2Binding.inflate(inflater, container, false); + return binding.getRoot(); + } + + protected void initData() { + findBookAdapter = new BaseListAdapter() { + @Override + protected IViewHolder createViewHolder(int viewType) { + return new FindBookHolder(); + } + }; + binding.rvFindBooks.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rvFindBooks.setAdapter(findBookAdapter); + page = 1; + loadBooks(); + } + + protected void initWidget() { + binding.loading.setOnReloadingListener(() -> { + page = 1; + loadBooks(); + }); + //小说列表下拉加载更多事件 + binding.srlFindBooks.setOnLoadMoreListener(refreshLayout -> loadBooks()); + + //小说列表上拉刷新事件 + binding.srlFindBooks.setOnRefreshListener(refreshLayout -> { + page = 1; + loadBooks(); + }); + + findBookAdapter.setOnItemClickListener((view, pos) -> { + Book book = findBookAdapter.getItem(pos); + if (!findCrawler.needSearch()) { + goToBookDetail(book); + } else { + if (BookService.getInstance().isBookCollected(book)) { + goToBookDetail(book); + return; + } + mSourceDia = new SourceExchangeDialog(getActivity(), book); + mSourceDia.setOnSourceChangeListener((bean, pos1) -> { + Intent intent = new Intent(getContext(), BookDetailedActivity.class); + BitIntentDataManager.getInstance().putData(intent, mSourceDia.getaBooks()); + intent.putExtra(APPCONST.SOURCE_INDEX, pos1); + getActivity().startActivity(intent); + mSourceDia.dismiss(); + }); + mSourceDia.show(); + } + }); + } + + private void loadBooks() { + BookApi.findBooks(kind, findCrawler, page).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } + + @Override + public void onNext(@NotNull List books) { + binding.loading.showFinish(); + if (page == 1) { + if (books.size() == 0) { + binding.srlFindBooks.finishRefreshWithNoMoreData(); + } else { + findBookAdapter.refreshItems(books); + binding.srlFindBooks.finishRefresh(); + } + } else { + if (books.size() == 0) { + binding.srlFindBooks.finishLoadMoreWithNoMoreData(); + } else { + findBookAdapter.addItems(books); + binding.srlFindBooks.finishLoadMore(); + } + } + page++; + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + if (page == 1) { + ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); + binding.loading.showError(); + binding.srlFindBooks.finishRefresh(); + } else { + if (e.getMessage()!= null && e.getMessage().contains("没有下一页")) { + binding.srlFindBooks.finishLoadMoreWithNoMoreData(); + } else { + ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage()); + binding.srlFindBooks.finishLoadMore(); + } + } + } + }); + } + + /** + * 前往书籍详情 + * + * @param book + */ + private void goToBookDetail(Book book) { + Intent intent = new Intent(getContext(), BookDetailedActivity.class); + BitIntentDataManager.getInstance().putData(intent, book); + getActivity().startActivity(intent); + } + +} 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 2272590..0d8fb47 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,37 +1,45 @@ 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; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.recyclerview.widget.LinearLayoutManager; + import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.ObservableOnSubscribe; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; -import xyz.fycz.myreader.application.App; +import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.base.BaseFragment; +import xyz.fycz.myreader.base.BitIntentDataManager; +import xyz.fycz.myreader.base.adapter.BaseListAdapter; +import xyz.fycz.myreader.base.adapter.IViewHolder; +import xyz.fycz.myreader.base.observer.MyObserver; 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.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager; +import xyz.fycz.myreader.ui.activity.FindBookActivity; +import xyz.fycz.myreader.ui.adapter.holder.FindSourceHolder; 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; +import xyz.fycz.myreader.webapi.crawler.find.MiaoBiGeFindCrawler; import xyz.fycz.myreader.webapi.crawler.find.QB5FindCrawler; -import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; +import xyz.fycz.myreader.webapi.crawler.find.QiDianFindCrawler; +import xyz.fycz.myreader.widget.DividerItemDecoration; /** * @author fengyue @@ -40,6 +48,7 @@ import xyz.fycz.myreader.webapi.crawler.find.QiDianMobileRank; public class FindFragment extends BaseFragment { private FragmentFindBinding binding; + private BaseListAdapter findSourcesAdapter; @Override protected View bindView(LayoutInflater inflater, ViewGroup container) { @@ -47,6 +56,52 @@ public class FindFragment extends BaseFragment { return binding.getRoot(); } + @Override + protected void initData(Bundle savedInstanceState) { + Observable.create((ObservableOnSubscribe>) emitter -> { + List findSources = new ArrayList<>(); + List sources = BookSourceManager.getEnabledBookSourceByOrderNum(); + for (BookSource source : sources){ + if (source.getFindRule() != null && !TextUtils.isEmpty(source.getFindRule().getUrl())){ + findSources.add(source); + } + } + emitter.onNext(findSources); + emitter.onComplete(); + }).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } + + @Override + public void onNext(@NotNull List findSources) { + initFindSources(findSources); + } + }); + } + + private void initFindSources(List findSources) { + findSourcesAdapter = new BaseListAdapter() { + @Override + protected IViewHolder createViewHolder(int viewType) { + return new FindSourceHolder(); + } + }; + binding.rvFindSources.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rvFindSources.setAdapter(findSourcesAdapter); + //设置分割线 + binding.rvFindSources.addItemDecoration(new DividerItemDecoration(getContext())); + + findSourcesAdapter.setOnItemClickListener((view, pos) -> { + Intent intent = new Intent(getContext(), FindBookActivity.class); + BitIntentDataManager.getInstance().putData(intent, findSourcesAdapter.getItem(pos)); + getContext().startActivity(intent); + }); + + findSourcesAdapter.refreshItems(findSources); + } + @Override protected void initWidget(Bundle savedInstanceState) { getQuotation(); @@ -56,21 +111,16 @@ public class FindFragment extends BaseFragment { protected void initClick() { super.initClick(); binding.findRlQuotation.setOnClickListener(v -> getQuotation()); - binding.findRlQidianTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(false))); - binding.findRlQidianNsTop.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(true))); - //binding.findRlXs7Top.setOnClickListener(v -> comeToBookstore(new XS7Rank())); - binding.findRlQidianSort.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(false, true))); - binding.findRlQidianNsSort.setOnClickListener(v -> comeToBookstore(new QiDianMobileRank(true, true))); + binding.findRlQidian.setOnClickListener(v -> comeToBookstore(new QiDianFindCrawler(false))); + binding.findRlQidianNs.setOnClickListener(v -> comeToBookstore(new QiDianFindCrawler(true))); + binding.findRlMiaoquStore.setOnClickListener(v -> comeToBookstore(new MiaoBiGeFindCrawler())); binding.findRlQb5Store.setOnClickListener(v -> comeToBookstore(new QB5FindCrawler())); - binding.findRlBen100Store.setOnClickListener(v -> comeToBookstore(new Ben100FindCrawler())); - binding.findRlMiaoquStore.setOnClickListener(v -> comeToBookstore(new MiaoBiFindCrawler())); - //binding.findRlXs7Store.setOnClickListener(v -> comeToBookstore(new XS7ReadCrawler())); } private void comeToBookstore(FindCrawler findCrawler) { - Intent intent = new Intent(getContext(), BookstoreActivity.class); - intent.putExtra(APPCONST.FIND_CRAWLER, findCrawler); + Intent intent = new Intent(getContext(), FindBookActivity.class); + BitIntentDataManager.getInstance().putData(intent, findCrawler); startActivity(intent); } @@ -79,6 +129,10 @@ public class FindFragment extends BaseFragment { String json = OkHttpUtils.getHtml(URLCONST.QUOTATION); emitter.onSuccess(GsonExtensionsKt.getGSON().fromJson(json, Quotation.class)); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NotNull Quotation q) { binding.tvQuotation.setText(q.getHitokoto()); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java index a9e2c94..9558cb9 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java @@ -12,6 +12,7 @@ import java.util.List; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; import xyz.fycz.myreader.base.BaseFragment; import xyz.fycz.myreader.base.observer.MySingleObserver; @@ -109,6 +110,10 @@ public class LocalSourceFragment extends BaseFragment { emitter.onSuccess(true); }).compose(RxUtils::toSimpleSingle) .subscribe(new MySingleObserver() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(@NonNull Boolean aBoolean) { mAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java index db21487..fb17d57 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java @@ -26,6 +26,7 @@ import java.util.Date; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.App; @@ -176,6 +177,10 @@ public class MineFragment extends BaseFragment { }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new MySingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(ArrayList strings) { if (!WebDavHelp.INSTANCE.showRestoreDialog(getContext(), strings, new Restore.CallBack() { diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/WebDavFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/WebDavFragment.java index 5b91e16..e9490f6 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/WebDavFragment.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/WebDavFragment.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import io.reactivex.Single; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.SysManager; @@ -116,6 +117,10 @@ public class WebDavFragment extends BaseFragment { }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new MySingleObserver>() { + @Override + public void onSubscribe(Disposable d) { + addDisposable(d); + } @Override public void onSuccess(ArrayList strings) { if (!WebDavHelp.INSTANCE.showRestoreDialog(getContext(), strings, new Restore.CallBack() { diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java index 66ff6ce..e673ffb 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java @@ -379,6 +379,11 @@ public class BookcasePresenter implements BasePresenter { emitter.onNext(book); emitter.onComplete(); })).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + mMainActivity.addDisposable(d); + } + @Override public void onNext(@NotNull Object o) { mBookcaseAdapter.getIsLoading().put(book.getId(), false); @@ -413,6 +418,11 @@ public class BookcasePresenter implements BasePresenter { emitter.onNext(book); emitter.onComplete(); })).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver() { + @Override + public void onSubscribe(Disposable d) { + mMainActivity.addDisposable(d); + } + @Override public void onNext(@NotNull Object o) { mBookcaseAdapter.getIsLoading().put(book.getId(), false); @@ -856,6 +866,12 @@ public class BookcasePresenter implements BasePresenter { emitter.onNext(chapter); emitter.onComplete(); })).subscribeOn(Schedulers.from(App.getApplication().getmFixedThreadPool())).subscribe(new MyObserver() { + + @Override + public void onSubscribe(Disposable d) { + mMainActivity.addDisposable(d); + } + @Override public void onNext(@NotNull Object o) { diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java index c4ab925..35ced5b 100644 --- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java +++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java @@ -6,6 +6,7 @@ import android.view.View; import org.jetbrains.annotations.NotNull; +import io.reactivex.disposables.Disposable; import xyz.fycz.myreader.R; import xyz.fycz.myreader.application.App; import xyz.fycz.myreader.base.BasePresenter; @@ -61,8 +62,8 @@ public class CatalogPresenter implements BasePresenter { mChapters = mChapterService.findBookAllChapterByBookId(mBook.getId()); if (mChapters.size() != 0) { initChapterTitleList(); - }else { - if ("本地书籍".equals(mBook.getType())){ + } else { + if ("本地书籍".equals(mBook.getType())) { ToastUtils.showWarring("本地书籍请先拆分章节!"); return; } @@ -147,7 +148,7 @@ public class CatalogPresenter implements BasePresenter { * @param query */ public void startSearch(String query) { - if (mChapters.size() == 0) return; + if (mChapters.size() == 0) return; mChapterTitleAdapter.getFilter().filter(query); mCatalogFragment.getLvChapterList().setSelection(0); } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java index c87efde..fb56ede 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java @@ -1,9 +1,8 @@ package xyz.fycz.myreader.webapi; -import android.util.Log; - import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -11,6 +10,7 @@ import io.reactivex.Observable; import io.reactivex.ObservableOnSubscribe; import okhttp3.MediaType; import okhttp3.RequestBody; +import xyz.fycz.myreader.entity.FindKind; import xyz.fycz.myreader.entity.SearchBookBean; import xyz.fycz.myreader.entity.StrResponse; import xyz.fycz.myreader.greendao.entity.Book; @@ -20,9 +20,11 @@ import xyz.fycz.myreader.util.help.StringHelper; import xyz.fycz.myreader.util.utils.NetworkUtils; import xyz.fycz.myreader.util.utils.OkHttpUtils; import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.webapi.crawler.read.TianLaiReadCrawler; import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler; +import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler; import static xyz.fycz.myreader.util.utils.OkHttpUtils.getCookies; @@ -143,4 +145,16 @@ public class BookApi { }).flatMap(response -> OkHttpUtils.setCookie(response, rc.getNameSpace())).flatMap(rc::getContentFormStrResponse); } + + public static Observable> findBooks(FindKind kind, FindCrawler findCrawler, int page) { + if (findCrawler instanceof ThirdFindCrawler){ + return ThirdSourceApi.findBook(kind.getUrl(), (ThirdFindCrawler) findCrawler, page); + } + if (kind.getMaxPage() > 0 && page > kind.getMaxPage()) return Observable.just(Collections.EMPTY_LIST); + String url = kind.getUrl().replace("{page}", page + ""); + return Observable.create((ObservableOnSubscribe) emitter -> { + emitter.onNext(OkHttpUtils.getStrResponse(url, null, null)); + emitter.onComplete(); + }).flatMap(response -> OkHttpUtils.setCookie(response, findCrawler.getTag())).flatMap((StrResponse strResponse) -> findCrawler.getFindBooks(strResponse, kind)); + } } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java index 73bc653..e93c76b 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java @@ -12,20 +12,20 @@ import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.greendao.entity.Book; 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.base.FindCrawler3; public class BookStoreApi { /** * 获取书城小说分类列表 - * @param findCrawler + * @param findCrawler3 * @param callback */ - public static void getBookTypeList(FindCrawler findCrawler, final ResultCallback callback){ + public static void getBookTypeList(FindCrawler3 findCrawler3, final ResultCallback callback){ Single.create((SingleOnSubscribe>) emitter -> { - String html = OkHttpUtils.getHtml(findCrawler.getFindUrl(), findCrawler.getCharset()); - emitter.onSuccess(findCrawler.getBookTypes(html)); + String html = OkHttpUtils.getHtml(findCrawler3.getFindUrl(), findCrawler3.getCharset()); + emitter.onSuccess(findCrawler3.getBookTypes(html)); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver>() { @Override public void onSuccess(@NotNull List bookTypes) { @@ -42,13 +42,13 @@ public class BookStoreApi { /** * 获取某一分类小说排行榜列表 - * @param findCrawler + * @param findCrawler3 * @param callback */ - public static void getBookRankList(BookType bookType, FindCrawler findCrawler, final ResultCallback callback){ + public static void getBookRankList(BookType bookType, FindCrawler3 findCrawler3, final ResultCallback callback){ Single.create((SingleOnSubscribe>) emitter -> { - String html = OkHttpUtils.getHtml(bookType.getUrl(), findCrawler.getCharset()); - emitter.onSuccess(findCrawler.getFindBooks(html, bookType)); + String html = OkHttpUtils.getHtml(bookType.getUrl(), findCrawler3.getCharset()); + emitter.onSuccess(findCrawler3.getFindBooks(html, bookType)); }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver>() { @Override public void onSuccess(@NotNull List books) { diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java b/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java index c4bf35c..c47d671 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java @@ -22,7 +22,9 @@ import xyz.fycz.myreader.model.third2.content.BookContent; import xyz.fycz.myreader.model.third2.content.BookInfo; import xyz.fycz.myreader.model.third2.content.BookList; import xyz.fycz.myreader.util.utils.OkHttpUtils; +import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler; import xyz.fycz.myreader.webapi.crawler.source.ThirdCrawler; +import xyz.fycz.myreader.webapi.crawler.source.find.ThirdFindCrawler; import static xyz.fycz.myreader.common.APPCONST.JS_PATTERN; import static xyz.fycz.myreader.util.utils.OkHttpUtils.getCookies; @@ -146,5 +148,20 @@ public class ThirdSourceApi { return Observable.error(new Throwable(String.format("url错误:%s", chapter.getUrl()))); } } - + /** + * 发现 + */ + public static Observable> findBook(String url, ThirdFindCrawler fc, int page) { + BookSource source = fc.getSource(); + Map headers = getCookies(fc.getTag()); + BookList bookList = new BookList(source.getSourceUrl(), source.getSourceName(), source, true); + try { + AnalyzeUrl analyzeUrl = new AnalyzeUrl(url, null, page, headers, source.getSourceUrl()); + return OkHttpUtils.getStrResponse(analyzeUrl) + .flatMap(response -> OkHttpUtils.setCookie(response, source.getSourceUrl())) + .flatMap(bookList::analyzeSearchBook); + } catch (Exception e) { + return Observable.error(new Throwable(String.format("%s错误:%s", url, e.getLocalizedMessage()))); + } + } } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseFindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseFindCrawler.java new file mode 100644 index 0000000..dbf9026 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseFindCrawler.java @@ -0,0 +1,43 @@ +package xyz.fycz.myreader.webapi.crawler.base; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.reactivex.Observable; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.greendao.entity.Book; + +/** + * @author fengyue + * @date 2021/7/21 20:26 + */ +public abstract class BaseFindCrawler implements FindCrawler { + protected Map> kindsMap = new LinkedHashMap<>(); + + @Override + public List getGroups() { + return new ArrayList<>(kindsMap.keySet()); + } + + @Override + public Map> getKindsMap() { + return kindsMap; + } + + @Override + public List getKindsByKey(String key) { + return kindsMap.get(key); + } + + @Override + public abstract Observable initData(); + + @Override + public boolean needSearch() { + return false; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java index a8e3e1b..518f3bb 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java @@ -1,31 +1,24 @@ package xyz.fycz.myreader.webapi.crawler.base; -import xyz.fycz.myreader.entity.bookstore.BookType; -import xyz.fycz.myreader.greendao.entity.Book; - -import java.io.Serializable; import java.util.List; +import java.util.Map; + +import io.reactivex.Observable; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.greendao.entity.Book; /** * @author fengyue - * @date 2020/9/14 18:36 + * @date 2021/7/21 22:07 */ -public abstract class FindCrawler implements Serializable { - private static final long serialVersionUID = 1L; - - public abstract String getCharset(); - public abstract String getFindName(); - public abstract String getFindUrl(); - public abstract boolean hasImg(); - public abstract boolean needSearch(); - //动态获取 - public List getBookTypes(String html) { - return null; - } - //静态获取 - public List getBookTypes(){ - return null; - } - public abstract List getFindBooks(String html, BookType bookType); - public abstract boolean getTypePage(BookType curType, int page); +public interface FindCrawler { + String getName(); + String getTag(); + List getGroups(); + Map> getKindsMap(); + List getKindsByKey(String key); + Observable initData(); + boolean needSearch(); + Observable> getFindBooks(StrResponse strResponse, FindKind kind); } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler3.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler3.java new file mode 100644 index 0000000..fed43dd --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler3.java @@ -0,0 +1,31 @@ +package xyz.fycz.myreader.webapi.crawler.base; + +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.greendao.entity.Book; + +import java.io.Serializable; +import java.util.List; + +/** + * @author fengyue + * @date 2020/9/14 18:36 + */ +public abstract class FindCrawler3 implements Serializable { + private static final long serialVersionUID = 1L; + + public abstract String getCharset(); + public abstract String getFindName(); + public abstract String getFindUrl(); + public abstract boolean hasImg(); + public abstract boolean needSearch(); + //动态获取 + public List getBookTypes(String html) { + return null; + } + //静态获取 + public List getBookTypes(){ + return null; + } + public abstract List getFindBooks(String html, BookType bookType); + public abstract boolean getTypePage(BookType curType, int page); +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/Ben100FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/Ben100FindCrawler.java index b22380d..66d8d43 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/Ben100FindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/Ben100FindCrawler.java @@ -12,10 +12,10 @@ import java.util.List; import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.greendao.entity.Book; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; @Deprecated -public class Ben100FindCrawler extends FindCrawler { +public class Ben100FindCrawler extends FindCrawler3 { public static final String NAME_SPACE = "https://www.100ben.net"; public static final String CHARSET = "UTF-8"; public static final String SEARCH_CHARSET = "utf-8"; diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiFindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiFindCrawler.java index 6d95296..3c5d3e4 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiFindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiFindCrawler.java @@ -12,10 +12,10 @@ import java.util.List; import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.greendao.entity.Book; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; -public class MiaoBiFindCrawler extends FindCrawler { +public class MiaoBiFindCrawler extends FindCrawler3 { public static final String NAME_SPACE = "https://www.imiaobige.com"; public static final String CHARSET = "UTF-8"; public static final String SEARCH_CHARSET = "UTF-8"; diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiGeFindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiGeFindCrawler.java new file mode 100644 index 0000000..3ea7af0 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiGeFindCrawler.java @@ -0,0 +1,93 @@ +package xyz.fycz.myreader.webapi.crawler.find; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import io.reactivex.Observable; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.enums.LocalBookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; + +/** + * @author fengyue + * @date 2021/7/22 17:11 + */ +public class MiaoBiGeFindCrawler extends BaseFindCrawler { + private final LinkedHashMap mBookTypes = new LinkedHashMap<>(); + + @Override + public String getName() { + return "妙笔阁"; + } + + @Override + public String getTag() { + return "https://www.imiaobige.com"; + } + + private void initBookTypes() { + mBookTypes.put("玄幻奇幻", "https://www.imiaobige.com/xuanhuan/{page}.html"); + mBookTypes.put("武侠仙侠", "https://www.imiaobige.com/wuxia/{page}.html"); + mBookTypes.put("都市生活", "https://www.imiaobige.com/dushi/{page}.html"); + mBookTypes.put("历史军事", "https://www.imiaobige.com/lishi/{page}.html"); + mBookTypes.put("游戏竞技", "https://www.imiaobige.com/youxi/{page}.html"); + mBookTypes.put("科幻未来", "https://www.imiaobige.com/kehuan/{page}.html"); + } + + @Override + public Observable initData() { + return Observable.create(emitter -> { + initBookTypes(); + List kinds = new ArrayList<>(); + for (String name : mBookTypes.keySet()) { + FindKind findKind = new FindKind(); + findKind.setName(name); + findKind.setUrl(mBookTypes.get(name)); + findKind.setTag(getTag()); + kinds.add(findKind); + } + kindsMap.put(getName(), kinds); + emitter.onNext(true); + emitter.onComplete(); + }); + } + + @Override + public Observable> getFindBooks(StrResponse strResponse, FindKind kind) { + return Observable.create(emitter -> { + emitter.onNext(getFindBooks(strResponse.body(), kind)); + emitter.onComplete(); + }); + } + + public List getFindBooks(String html, FindKind kind) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Element div = doc.getElementById("sitebox"); + Elements dls = div.getElementsByTag("dl"); + for (Element dl : dls) { + Book book = new Book(); + Elements as = dl.getElementsByTag("a"); + book.setName(as.get(1).text()); + book.setAuthor(as.get(2).text()); + book.setType(kind.getName()); + book.setNewestChapterTitle(as.get(3).text()); + book.setDesc(dl.getElementsByClass("book_des").first().text()); + book.setImgUrl(as.first().getElementsByTag("img").attr("src")); + book.setChapterUrl(as.get(1).attr("href").replace("novel", "read").replace(".html", "/")); + book.setUpdateDate(dl.getElementsByClass("uptime").first().text()); + book.setSource(LocalBookSource.miaobi.toString()); + books.add(book); + } + return books; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler.java index 1a8b7eb..48c3578 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler.java @@ -1,5 +1,6 @@ package xyz.fycz.myreader.webapi.crawler.find; +import org.jetbrains.annotations.NotNull; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -8,41 +9,47 @@ import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.List; +import io.reactivex.Observable; +import io.reactivex.ObservableOnSubscribe; +import io.reactivex.ObservableSource; +import io.reactivex.functions.Function; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.entity.bookstore.FindBook; import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.util.help.StringHelper; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.util.utils.OkHttpUtils; +import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; - -public class QB5FindCrawler extends FindCrawler { - public static final String FIND_URL = "https://www.qb50.com"; - public static final String FIND_NAME = "书城[全本小说]"; - private static final String CHARSET = "GBK"; +/** + * @author fengyue + * @date 2021/7/22 17:34 + */ +public class QB5FindCrawler extends BaseFindCrawler { @Override - public String getCharset() { - return CHARSET; + public String getName() { + return "全本小说"; } @Override - public String getFindName() { - return FIND_NAME; + public String getTag() { + return "https://www.qb50.com"; } @Override - public String getFindUrl() { - return FIND_URL; - } - - @Override - public boolean hasImg() { - return false; - } - - @Override - public boolean needSearch() { - return false; + public Observable initData() { + return Observable.create((ObservableOnSubscribe) emitter -> { + emitter.onNext(OkHttpUtils.getStrResponse(getTag(), "gbk", null)); + emitter.onComplete(); + }).flatMap(response -> OkHttpUtils.setCookie(response, getTag())) + .flatMap((Function>) response -> Observable.create(emitter -> { + kindsMap.put(getName(), getBookKinds(response.body())); + emitter.onNext(true); + emitter.onComplete(); + })); } /** @@ -51,8 +58,8 @@ public class QB5FindCrawler extends FindCrawler { * @param html * @return */ - public List getBookTypes(String html) { - List bookTypes = new ArrayList<>(); + public List getBookKinds(String html) { + List kinds = new ArrayList<>(); Document doc = Jsoup.parse(html); Elements divs = doc.getElementsByClass("nav_cont"); if (divs.size() > 0) { @@ -60,59 +67,40 @@ public class QB5FindCrawler extends FindCrawler { if (uls.size() > 0) { for (Element li : uls.get(0).children()) { Element a = li.child(0); - BookType bookType = new BookType(); - bookType.setTypeName(a.attr("title")); - bookType.setUrl(a.attr("href")); - if (bookType.getTypeName().contains("首页") || bookType.getTypeName().contains("热门小说")) + FindKind kind = new FindKind(); + kind.setName(a.attr("title")); + kind.setUrl(a.attr("href")); + if (kind.getName().contains("首页") || kind.getName().contains("热门小说")) continue; - if (!StringHelper.isEmpty(bookType.getTypeName())) { - bookTypes.add(bookType); + if (!kind.getName().equals("完本小说")) { + kind.setUrl(kind.getUrl().substring(0, kind.getUrl().lastIndexOf("_") + 1) + "{page}" + "/"); + } else { + kind.setUrl(kind.getUrl().substring(0, kind.getUrl().lastIndexOf("/") + 1) + "{page}"); + } + if (!StringHelper.isEmpty(kind.getName())) { + kinds.add(kind); } } } } - return bookTypes; + return kinds; } - /** - * 获取某一分类小说排行榜列表 - * - * @param html - * @return - */ - public List getBookRankList(String html) { - List books = new ArrayList<>(); - Document doc = Jsoup.parse(html); - Elements divs = doc.getElementsByClass("r"); - if (divs.size() > 0) { - Elements uls = divs.get(0).getElementsByTag("ul"); - if (uls.size() > 0) { - for (Element li : uls.get(0).children()) { - Book book = new Book(); - Element scanS1 = li.getElementsByClass("s1").get(0); - Element scanS2 = li.getElementsByClass("s2").get(0); - Element scanS5 = li.getElementsByClass("s5").get(0); - book.setType(scanS1.html().replace("[", "").replace("]", "")); - Element a = scanS2.getElementsByTag("a").get(0); - book.setName(a.attr("title")); - book.setChapterUrl(a.attr("href")); - book.setAuthor(scanS5.html()); - book.setSource(LocalBookSource.biquge.toString()); - books.add(book); - } - } - } - - return books; + @Override + public Observable> getFindBooks(StrResponse strResponse, FindKind kind) { + return Observable.create(emitter -> { + emitter.onNext(getFindBooks(strResponse.body(), kind)); + emitter.onComplete(); + }); } - public List getFindBooks(String html, BookType bookType) { + public List getFindBooks(String html, FindKind kind) { List books = new ArrayList<>(); Document doc = Jsoup.parse(html); try { int pageSize = Integer.parseInt(doc.getElementsByClass("last").first().text()); - bookType.setPageSize(pageSize); + kind.setMaxPage(pageSize); } catch (Exception ignored) { } String type = doc.select("meta[name=keywords]").attr("content").replace(",全本小说网", ""); @@ -128,6 +116,7 @@ public class QB5FindCrawler extends FindCrawler { book.setType(type); book.setName(aName.attr("title")); book.setChapterUrl(aName.attr("href")); + book.setInfoUrl(aName.attr("href")); book.setNewestChapterTitle(divZz.text()); book.setAuthor(divAuthor.text()); book.setUpdateDate(divSj.text()); @@ -135,24 +124,6 @@ public class QB5FindCrawler extends FindCrawler { books.add(book); } } - return books; - - } - - public boolean getTypePage(BookType curType, int page) { - if (curType.getPageSize() <= 0) { - curType.setPageSize(10); - } - if (page > curType.getPageSize()) { - return true; - } - if (!curType.getTypeName().equals("完本小说")) { - curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + "/"); - } else { - curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page); - } - return false; } - } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler3.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler3.java new file mode 100644 index 0000000..fe78f25 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler3.java @@ -0,0 +1,126 @@ +package xyz.fycz.myreader.webapi.crawler.find; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.List; + +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.enums.LocalBookSource; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.util.help.StringHelper; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; + + +public class QB5FindCrawler3 extends FindCrawler3 { + public static final String FIND_URL = "https://www.qb50.com"; + public static final String FIND_NAME = "书城[全本小说]"; + private static final String CHARSET = "GBK"; + + @Override + public String getCharset() { + return CHARSET; + } + + @Override + public String getFindName() { + return FIND_NAME; + } + + @Override + public String getFindUrl() { + return FIND_URL; + } + + @Override + public boolean hasImg() { + return false; + } + + @Override + public boolean needSearch() { + return false; + } + + /** + * 获取书城小说分类列表 + * + * @param html + * @return + */ + public List getBookTypes(String html) { + List bookTypes = new ArrayList<>(); + Document doc = Jsoup.parse(html); + Elements divs = doc.getElementsByClass("nav_cont"); + if (divs.size() > 0) { + Elements uls = divs.get(0).getElementsByTag("ul"); + if (uls.size() > 0) { + for (Element li : uls.get(0).children()) { + Element a = li.child(0); + BookType bookType = new BookType(); + bookType.setTypeName(a.attr("title")); + bookType.setUrl(a.attr("href")); + if (bookType.getTypeName().contains("首页") || bookType.getTypeName().contains("热门小说")) + continue; + if (!StringHelper.isEmpty(bookType.getTypeName())) { + bookTypes.add(bookType); + } + } + } + + } + return bookTypes; + } + + + public List getFindBooks(String html, BookType bookType) { + List books = new ArrayList<>(); + Document doc = Jsoup.parse(html); + try { + int pageSize = Integer.parseInt(doc.getElementsByClass("last").first().text()); + bookType.setPageSize(pageSize); + } catch (Exception ignored) { + } + String type = doc.select("meta[name=keywords]").attr("content").replace(",全本小说网", ""); + Element div = doc.getElementById("tlist"); + Elements uls = div.getElementsByTag("ul"); + if (uls.size() > 0) { + for (Element li : uls.get(0).children()) { + Book book = new Book(); + Element aName = li.getElementsByClass("name").get(0); + Element divZz = li.getElementsByClass("zz").get(0); + Element divAuthor = li.getElementsByClass("author").get(0); + Element divSj = li.getElementsByClass("sj").get(0); + book.setType(type); + book.setName(aName.attr("title")); + book.setChapterUrl(aName.attr("href")); + book.setNewestChapterTitle(divZz.text()); + book.setAuthor(divAuthor.text()); + book.setUpdateDate(divSj.text()); + book.setSource(LocalBookSource.qb5.toString()); + books.add(book); + } + } + + return books; + } + + public boolean getTypePage(BookType curType, int page) { + if (curType.getPageSize() <= 0) { + curType.setPageSize(10); + } + if (page > curType.getPageSize()) { + return true; + } + if (!curType.getTypeName().equals("完本小说")) { + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("_") + 1) + page + "/"); + } else { + curType.setUrl(curType.getUrl().substring(0, curType.getUrl().lastIndexOf("/") + 1) + page); + } + return false; + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianFindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianFindCrawler.java new file mode 100644 index 0000000..dab4c9e --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianFindCrawler.java @@ -0,0 +1,238 @@ +package xyz.fycz.myreader.webapi.crawler.find; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import io.reactivex.Observable; +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.application.App; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.entity.bookstore.BookType; +import xyz.fycz.myreader.entity.bookstore.QDBook; +import xyz.fycz.myreader.entity.bookstore.RankBook; +import xyz.fycz.myreader.entity.bookstore.SortBook; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.util.SharedPreUtils; +import xyz.fycz.myreader.util.help.StringHelper; +import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; + +/** + * @author fengyue + * @date 2021/7/21 22:25 + */ +public class QiDianFindCrawler extends BaseFindCrawler { + private String sourceUrl = "https://m.qidian.com"; + private String rankUrl = "https://m.qidian.com/majax/rank/{rankName}list?_csrfToken={cookie}&gender={sex}&pageNum={page}&catId=-1"; + private String sortUrl = "https://m.qidian.com/majax/category/list?_csrfToken={cookie}&gender={sex}&pageNum={page}&orderBy=&catId={catId}&subCatId="; + private String[] sex = {"male", "female"}; + private String yuepiaoParam = "&yearmonth={yearmonth}"; + private String imgUrl = "https://bookcover.yuewen.com/qdbimg/349573/{bid}/150"; + private String defaultCookie = "eXRDlZxmRDLvFAmdgzqvwWAASrxxp2WkVlH4ZM7e"; + private String yearmonthFormat = "yyyyMM"; + private LinkedHashMap rankName = new LinkedHashMap<>(); + private LinkedHashMap sortName = new LinkedHashMap<>(); + private boolean isFemale; + + public QiDianFindCrawler(boolean isFemale) { + this.isFemale = isFemale; + } + + @Override + public String getName() { + return isFemale ? "起点女生网" : "起点中文网"; + } + + @Override + public String getTag() { + return sourceUrl; + } + + @Override + public boolean needSearch() { + return true; + } + + private void initMaleRankName() { + if (!isFemale) { + rankName.put("风云榜", "yuepiao"); + rankName.put("畅销榜", "hotsales"); + rankName.put("阅读榜", "readIndex"); + rankName.put("粉丝榜", "newfans"); + rankName.put("推荐榜", "rec"); + rankName.put("更新榜", "update"); + rankName.put("签约榜", "sign"); + rankName.put("新书榜", "newbook"); + rankName.put("新人榜", "newauthor"); + } else { + rankName.put("风云榜", "yuepiao"); + rankName.put("阅读榜", "readIndex"); + rankName.put("粉丝榜", "newfans"); + rankName.put("推荐榜", "rec"); + rankName.put("更新榜", "update"); + rankName.put("收藏榜", "collect"); + rankName.put("免费榜", "free"); + } + } + + private void initSortNames() { + /* + {value: -1, text: "全站"} + 1: {value: 21, text: "玄幻"} + 2: {value: 1, text: "奇幻"} + 3: {value: 2, text: "武侠"} + 4: {value: 22, text: "仙侠"} + 5: {value: 4, text: "都市"} + 6: {value: 15, text: "现实"} + 7: {value: 6, text: "军事"} + 8: {value: 5, text: "历史"} + 9: {value: 7, text: "游戏"} + 10: {value: 8, text: "体育"} + 11: {value: 9, text: "科幻"} + 12: {value: 10, text: "悬疑"} + 13: {value: 12, text: "轻小说"} + */ + if (!isFemale) { + sortName.put("玄幻小说", 21); + sortName.put("奇幻小说", 1); + sortName.put("武侠小说", 2); + sortName.put("都市小说", 4); + sortName.put("现实小说", 15); + sortName.put("军事小说", 6); + sortName.put("历史小说", 5); + sortName.put("体育小说", 8); + sortName.put("科幻小说", 9); + sortName.put("悬疑小说", 10); + sortName.put("轻小说", 12); + sortName.put("短篇小说", 20076); + } else { + sortName.put("古代言情", 80); + sortName.put("仙侠奇缘", 81); + sortName.put("现代言情", 82); + sortName.put("烂漫青春", 83); + sortName.put("玄幻言情", 84); + sortName.put("悬疑推理", 85); + sortName.put("短篇小说", 30083); + sortName.put("科幻空间", 86); + sortName.put("游戏竞技", 88); + sortName.put("轻小说", 87); + sortName.put("现实生活", 30120); + } + } + + private List initKinds(boolean isSort) { + Set names = !isSort ? rankName.keySet() : sortName.keySet(); + List kinds = new ArrayList<>(); + for (String name : names) { + FindKind kind = new FindKind(); + kind.setName(name); + String url; + if (!isSort) { + url = rankUrl.replace("{rankName}", rankName.get(name)); + kind.setMaxPage(30); + } else { + url = sortUrl.replace("{catId}", sortName.get(name) + ""); + kind.setMaxPage(5); + } + url = url.replace("{sex}", !isFemale ? sex[0] : sex[1]); + SharedPreUtils spu = SharedPreUtils.getInstance(); + String cookie = spu.getString(App.getmContext().getString(R.string.qdCookie), ""); + if (!cookie.equals("")) { + url = url.replace("{cookie}", StringHelper.getSubString(cookie, "_csrfToken=", ";")); + } else { + url = url.replace("{cookie}", defaultCookie); + } + if ("风云榜".equals(name)) { + SimpleDateFormat sdf = new SimpleDateFormat(yearmonthFormat, Locale.CHINA); + String yearmonth = sdf.format(new Date()); + url = url + yuepiaoParam.replace("{yearmonth}", yearmonth); + } + kind.setUrl(url); + kinds.add(kind); + } + return kinds; + } + + @Override + public Observable initData() { + return Observable.create(emitter -> { + initMaleRankName(); + initSortNames(); + kindsMap.put("排行榜", initKinds(false)); + kindsMap.put("分类", initKinds(true)); + emitter.onNext(true); + emitter.onComplete(); + }); + } + + @Override + public Observable> getFindBooks(StrResponse strResponse, FindKind kind) { + return Observable.create(emitter -> { + List qdBooks = getBooksFromJson(strResponse.body()); + emitter.onNext(convertQDBook2Book(qdBooks)); + emitter.onComplete(); + }); + } + + private List getBooksFromJson(String json) throws JSONException { + List books = new ArrayList<>(); + JSONObject all = new JSONObject(json); + JSONObject data = all.getJSONObject("data"); + int total = data.getInt("total"); + JSONArray jsonBooks = data.getJSONArray("records"); + for (int i = 0; i < jsonBooks.length(); i++) { + JSONObject jsonBook = jsonBooks.getJSONObject(i); + boolean isSort = jsonBook.has("state"); + QDBook book = !isSort ? new RankBook() : new SortBook(); + book.setbName(jsonBook.getString("bName")); + book.setbAuth(jsonBook.getString("bAuth")); + book.setBid(jsonBook.getString("bid")); + book.setCat(jsonBook.getString("cat")); + book.setCatId(jsonBook.getInt("catId")); + book.setCnt(jsonBook.getString("cnt")); + book.setDesc(jsonBook.getString("desc")); + book.setImg(imgUrl.replace("{bid}", jsonBook.getString("bid"))); + if (!isSort) { + ((RankBook) book).setRankCnt(jsonBook.getString("rankCnt")); + ((RankBook) book).setRankNum(jsonBook.getInt("rankNum")); + } else { + ((SortBook) book).setState(jsonBook.getString("state")); + } + books.add(book); + } + return books; + } + + private List convertQDBook2Book(List qdBooks) { + List books = new ArrayList<>(); + for (QDBook rb : qdBooks) { + Book book = new Book(); + book.setName(rb.getbName()); + book.setAuthor(rb.getbAuth()); + book.setImgUrl(rb.getImg()); + String cat = rb.getCat(); + book.setType(cat.contains("小说") || cat.length() >= 4 ? cat : cat + "小说"); +// book.setNewestChapterTitle(rb.getDesc()); + book.setDesc(rb.getDesc()); + if (rb instanceof RankBook) { + boolean hasRankCnt = !((RankBook) rb).getRankCnt().equals("null"); + book.setWordCount(rb.getCnt()); + book.setStatus(hasRankCnt ? ((RankBook) rb).getRankCnt() : book.getType()); + } else if (rb instanceof SortBook) { + book.setWordCount(rb.getCnt()); + book.setStatus(((SortBook) rb).getState()); + } + books.add(book); + } + return books; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java index 1307eb7..ec4b155 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java @@ -23,7 +23,7 @@ import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.util.IOUtils; import xyz.fycz.myreader.util.SharedPreUtils; import xyz.fycz.myreader.util.help.StringHelper; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; import java.io.BufferedReader; import java.io.IOException; @@ -35,7 +35,7 @@ import java.util.*; * @author fengyue * @date 2020/9/16 22:01 */ -public class QiDianMobileRank extends FindCrawler { +public class QiDianMobileRank extends FindCrawler3 { private String rankUrl = "https://m.qidian.com/majax/rank/{rankName}list?_csrfToken={cookie}&gender={sex}&pageNum={page}&catId=-1"; private String sortUrl = "https://m.qidian.com/majax/category/list?_csrfToken={cookie}&gender={sex}&pageNum={page}&orderBy=&catId={catId}&subCatId="; private String[] sex = {"male", "female"}; @@ -83,19 +83,19 @@ public class QiDianMobileRank extends FindCrawler { private void initSortNames() { /* {value: -1, text: "全站"} -1: {value: 21, text: "玄幻"} -2: {value: 1, text: "奇幻"} -3: {value: 2, text: "武侠"} -4: {value: 22, text: "仙侠"} -5: {value: 4, text: "都市"} -6: {value: 15, text: "现实"} -7: {value: 6, text: "军事"} -8: {value: 5, text: "历史"} -9: {value: 7, text: "游戏"} -10: {value: 8, text: "体育"} -11: {value: 9, text: "科幻"} -12: {value: 10, text: "悬疑"} -13: {value: 12, text: "轻小说"} + 1: {value: 21, text: "玄幻"} + 2: {value: 1, text: "奇幻"} + 3: {value: 2, text: "武侠"} + 4: {value: 22, text: "仙侠"} + 5: {value: 4, text: "都市"} + 6: {value: 15, text: "现实"} + 7: {value: 6, text: "军事"} + 8: {value: 5, text: "历史"} + 9: {value: 7, text: "游戏"} + 10: {value: 8, text: "体育"} + 11: {value: 9, text: "科幻"} + 12: {value: 10, text: "悬疑"} + 13: {value: 12, text: "轻小说"} */ if (!isFemale) { sortName.put("玄幻小说", 21); diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7FindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7FindCrawler.java index dc481ad..6c0781d 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7FindCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7FindCrawler.java @@ -11,10 +11,10 @@ import java.util.List; import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.enums.LocalBookSource; import xyz.fycz.myreader.greendao.entity.Book; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; -public class XS7FindCrawler extends FindCrawler{ +public class XS7FindCrawler extends FindCrawler3 { public static final String NAME_SPACE = "https://www.xs7.la"; public static final String CHARSET = "GBK"; public static final String SEARCH_CHARSET = "GBK"; diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java index 869c533..06e4f93 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java @@ -10,8 +10,7 @@ import java.util.List; import xyz.fycz.myreader.entity.bookstore.BookType; import xyz.fycz.myreader.greendao.entity.Book; -import xyz.fycz.myreader.webapi.crawler.base.FindCrawler; -import xyz.fycz.myreader.webapi.crawler.read.XS7ReadCrawler; +import xyz.fycz.myreader.webapi.crawler.base.FindCrawler3; /** * @author fengyue @@ -19,8 +18,8 @@ import xyz.fycz.myreader.webapi.crawler.read.XS7ReadCrawler; * 已失效 */ @Deprecated -public class XS7Rank extends FindCrawler { - private FindCrawler xs7 = new XS7FindCrawler(); +public class XS7Rank extends FindCrawler3 { + private FindCrawler3 xs7 = new XS7FindCrawler(); public static final String CHARSET = "GBK"; public static final String FIND_NAME = "排行榜[小说旗]"; public static final String FIND_URL = "https://www.xs7.la/top/lastupdate/1.html"; diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/BiQuGeReadCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/BiQuGeReadCrawler.java index de23239..76ad564 100644 --- a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/BiQuGeReadCrawler.java +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/BiQuGeReadCrawler.java @@ -79,6 +79,7 @@ public class BiQuGeReadCrawler extends BaseReadCrawler implements BookInfoCrawle } } + return bookTypes; } diff --git a/app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/ThirdFindCrawler.java b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/ThirdFindCrawler.java new file mode 100644 index 0000000..4a19f62 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/ThirdFindCrawler.java @@ -0,0 +1,120 @@ +package xyz.fycz.myreader.webapi.crawler.source.find; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; + +import javax.script.SimpleBindings; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; +import xyz.fycz.myreader.entity.FindKind; +import xyz.fycz.myreader.entity.StrResponse; +import xyz.fycz.myreader.greendao.entity.Book; +import xyz.fycz.myreader.greendao.entity.rule.BookSource; +import xyz.fycz.myreader.greendao.entity.rule.FindRule; +import xyz.fycz.myreader.model.sourceAnalyzer.BookSourceManager; +import xyz.fycz.myreader.model.third2.analyzeRule.AnalyzeRule; +import xyz.fycz.myreader.util.utils.StringUtils; +import xyz.fycz.myreader.webapi.crawler.base.BaseFindCrawler; + +import static xyz.fycz.myreader.common.APPCONST.SCRIPT_ENGINE; + +/** + * @author fengyue + * @date 2021/7/22 22:30 + */ +public class ThirdFindCrawler extends BaseFindCrawler { + private BookSource source; + private FindRule findRuleBean; + private AnalyzeRule analyzeRule; + private String findError = "发现规则语法错误"; + + public ThirdFindCrawler(BookSource source) { + this.source = source; + findRuleBean = source.getFindRule(); + } + + public BookSource getSource() { + return source; + } + + @Override + public String getName() { + return source.getSourceName(); + } + + @Override + public String getTag() { + return source.getSourceUrl(); + } + + @Override + public Observable initData() { + return Observable.create(emitter -> { + try { + String[] kindA; + String findRule; + if (!TextUtils.isEmpty(findRuleBean.getUrl()) && !source.containsGroup(findError)) { + boolean isJs = findRuleBean.getUrl().startsWith(""); + if (isJs) { + String jsStr = findRuleBean.getUrl().substring(4, findRuleBean.getUrl().lastIndexOf("<")); + findRule = evalJS(jsStr, source.getSourceUrl()).toString(); + } else { + findRule = findRuleBean.getUrl(); + } + kindA = findRule.split("(&&|\n)+"); + List children = new ArrayList<>(); + String groupName = getName(); + for (String kindB : kindA) { + if (kindB.trim().isEmpty()) continue; + String[] kind = kindB.split("::"); + if (kind.length == 1){ + if (children.size() > 0) { + kindsMap.put(groupName, children); + children = new ArrayList<>(); + } + groupName = kind[0].replaceAll("\\s", ""); + continue; + } + FindKind findKindBean = new FindKind(); + findKindBean.setTag(source.getSourceUrl()); + findKindBean.setName(kind[0]); + findKindBean.setUrl(kind[1]); + children.add(findKindBean); + } + kindsMap.put(groupName, children); + } + emitter.onNext(true); + } catch (Exception exception) { + source.addGroup(findError); + BookSourceManager.addBookSource(source); + emitter.onNext(false); + } + emitter.onComplete(); + }); + } + + @Override + public Observable> getFindBooks(StrResponse strResponse, FindKind kind) { + return null; + } + + /** + * 执行JS + */ + private Object evalJS(String jsStr, String baseUrl) throws Exception { + SimpleBindings bindings = new SimpleBindings(); + bindings.put("java", getAnalyzeRule()); + bindings.put("baseUrl", baseUrl); + return SCRIPT_ENGINE.eval(jsStr, bindings); + } + + private AnalyzeRule getAnalyzeRule() { + if (analyzeRule == null) { + analyzeRule = new AnalyzeRule(null); + } + return analyzeRule; + } +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/RefreshLayout.java b/app/src/main/java/xyz/fycz/myreader/widget/RefreshLayout.java index 4faad4d..623278c 100644 --- a/app/src/main/java/xyz/fycz/myreader/widget/RefreshLayout.java +++ b/app/src/main/java/xyz/fycz/myreader/widget/RefreshLayout.java @@ -224,10 +224,16 @@ public class RefreshLayout extends FrameLayout { @Override protected void onRestoreInstanceState(Parcelable state) { - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - //刷新状态 - toggleStatus(savedState.status); + if (state instanceof SavedState) { + SavedState savedState = (SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + //刷新状态 + toggleStatus(savedState.status); + }else { + super.onRestoreInstanceState(state); + //刷新状态 + toggleStatus(STATUS_LOADING); + } } static class SavedState extends BaseSavedState { diff --git a/app/src/main/java/xyz/fycz/myreader/widget/loading/Indicator.java b/app/src/main/java/xyz/fycz/myreader/widget/loading/Indicator.java new file mode 100644 index 0000000..45f6576 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/loading/Indicator.java @@ -0,0 +1,201 @@ +package xyz.fycz.myreader.widget.loading; + +import android.animation.ValueAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by Jack Wang on 2016/8/5. + */ + +public abstract class Indicator extends Drawable implements Animatable { + + private HashMap mUpdateListeners=new HashMap<>(); + + private ArrayList mAnimators; + private int alpha = 255; + private static final Rect ZERO_BOUNDS_RECT = new Rect(); + protected Rect drawBounds = ZERO_BOUNDS_RECT; + + private boolean mHasAnimators; + + private Paint mPaint=new Paint(); + + public Indicator(){ + mPaint.setColor(Color.WHITE); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAntiAlias(true); + } + + public int getColor() { + return mPaint.getColor(); + } + + public void setColor(int color) { + mPaint.setColor(color); + } + + @Override + public void setAlpha(int alpha) { + this.alpha = alpha; + } + + @Override + public int getAlpha() { + return alpha; + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public void draw(Canvas canvas) { + draw(canvas,mPaint); + } + + public abstract void draw(Canvas canvas, Paint paint); + + public abstract ArrayList onCreateAnimators(); + + @Override + public void start() { + ensureAnimators(); + + if (mAnimators == null) { + return; + } + + // If the animators has not ended, do nothing. + if (isStarted()) { + return; + } + startAnimators(); + invalidateSelf(); + } + + private void startAnimators() { + for (int i = 0; i < mAnimators.size(); i++) { + ValueAnimator animator = mAnimators.get(i); + + //when the animator restart , add the updateListener again because they + // was removed by animator stop . + ValueAnimator.AnimatorUpdateListener updateListener=mUpdateListeners.get(animator); + if (updateListener!=null){ + animator.addUpdateListener(updateListener); + } + + animator.start(); + } + } + + private void stopAnimators() { + if (mAnimators!=null){ + for (ValueAnimator animator : mAnimators) { + if (animator != null && animator.isStarted()) { + animator.removeAllUpdateListeners(); + animator.end(); + } + } + } + } + + private void ensureAnimators() { + if (!mHasAnimators) { + mAnimators = onCreateAnimators(); + mHasAnimators = true; + } + } + + @Override + public void stop() { + stopAnimators(); + } + + private boolean isStarted() { + for (ValueAnimator animator : mAnimators) { + return animator.isStarted(); + } + return false; + } + + @Override + public boolean isRunning() { + for (ValueAnimator animator : mAnimators) { + return animator.isRunning(); + } + return false; + } + + /** + * Your should use this to add AnimatorUpdateListener when + * create animator , otherwise , animator doesn't work when + * the animation restart . + * @param updateListener + */ + public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener){ + mUpdateListeners.put(animator,updateListener); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + setDrawBounds(bounds); + } + + public void setDrawBounds(Rect drawBounds) { + setDrawBounds(drawBounds.left, drawBounds.top, drawBounds.right, drawBounds.bottom); + } + + public void setDrawBounds(int left, int top, int right, int bottom) { + this.drawBounds = new Rect(left, top, right, bottom); + } + + public void postInvalidate(){ + invalidateSelf(); + } + + public Rect getDrawBounds() { + return drawBounds; + } + + public int getWidth(){ + return drawBounds.width(); + } + + public int getHeight(){ + return drawBounds.height(); + } + + public int centerX(){ + return drawBounds.centerX(); + } + + public int centerY(){ + return drawBounds.centerY(); + } + + public float exactCenterX(){ + return drawBounds.exactCenterX(); + } + + public float exactCenterY(){ + return drawBounds.exactCenterY(); + } + +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/loading/LoadingIndicatorView.java b/app/src/main/java/xyz/fycz/myreader/widget/loading/LoadingIndicatorView.java new file mode 100644 index 0000000..257df37 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/loading/LoadingIndicatorView.java @@ -0,0 +1,420 @@ +package xyz.fycz.myreader.widget.loading; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.animation.AnimationUtils; + +import xyz.fycz.myreader.R; +import xyz.fycz.myreader.widget.loading.indicators.BallTrianglePathIndicator; + + +public class LoadingIndicatorView extends View { + + private static final String TAG="LoadingIndicatorView"; + + private static final BallTrianglePathIndicator DEFAULT_INDICATOR=new BallTrianglePathIndicator(); + + private static final int MIN_SHOW_TIME = 500; // ms + private static final int MIN_DELAY = 500; // ms + + private long mStartTime = -1; + + private boolean mPostedHide = false; + + private boolean mPostedShow = false; + + private boolean mDismissed = false; + + private final Runnable mDelayedHide = new Runnable() { + + @Override + public void run() { + mPostedHide = false; + mStartTime = -1; + setVisibility(View.GONE); + } + }; + + private final Runnable mDelayedShow = new Runnable() { + + @Override + public void run() { + mPostedShow = false; + if (!mDismissed) { + mStartTime = System.currentTimeMillis(); + setVisibility(View.VISIBLE); + } + } + }; + + int mMinWidth; + int mMaxWidth; + int mMinHeight; + int mMaxHeight; + + private Indicator mIndicator; + private int mIndicatorColor; + + private boolean mShouldStartAnimationDrawable; + + public LoadingIndicatorView(Context context) { + super(context); + init(context, null,0,0); + } + + public LoadingIndicatorView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs,0,R.style.LoadingIndicatorView); + } + + public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs,defStyleAttr, R.style.LoadingIndicatorView); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context,attrs,defStyleAttr,R.style.LoadingIndicatorView); + } + + private void init(Context context,AttributeSet attrs,int defStyleAttr, int defStyleRes) { + mMinWidth = 24; + mMaxWidth = 48; + mMinHeight = 24; + mMaxHeight = 48; + + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.AVLoadingIndicatorView, defStyleAttr, defStyleRes); + + mMinWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minWidth, mMinWidth); + mMaxWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxWidth, mMaxWidth); + mMinHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minHeight, mMinHeight); + mMaxHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxHeight, mMaxHeight); + String indicatorName=a.getString(R.styleable.AVLoadingIndicatorView_indicatorName); + mIndicatorColor=a.getColor(R.styleable.AVLoadingIndicatorView_indicatorColor, Color.WHITE); + setIndicator(indicatorName); + if (mIndicator==null){ + setIndicator(DEFAULT_INDICATOR); + } + a.recycle(); + } + + public Indicator getIndicator() { + return mIndicator; + } + + public void setIndicator(Indicator d) { + if (mIndicator != d) { + if (mIndicator != null) { + mIndicator.setCallback(null); + unscheduleDrawable(mIndicator); + } + + mIndicator = d; + //need to set indicator color again if you didn't specified when you update the indicator . + setIndicatorColor(mIndicatorColor); + if (d != null) { + d.setCallback(this); + } + postInvalidate(); + } + } + + + /** + * setIndicatorColor(0xFF00FF00) + * or + * setIndicatorColor(Color.BLUE) + * or + * setIndicatorColor(Color.parseColor("#FF4081")) + * or + * setIndicatorColor(0xFF00FF00) + * or + * setIndicatorColor(getResources().getColor(android.R.color.black)) + * @param color + */ + public void setIndicatorColor(int color){ + this.mIndicatorColor=color; + mIndicator.setColor(color); + } + + + /** + * You should pay attention to pass this parameter with two way: + * for example: + * 1. Only class Name,like "SimpleIndicator".(This way would use default package name with + * "com.wang.avi.indicators") + * 2. Class name with full package,like "com.my.android.indicators.SimpleIndicator". + * @param indicatorName the class must be extend Indicator . + */ + public void setIndicator(String indicatorName){ + if (TextUtils.isEmpty(indicatorName)){ + return; + } + StringBuilder drawableClassName=new StringBuilder(); + if (!indicatorName.contains(".")){ + String defaultPackageName=getClass().getPackage().getName(); + drawableClassName.append(defaultPackageName) + .append(".indicators") + .append("."); + } + drawableClassName.append(indicatorName); + try { + Class drawableClass = Class.forName(drawableClassName.toString()); + Indicator indicator = (Indicator) drawableClass.newInstance(); + setIndicator(indicator); + } catch (ClassNotFoundException e) { + Log.e(TAG,"Didn't find your class , check the name again !"); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + public void smoothToShow(){ + startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_in)); + setVisibility(VISIBLE); + } + + public void smoothToHide(){ + startAnimation(AnimationUtils.loadAnimation(getContext(),android.R.anim.fade_out)); + setVisibility(GONE); + } + + public void hide() { + mDismissed = true; + removeCallbacks(mDelayedShow); + long diff = System.currentTimeMillis() - mStartTime; + if (diff >= MIN_SHOW_TIME || mStartTime == -1) { + // The progress spinner has been shown long enough + // OR was not shown yet. If it wasn't shown yet, + // it will just never be shown. + setVisibility(View.GONE); + } else { + // The progress spinner is shown, but not long enough, + // so put a delayed message in to hide it when its been + // shown long enough. + if (!mPostedHide) { + postDelayed(mDelayedHide, MIN_SHOW_TIME - diff); + mPostedHide = true; + } + } + } + + public void show() { + // Reset the start time. + mStartTime = -1; + mDismissed = false; + removeCallbacks(mDelayedHide); + if (!mPostedShow) { + postDelayed(mDelayedShow, MIN_DELAY); + mPostedShow = true; + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mIndicator + || super.verifyDrawable(who); + } + + void startAnimation() { + if (getVisibility() != VISIBLE) { + return; + } + + if (mIndicator instanceof Animatable) { + mShouldStartAnimationDrawable = true; + } + postInvalidate(); + } + + void stopAnimation() { + if (mIndicator instanceof Animatable) { + mIndicator.stop(); + mShouldStartAnimationDrawable = false; + } + postInvalidate(); + } + + @Override + public void setVisibility(int v) { + if (getVisibility() != v) { + super.setVisibility(v); + if (v == GONE || v == INVISIBLE) { + stopAnimation(); + } else { + startAnimation(); + } + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility == GONE || visibility == INVISIBLE) { + stopAnimation(); + } else { + startAnimation(); + } + } + + @Override + public void invalidateDrawable(Drawable dr) { + if (verifyDrawable(dr)) { + final Rect dirty = dr.getBounds(); + final int scrollX = getScrollX() + getPaddingLeft(); + final int scrollY = getScrollY() + getPaddingTop(); + + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); + } else { + super.invalidateDrawable(dr); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + updateDrawableBounds(w, h); + } + + private void updateDrawableBounds(int w, int h) { + // onDraw will translate the canvas so we draw starting at 0,0. + // Subtract out padding for the purposes of the calculations below. + w -= getPaddingRight() + getPaddingLeft(); + h -= getPaddingTop() + getPaddingBottom(); + + int right = w; + int bottom = h; + int top = 0; + int left = 0; + + if (mIndicator != null) { + // Maintain aspect ratio. Certain kinds of animated drawables + // get very confused otherwise. + final int intrinsicWidth = mIndicator.getIntrinsicWidth(); + final int intrinsicHeight = mIndicator.getIntrinsicHeight(); + final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; + final float boundAspect = (float) w / h; + if (intrinsicAspect != boundAspect) { + if (boundAspect > intrinsicAspect) { + // New width is larger. Make it smaller to match height. + final int width = (int) (h * intrinsicAspect); + left = (w - width) / 2; + right = left + width; + } else { + // New height is larger. Make it smaller to match width. + final int height = (int) (w * (1 / intrinsicAspect)); + top = (h - height) / 2; + bottom = top + height; + } + } + mIndicator.setBounds(left, top, right, bottom); + } + } + + @Override + protected synchronized void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawTrack(canvas); + } + + void drawTrack(Canvas canvas) { + final Drawable d = mIndicator; + if (d != null) { + // Translate canvas so a indeterminate circular progress bar with padding + // rotates properly in its animation + final int saveCount = canvas.save(); + + canvas.translate(getPaddingLeft(), getPaddingTop()); + + d.draw(canvas); + canvas.restoreToCount(saveCount); + + if (mShouldStartAnimationDrawable && d instanceof Animatable) { + ((Animatable) d).start(); + mShouldStartAnimationDrawable = false; + } + } + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int dw = 0; + int dh = 0; + + final Drawable d = mIndicator; + if (d != null) { + dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); + dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); + } + + updateDrawableState(); + + dw += getPaddingLeft() + getPaddingRight(); + dh += getPaddingTop() + getPaddingBottom(); + + final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); + final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + updateDrawableState(); + } + + private void updateDrawableState() { + final int[] state = getDrawableState(); + if (mIndicator != null && mIndicator.isStateful()) { + mIndicator.setState(state); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + + if (mIndicator != null) { + mIndicator.setHotspot(x, y); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + startAnimation(); + removeCallbacks(); + } + + @Override + protected void onDetachedFromWindow() { + stopAnimation(); + // This should come after stopAnimation(), otherwise an invalidate message remains in the + // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation + super.onDetachedFromWindow(); + removeCallbacks(); + } + + private void removeCallbacks() { + removeCallbacks(mDelayedHide); + removeCallbacks(mDelayedShow); + } + + +} diff --git a/app/src/main/java/xyz/fycz/myreader/widget/loading/indicators/BallTrianglePathIndicator.java b/app/src/main/java/xyz/fycz/myreader/widget/loading/indicators/BallTrianglePathIndicator.java new file mode 100644 index 0000000..7cf4e75 --- /dev/null +++ b/app/src/main/java/xyz/fycz/myreader/widget/loading/indicators/BallTrianglePathIndicator.java @@ -0,0 +1,80 @@ +package xyz.fycz.myreader.widget.loading.indicators; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.animation.LinearInterpolator; + +import android.animation.ValueAnimator; +import xyz.fycz.myreader.widget.loading.Indicator; + +import java.util.ArrayList; + +/** + * Created by Jack on 2015/10/19. + */ +public class BallTrianglePathIndicator extends Indicator { + + float[] translateX=new float[3],translateY=new float[3]; + + @Override + public void draw(Canvas canvas, Paint paint) { + paint.setStrokeWidth(3); + paint.setStyle(Paint.Style.STROKE); + for (int i = 0; i < 3; i++) { + canvas.save(); + canvas.translate(translateX[i], translateY[i]); + canvas.drawCircle(0, 0, getWidth() / 10, paint); + canvas.restore(); + } + } + + @Override + public ArrayList onCreateAnimators() { + ArrayList animators=new ArrayList<>(); + float startX=getWidth()/5; + float startY=getWidth()/5; + for (int i = 0; i < 3; i++) { + final int index=i; + ValueAnimator translateXAnim=ValueAnimator.ofFloat(getWidth()/2,getWidth()-startX,startX,getWidth()/2); + if (i==1){ + translateXAnim=ValueAnimator.ofFloat(getWidth()-startX,startX,getWidth()/2,getWidth()-startX); + }else if (i==2){ + translateXAnim=ValueAnimator.ofFloat(startX,getWidth()/2,getWidth()-startX,startX); + } + ValueAnimator translateYAnim=ValueAnimator.ofFloat(startY,getHeight()-startY,getHeight()-startY,startY); + if (i==1){ + translateYAnim=ValueAnimator.ofFloat(getHeight()-startY,getHeight()-startY,startY,getHeight()-startY); + }else if (i==2){ + translateYAnim=ValueAnimator.ofFloat(getHeight()-startY,startY,getHeight()-startY,getHeight()-startY); + } + + translateXAnim.setDuration(2000); + translateXAnim.setInterpolator(new LinearInterpolator()); + translateXAnim.setRepeatCount(-1); + addUpdateListener(translateXAnim,new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + translateX [index]= (float) animation.getAnimatedValue(); + postInvalidate(); + } + }); + + translateYAnim.setDuration(2000); + translateYAnim.setInterpolator(new LinearInterpolator()); + translateYAnim.setRepeatCount(-1); + addUpdateListener(translateYAnim,new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + translateY [index]= (float) animation.getAnimatedValue(); + postInvalidate(); + } + }); + + animators.add(translateXAnim); + animators.add(translateYAnim); + } + return animators; + } + + +} diff --git a/app/src/main/res/drawable/ic_leaderboard.xml b/app/src/main/res/drawable/ic_leaderboard.xml deleted file mode 100644 index 0574938..0000000 --- a/app/src/main/res/drawable/ic_leaderboard.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml deleted file mode 100644 index ed0a932..0000000 --- a/app/src/main/res/drawable/ic_sort.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/activity_find_book.xml b/app/src/main/res/layout/activity_find_book.xml new file mode 100644 index 0000000..96b356f --- /dev/null +++ b/app/src/main/res/layout/activity_find_book.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_find.xml b/app/src/main/res/layout/fragment_find.xml index ee3f05a..87124c8 100644 --- a/app/src/main/res/layout/fragment_find.xml +++ b/app/src/main/res/layout/fragment_find.xml @@ -66,88 +66,6 @@ - - - - - - - - - - - - - - - - - - - - - - - + android:src="@drawable/ic_bookstore" /> - - - - - - - - - - - - --> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_find_book_1.xml b/app/src/main/res/layout/fragment_find_book_1.xml new file mode 100644 index 0000000..90f7e95 --- /dev/null +++ b/app/src/main/res/layout/fragment_find_book_1.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_find_book_2.xml b/app/src/main/res/layout/fragment_find_book_2.xml new file mode 100644 index 0000000..f890ec8 --- /dev/null +++ b/app/src/main/res/layout/fragment_find_book_2.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_find_source.xml b/app/src/main/res/layout/item_find_source.xml new file mode 100644 index 0000000..c2d9b59 --- /dev/null +++ b/app/src/main/res/layout/item_find_source.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/layout/listview_book_store_book_item.xml b/app/src/main/res/layout/listview_book_store_book_item.xml index e64c3c3..0bbd89a 100644 --- a/app/src/main/res/layout/listview_book_store_book_item.xml +++ b/app/src/main/res/layout/listview_book_store_book_item.xml @@ -1,92 +1,96 @@ - + + android:id="@+id/iv_book_img" + android:layout_width="42dp" + android:layout_height="60dp" + android:scaleType="fitXY" + android:visibility="gone" + app:srcCompat="@mipmap/default_cover" /> + + + android:orientation="horizontal" + android:padding="2dp"> - - + android:id="@+id/tv_book_name" + android:layout_width="wrap_content" + android:layout_height="18dp" + android:text="bookname" + android:textColor="@color/textPrimary" + android:textSize="15sp" /> - - + android:paddingStart="5dp" + android:text="author" + android:textColor="@color/textSecondary" + android:textSize="12sp" /> + + + + + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="2dp"> + android:id="@+id/tv_book_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:text="time" + android:textColor="@color/textSecondary" + android:textSize="12sp" /> + + android:id="@+id/tv_book_source" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:maxLines="1" + android:text="book source" + android:textColor="@color/textSecondary" + android:textSize="12sp" /> + android:layout_width="fill_parent" + android:layout_height="0.5dp" + android:background="@color/colorDivider" /> \ No newline at end of file diff --git a/app/src/main/res/layout/view_loading.xml b/app/src/main/res/layout/view_loading.xml index 03e09ce..d25b03c 100644 --- a/app/src/main/res/layout/view_loading.xml +++ b/app/src/main/res/layout/view_loading.xml @@ -1,20 +1,22 @@ + android:layout_width="match_parent" + android:layout_height="match_parent"> - + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/loading" + android:layout_centerInParent="true" + android:paddingTop="10dp" + android:text="@string/loading_tip" + android:textSize="@dimen/text_normal_size" + android:textColor="@color/textPrimary" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_store.xml b/app/src/main/res/menu/menu_store.xml index 206aa7f..fc5cd8e 100644 --- a/app/src/main/res/menu/menu_store.xml +++ b/app/src/main/res/menu/menu_store.xml @@ -1,12 +1,6 @@ - + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 34b4917..ae8d236 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,6 +31,7 @@ + - - - - - + + + + + - - - - - + + + + + - - + + + - @@ -193,15 +196,15 @@ - - - + + @@ -241,42 +244,64 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + - + Add Tag - + + + + + + + + + +