新增自定义发现

master
fengyuecanzhu 3 years ago
parent a80eed8c11
commit 054b8cb325
  1. 36
      .idea/inspectionProfiles/Project_Default.xml
  2. 10
      .idea/runConfigurations.xml
  3. 2
      app/build.gradle
  4. 1
      app/src/main/AndroidManifest.xml
  5. 2
      app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java
  6. 86
      app/src/main/java/xyz/fycz/myreader/base/LazyFragment.kt
  7. 45
      app/src/main/java/xyz/fycz/myreader/entity/FindKind.java
  8. 4
      app/src/main/java/xyz/fycz/myreader/entity/StrResponse.java
  9. 6
      app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
  10. 2
      app/src/main/java/xyz/fycz/myreader/ui/activity/AdSettingActivity.java
  11. 6
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  12. 45
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java
  13. 162
      app/src/main/java/xyz/fycz/myreader/ui/activity/FindBookActivity.java
  14. 5
      app/src/main/java/xyz/fycz/myreader/ui/activity/QRCodeScanActivity.java
  15. 5
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
  16. 13
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReadRecordActivity.java
  17. 9
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java
  18. 9
      app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
  19. 1
      app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java
  20. 154
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java
  21. 36
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindSourceHolder.java
  22. 25
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
  23. 13
      app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java
  24. 66
      app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook1Fragment.java
  25. 177
      app/src/main/java/xyz/fycz/myreader/ui/fragment/FindBook2Fragment.java
  26. 98
      app/src/main/java/xyz/fycz/myreader/ui/fragment/FindFragment.java
  27. 5
      app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java
  28. 5
      app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
  29. 5
      app/src/main/java/xyz/fycz/myreader/ui/fragment/WebDavFragment.java
  30. 16
      app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
  31. 1
      app/src/main/java/xyz/fycz/myreader/ui/presenter/CatalogPresenter.java
  32. 18
      app/src/main/java/xyz/fycz/myreader/webapi/BookApi.java
  33. 18
      app/src/main/java/xyz/fycz/myreader/webapi/BookStoreApi.java
  34. 19
      app/src/main/java/xyz/fycz/myreader/webapi/ThirdSourceApi.java
  35. 43
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/BaseFindCrawler.java
  36. 39
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler.java
  37. 31
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/base/FindCrawler3.java
  38. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/Ben100FindCrawler.java
  39. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiFindCrawler.java
  40. 93
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/MiaoBiGeFindCrawler.java
  41. 131
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler.java
  42. 126
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QB5FindCrawler3.java
  43. 238
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianFindCrawler.java
  44. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/QiDianMobileRank.java
  45. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7FindCrawler.java
  46. 7
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/find/XS7Rank.java
  47. 1
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/read/BiQuGeReadCrawler.java
  48. 120
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/ThirdFindCrawler.java
  49. 6
      app/src/main/java/xyz/fycz/myreader/widget/RefreshLayout.java
  50. 201
      app/src/main/java/xyz/fycz/myreader/widget/loading/Indicator.java
  51. 420
      app/src/main/java/xyz/fycz/myreader/widget/loading/LoadingIndicatorView.java
  52. 80
      app/src/main/java/xyz/fycz/myreader/widget/loading/indicators/BallTrianglePathIndicator.java
  53. 18
      app/src/main/res/drawable/ic_leaderboard.xml
  54. 9
      app/src/main/res/drawable/ic_sort.xml
  55. 50
      app/src/main/res/layout/activity_find_book.xml
  56. 143
      app/src/main/res/layout/fragment_find.xml
  57. 47
      app/src/main/res/layout/fragment_find_book_1.xml
  58. 33
      app/src/main/res/layout/fragment_find_book_2.xml
  59. 20
      app/src/main/res/layout/item_find_source.xml
  60. 44
      app/src/main/res/layout/listview_book_store_book_item.xml
  61. 16
      app/src/main/res/layout/view_loading.xml
  62. 6
      app/src/main/res/menu/menu_store.xml
  63. 9
      app/src/main/res/values/attrs.xml
  64. 48
      app/src/main/res/values/styles.xml

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -238,6 +238,8 @@ dependencies {
exclude group: 'org.slf4j'
exclude group: 'xmlpull'
}
implementation 'com.nshmura:recyclertablayout:1.5.0'
}
greendao {

@ -118,6 +118,7 @@
<activity android:name=".ui.activity.BookInfoEditActivity" />
<activity android:name=".ui.activity.SourceLoginActivity" />
<activity android:name=".ui.activity.ReadRecordActivity" />
<activity android:name=".ui.activity.FindBookActivity" />
<receiver android:name=".util.notification.NotificationClickReceiver" />
<receiver android:name=".ui.presenter.BookcasePresenter$cancelDownloadReceiver" />

@ -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();
}

@ -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()
}

@ -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;
}
}

@ -46,6 +46,7 @@ public class StrResponse {
public String body() {
if (body == null) {
if (response != null) {
ResponseBody body = response.body();
if (body != null) {
try {
@ -79,6 +80,9 @@ public class StrResponse {
this.body = "";
}
}
} else {
this.body = "";
}
}
return this.body;
}

@ -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<File>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NotNull File file) {
ShareUtils.share(AboutActivity.this, file, "分享日志文件", "application/x-zip-compressed");

@ -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

@ -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<Book>() {
@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

@ -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<BookType> 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:
if (item.getItemId() == R.id.action_tip) {
mHandler.sendEmptyMessage(6);
return true;
case R.id.action_refresh:
binding.refreshLayout.showLoading();
getData();
return true;
}
return false;
}

@ -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<String> 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<Boolean>() {
@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;
}
}

@ -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<Bitmap>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(Bitmap bitmap) {
binding.zxingview.decodeQRCode(bitmap);

@ -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<String>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NotNull String s) {
downloadingChapter = chapter.getTitle();

@ -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<List<ReadRecord>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NotNull List<ReadRecord> readRecords) {
records = readRecords;
@ -165,6 +170,10 @@ public class ReadRecordActivity extends BaseActivity {
case 0:
recordService.removeAll().compose(RxUtils::toSimpleSingle)
.subscribe(new MySingleObserver<Boolean>() {
@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<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NotNull Boolean aBoolean) {
ToastUtils.showSuccess("阅读时长已全部清空");

@ -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<List<ReplaceRuleBean>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull List<ReplaceRuleBean> replaceRuleBeans) {
mReplaceRules = replaceRuleBeans;
@ -303,6 +308,10 @@ public class ReplaceRuleActivity extends BaseActivity {
Observable<Boolean> observable = ReplaceRuleManager.importReplaceRule(text);
if (observable != null) {
observable.subscribe(new MyObserver<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(Boolean aBoolean) {
if (aBoolean) {

@ -368,6 +368,10 @@ public class SearchBookActivity extends BaseActivity {
mSourcesName, isDisables, dSourceCount, (dialog, which) -> {
BookSourceManager.saveDatas(sources)
.subscribe(new MySingleObserver<Boolean>() {
@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<Boolean>() {
@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

@ -190,6 +190,7 @@ public class SourceDebugActivity extends BaseActivity {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
addDisposable(d);
}
@Override

@ -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<Book> {
private List<String> 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<Book>() {
@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());
}
}

@ -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<BookSource> {
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());
}
}

@ -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;
@ -88,37 +89,34 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
BookSource source = BookSourceManager.getBookSourceByStr(book.getSource());
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source);
books2SearchBookBean(data, aBooks);
if (!StringHelper.isEmpty(data.getImgUrl())) {
if (StringHelper.isEmpty(data.getImgUrl())) {
data.setImgUrl("");
}
if (!App.isDestroy((Activity) getContext())) {
ivBookImg.load(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), data.getImgUrl()), data.getName(), data.getAuthor());
}
}else {
data.setImgUrl("");
}
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 {
data.setLastChapter("");
tvNewestChapter.setText("");
}
if (!StringHelper.isEmpty(data.getDesc())) {
tvDesc.setText(String.format("简介:%s", data.getDesc()));
} 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<SearchBookBean> {
books.add(book);
books2SearchBookBean(data, books);
initOtherInfo(data, rc);
} else {
tvBookName.setTag(false);
}
});
}
@ -163,8 +159,13 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
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(activity, tagList, 11));
}
}
private void books2SearchBookBean(SearchBookBean bookBean, List<Book> books) {
for (Book book : books) {

@ -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<List<BookSource>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull List<BookSource> 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<Boolean>() {
@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<File>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull File share) {
ShareUtils.share(sourceActivity, share, "书源分享", "text/plain");

@ -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<FindKind> kinds;
private FindCrawler findCrawler;
private PopupMenu kindMenu;
public FindBook1Fragment(List<FindKind> 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();
}
}

@ -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<Book> 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<Book>() {
@Override
protected IViewHolder<Book> 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<List<Book>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NotNull List<Book> 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);
}
}

@ -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<BookSource> 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<List<BookSource>>) emitter -> {
List<BookSource> findSources = new ArrayList<>();
List<BookSource> 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<List<BookSource>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(@NotNull List<BookSource> findSources) {
initFindSources(findSources);
}
});
}
private void initFindSources(List<BookSource> findSources) {
findSourcesAdapter = new BaseListAdapter<BookSource>() {
@Override
protected IViewHolder<BookSource> 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<Quotation>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NotNull Quotation q) {
binding.tvQuotation.setText(q.getHitokoto());

@ -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<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
mAdapter.notifyDataSetChanged();

@ -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<ArrayList<String>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(ArrayList<String> strings) {
if (!WebDavHelp.INSTANCE.showRestoreDialog(getContext(), strings, new Restore.CallBack() {

@ -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<ArrayList<String>>() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(ArrayList<String> strings) {
if (!WebDavHelp.INSTANCE.showRestoreDialog(getContext(), strings, new Restore.CallBack() {

@ -379,6 +379,11 @@ public class BookcasePresenter implements BasePresenter {
emitter.onNext(book);
emitter.onComplete();
})).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<Object>() {
@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<Object>() {
@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<Object>() {
@Override
public void onSubscribe(Disposable d) {
mMainActivity.addDisposable(d);
}
@Override
public void onNext(@NotNull Object o) {

@ -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;

@ -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<List<Book>> 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<StrResponse>) emitter -> {
emitter.onNext(OkHttpUtils.getStrResponse(url, null, null));
emitter.onComplete();
}).flatMap(response -> OkHttpUtils.setCookie(response, findCrawler.getTag())).flatMap((StrResponse strResponse) -> findCrawler.getFindBooks(strResponse, kind));
}
}

@ -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<List<BookType>>) 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<List<BookType>>() {
@Override
public void onSuccess(@NotNull List<BookType> 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<List<Book>>) 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<List<Book>>() {
@Override
public void onSuccess(@NotNull List<Book> books) {

@ -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<List<Book>> findBook(String url, ThirdFindCrawler fc, int page) {
BookSource source = fc.getSource();
Map<String, String> 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())));
}
}
}

@ -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<String, List<FindKind>> kindsMap = new LinkedHashMap<>();
@Override
public List<String> getGroups() {
return new ArrayList<>(kindsMap.keySet());
}
@Override
public Map<String, List<FindKind>> getKindsMap() {
return kindsMap;
}
@Override
public List<FindKind> getKindsByKey(String key) {
return kindsMap.get(key);
}
@Override
public abstract Observable<Boolean> initData();
@Override
public boolean needSearch() {
return false;
}
}

@ -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<BookType> getBookTypes(String html) {
return null;
}
//静态获取
public List<BookType> getBookTypes(){
return null;
}
public abstract List<Book> getFindBooks(String html, BookType bookType);
public abstract boolean getTypePage(BookType curType, int page);
public interface FindCrawler {
String getName();
String getTag();
List<String> getGroups();
Map<String, List<FindKind>> getKindsMap();
List<FindKind> getKindsByKey(String key);
Observable<Boolean> initData();
boolean needSearch();
Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind);
}

@ -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<BookType> getBookTypes(String html) {
return null;
}
//静态获取
public List<BookType> getBookTypes(){
return null;
}
public abstract List<Book> getFindBooks(String html, BookType bookType);
public abstract boolean getTypePage(BookType curType, int page);
}

@ -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";

@ -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";

@ -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<String, String> 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<Boolean> initData() {
return Observable.create(emitter -> {
initBookTypes();
List<FindKind> 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<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) {
return Observable.create(emitter -> {
emitter.onNext(getFindBooks(strResponse.body(), kind));
emitter.onComplete();
});
}
public List<Book> getFindBooks(String html, FindKind kind) {
List<Book> 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;
}
}

@ -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";
@Override
public String getCharset() {
return CHARSET;
}
@Override
public String getFindName() {
return FIND_NAME;
}
/**
* @author fengyue
* @date 2021/7/22 17:34
*/
public class QB5FindCrawler extends BaseFindCrawler {
@Override
public String getFindUrl() {
return FIND_URL;
public String getName() {
return "全本小说";
}
@Override
public boolean hasImg() {
return false;
public String getTag() {
return "https://www.qb50.com";
}
@Override
public boolean needSearch() {
return false;
public Observable<Boolean> initData() {
return Observable.create((ObservableOnSubscribe<StrResponse>) emitter -> {
emitter.onNext(OkHttpUtils.getStrResponse(getTag(), "gbk", null));
emitter.onComplete();
}).flatMap(response -> OkHttpUtils.setCookie(response, getTag()))
.flatMap((Function<StrResponse, ObservableSource<Boolean>>) 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<BookType> getBookTypes(String html) {
List<BookType> bookTypes = new ArrayList<>();
public List<FindKind> getBookKinds(String html) {
List<FindKind> 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;
}
/**
* 获取某一分类小说排行榜列表
*
* @param html
* @return
*/
public List<Book> getBookRankList(String html) {
List<Book> 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 kinds;
}
return books;
@Override
public Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) {
return Observable.create(emitter -> {
emitter.onNext(getFindBooks(strResponse.body(), kind));
emitter.onComplete();
});
}
public List<Book> getFindBooks(String html, BookType bookType) {
public List<Book> getFindBooks(String html, FindKind kind) {
List<Book> 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;
}
}

@ -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<BookType> getBookTypes(String html) {
List<BookType> 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<Book> getFindBooks(String html, BookType bookType) {
List<Book> 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;
}
}

@ -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<String, String> rankName = new LinkedHashMap<>();
private LinkedHashMap<String, Integer> 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<FindKind> initKinds(boolean isSort) {
Set<String> names = !isSort ? rankName.keySet() : sortName.keySet();
List<FindKind> 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<Boolean> initData() {
return Observable.create(emitter -> {
initMaleRankName();
initSortNames();
kindsMap.put("排行榜", initKinds(false));
kindsMap.put("分类", initKinds(true));
emitter.onNext(true);
emitter.onComplete();
});
}
@Override
public Observable<List<Book>> getFindBooks(StrResponse strResponse, FindKind kind) {
return Observable.create(emitter -> {
List<QDBook> qdBooks = getBooksFromJson(strResponse.body());
emitter.onNext(convertQDBook2Book(qdBooks));
emitter.onComplete();
});
}
private List<QDBook> getBooksFromJson(String json) throws JSONException {
List<QDBook> 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<Book> convertQDBook2Book(List<QDBook> qdBooks) {
List<Book> 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;
}
}

@ -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"};

@ -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";

@ -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";

@ -79,6 +79,7 @@ public class BiQuGeReadCrawler extends BaseReadCrawler implements BookInfoCrawle
}
}
return bookTypes;
}

@ -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<Boolean> initData() {
return Observable.create(emitter -> {
try {
String[] kindA;
String findRule;
if (!TextUtils.isEmpty(findRuleBean.getUrl()) && !source.containsGroup(findError)) {
boolean isJs = findRuleBean.getUrl().startsWith("<js>");
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<FindKind> 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<List<Book>> 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;
}
}

@ -224,10 +224,16 @@ public class RefreshLayout extends FrameLayout {
@Override
protected void onRestoreInstanceState(Parcelable state) {
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 {

@ -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<ValueAnimator,ValueAnimator.AnimatorUpdateListener> mUpdateListeners=new HashMap<>();
private ArrayList<ValueAnimator> 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<ValueAnimator> 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();
}
}

@ -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);
}
}

@ -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<ValueAnimator> onCreateAnimators() {
ArrayList<ValueAnimator> 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;
}
}

@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M348.16,153.6m40.96,0l245.76,0q40.96,0 40.96,40.96l0,286.72q0,40.96 -40.96,40.96l-245.76,0q-40.96,0 -40.96,-40.96l0,-286.72q0,-40.96 40.96,-40.96Z"
android:fillColor="#689CD5"/>
<path
android:pathData="M174.08,409.6m40.96,0l593.92,0q40.96,0 40.96,40.96l0,378.88q0,40.96 -40.96,40.96l-593.92,0q-40.96,0 -40.96,-40.96l0,-378.88q0,-40.96 40.96,-40.96Z"
android:fillColor="#81B7EF"/>
<path
android:pathData="M122.88,588.8m40.96,0l696.32,0q40.96,0 40.96,40.96l0,199.68q0,40.96 -40.96,40.96l-696.32,0q-40.96,0 -40.96,-40.96l0,-199.68q0,-40.96 40.96,-40.96Z"
android:fillColor="#689CD5"/>
<path
android:pathData="M511.99,481.51C479.65,507.55 458.66,516.92 449.02,509.59c-9.64,-7.31 -7.6,-31.05 6.12,-71.22 -33.6,-24.41 -48.61,-42.36 -45.03,-53.86 3.58,-11.51 25.83,-16.82 66.74,-15.93C488.57,327.66 500.28,307.2 511.99,307.2s23.42,20.46 35.13,61.38c40.83,-1.17 63.08,4.15 66.74,15.93 3.67,11.79 -11.35,29.74 -45.03,53.86 13.34,40.45 15.38,64.19 6.12,71.22 -9.27,7.03 -30.25,-2.33 -62.96,-28.09z"
android:fillColor="#E2EEFD"/>
</vector>

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M320,512 L192,512c-70.4,0 -128,57.6 -128,128l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128l0,-128C448,569.6 390.4,512 320,512zM384,768c0,35.2 -28.8,64 -64,64L192,832c-35.2,0 -64,-28.8 -64,-64l0,-128c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L384,768zM768,64l-128,0C569.6,64 512,121.6 512,192l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128L896,192C896,121.6 838.4,64 768,64zM832,320c0,35.2 -28.8,64 -64,64l-128,0C604.8,384 576,355.2 576,320L576,192c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L832,320zM768,512l-128,0c-70.4,0 -128,57.6 -128,128l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128l0,-128C896,569.6 838.4,512 768,512zM832,768c0,35.2 -28.8,64 -64,64l-128,0c-35.2,0 -64,-28.8 -64,-64l0,-128c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L832,768zM320,64 L192,64C121.6,64 64,121.6 64,192l0,128c0,70.4 57.6,128 128,128l128,0c70.4,0 128,-57.6 128,-128L448,192C448,121.6 390.4,64 320,64zM384,320c0,35.2 -28.8,64 -64,64L192,384C156.8,384 128,355.2 128,320L128,192c0,-35.2 28.8,-64 64,-64l128,0c35.2,0 64,28.8 64,64L384,320z"
android:fillColor="#689CD5"/>
</vector>

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--指示器-->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@color/colorPrimary"
android:fitsSystemWindows="true"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarStyle"
app:subtitleTextAppearance="@style/toolbar_subtitle_textStyle"
app:titleTextAppearance="@style/toolbar_title_textStyle">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tab_tl_indicator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="9"
app:rtl_tabIndicatorColor="@color/colorAccent"
app:rtl_tabIndicatorHeight="2dp"
app:rtl_tabBackground="?attr/selectableItemBackground"
app:rtl_tabTextAppearance="@style/TabLayoutTextStyle"
app:rtl_tabSelectedTextColor="@color/textPrimaryInverted"
app:rtl_tabPaddingStart="12dp"
app:rtl_tabPaddingEnd="12dp"
app:rtl_scrollEnabled="true"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<xyz.fycz.myreader.widget.RefreshLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
android:id="@+id/tab_vp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</xyz.fycz.myreader.widget.RefreshLayout>
</LinearLayout>

@ -66,88 +66,6 @@
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="35dp"
android:clickable="false"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<ImageView
android:id="@+id/iv_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_leaderboard" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_top"
android:text="排行榜"
android:textColor="@color/textPrimary"
android:textSize="@dimen/text_small_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_qidian_top"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/find_qidian"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_qidian_ns_top"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/find_qidian_ns"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
</RelativeLayout>
<!--<RelativeLayout
android:id="@+id/find_rl_xs7_top"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:background="@drawable/selector_common_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size"
android:text="@string/read_xs7"/>
</RelativeLayout>-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="35dp"
@ -156,26 +74,26 @@
android:paddingRight="20dp">
<ImageView
android:id="@+id/iv_sort"
android:id="@+id/iv_store"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_sort" />
android:src="@drawable/ic_bookstore" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_sort"
android:text="分类"
android:layout_toEndOf="@id/iv_store"
android:text="书库"
android:textColor="@color/textPrimary"
android:textSize="@dimen/text_small_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_qidian_sort"
android:id="@+id/find_rl_qidian"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
@ -193,7 +111,7 @@
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_qidian_ns_sort"
android:id="@+id/find_rl_qidian_ns"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
@ -210,50 +128,6 @@
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="35dp"
android:clickable="false"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<ImageView
android:id="@+id/iv_store"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_bookstore" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_store"
android:text="书城"
android:textColor="@color/textPrimary"
android:textSize="@dimen/text_small_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_ben100_store"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/read_ben100"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/find_rl_qb5_store"
@ -309,5 +183,10 @@
</RelativeLayout>-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_find_sources"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tl="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorForeground"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="35dp"
android:orientation="horizontal">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tab_tl_indicator"
android:layout_weight="10"
android:layout_width="0dp"
android:layout_height="match_parent"
app:rtl_tabIndicatorColor="@color/colorAccent"
app:rtl_tabIndicatorHeight="2dp"
app:rtl_tabBackground="?attr/selectableItemBackground"
app:rtl_tabTextAppearance="@android:style/TextAppearance.Small"
app:rtl_tabSelectedTextColor="?android:textColorPrimary"
app:rtl_tabPaddingStart="12dp"
app:rtl_tabPaddingEnd="12dp"
app:rtl_scrollEnabled="true"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_menu"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:layout_marginStart="5dp"
android:paddingHorizontal="5dp"
app:srcCompat="@drawable/ic_menu"
app:tint="@color/colorAccent" />
</LinearLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/tab_vp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<xyz.fycz.myreader.widget.RefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/srl_find_books"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants">
<com.scwang.smartrefresh.header.MaterialHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_find_books"
android:layout_marginHorizontal="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.scwang.smartrefresh.layout.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srlTextLoading="@string/loading_tip"
app:srlTextFailed="@string/loading_tip"
app:srlTextNothing="总得有个结尾吧"
app:srlAccentColor="@color/textSecondary"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</xyz.fycz.myreader.widget.RefreshLayout>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_find_source"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/selector_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/app_name"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
</RelativeLayout>

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:background="@color/colorBackground"
android:orientation="horizontal"
android:padding="3dp">
<xyz.fycz.myreader.widget.CoverImageView
@ -12,73 +12,77 @@
android:layout_width="42dp"
android:layout_height="60dp"
android:scaleType="fitXY"
app:srcCompat="@mipmap/default_cover"
android:visibility="gone"/>
android:visibility="gone"
app:srcCompat="@mipmap/default_cover" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="2dp"
android:paddingEnd="2dp"
android:orientation="vertical">
android:paddingEnd="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:orientation="horizontal">
android:orientation="horizontal"
android:padding="2dp">
<TextView
android:id="@+id/tv_book_name"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:text="bookname"
android:textSize="15sp"
android:textColor="@color/textPrimary"/>
android:textColor="@color/textPrimary"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_book_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingStart="5dp"
android:text="author"
android:textColor="@color/textSecondary"
android:maxLines="1"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_book_newest_chapter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:padding="2dp"
android:text="newest_chapter"
android:ellipsize="end"
android:textColor="@color/textSecondary"
android:maxLines="1"
android:textSize="12sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:orientation="horizontal">
android:orientation="horizontal"
android:padding="2dp">
<TextView
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:maxLines="1"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_book_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:textColor="@color/textSecondary"
android:text="book source"
android:maxLines="1"
android:text="book source"
android:textColor="@color/textSecondary"
android:textSize="12sp" />
</RelativeLayout>

@ -3,18 +3,20 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loadding_pb"
<xyz.fycz.myreader.widget.loading.LoadingIndicatorView
android:id="@+id/loading"
style="@style/LoadingIndicatorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<TextView
android:paddingTop="5dp"
android:layout_below="@+id/loadding_pb"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/textSecondary"
android:text="@string/loading_tip"/>
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" />
</RelativeLayout>

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_refresh"
android:icon="@drawable/ic_refresh"
android:title="刷新"
app:showAsAction="always"
android:visible="true"/>
<item
android:id="@+id/action_tip"
android:icon="@drawable/ic_question"

@ -64,4 +64,13 @@
<attr name="bubbleColor" format="color" />
<attr name="bubbleTextColor" format="color" />
</declare-styleable>
<declare-styleable name="AVLoadingIndicatorView">
<attr name="minWidth" format="dimension" />
<attr name="maxWidth" format="dimension"/>
<attr name="minHeight" format="dimension" />
<attr name="maxHeight" format="dimension"/>
<attr name="indicatorName" format="string"/>
<attr name="indicatorColor" format="color"/>
</declare-styleable>
</resources>

@ -31,6 +31,7 @@
<style name="TabLayoutTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/translucent_text</item>
</style>
<style name="jmui_default_dialog_style">
@ -61,6 +62,7 @@
<item name="colorControlNormal">@color/gray</item>
<item name="colorControlActivated">@color/colorAccent</item>
</style>
<declare-styleable name="CircleCheckbox">
<attr name="showOuterCircle" format="boolean" />
@ -139,6 +141,7 @@
<item name="android:textSize">@dimen/text_normal_size</item>
<item name="android:textColor">@color/white</item>
</style>
<style name="toolbar_subtitle_textStyle">
<item name="android:textSize">@dimen/text_default_size</item>
<item name="android:textColor">@color/md_dark_secondary</item>
@ -193,7 +196,7 @@
</style>
<style name="dialogWindowAnim" parent="android:Animation" mce_bogus="1">
<style name="dialogWindowAnim" mce_bogus="1" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/slide_bottom_in</item>
<item name="android:windowExitAnimation">@anim/slide_bottom_out</item>
</style>
@ -268,15 +271,37 @@
</declare-styleable>
<!-- TagGroup -->
<declare-styleable name="TagGroup"><attr format="boolean" name="atg_isAppendMode"/><attr format="string" name="atg_inputHint"/><attr format="color" name="atg_borderColor"/><attr format="color" name="atg_textColor"/><attr format="color" name="atg_backgroundColor"/><attr format="color" name="atg_dashBorderColor"/><attr format="color" name="atg_inputHintColor"/><attr format="color" name="atg_inputTextColor"/><attr format="color" name="atg_checkedBorderColor"/><attr format="color" name="atg_checkedTextColor"/><attr format="color" name="atg_checkedMarkerColor"/><attr format="color" name="atg_checkedBackgroundColor"/><attr format="color" name="atg_pressedBackgroundColor"/><attr format="dimension" name="atg_borderStrokeWidth"/><attr format="dimension" name="atg_textSize"/><attr format="dimension" name="atg_horizontalSpacing"/><attr format="dimension" name="atg_verticalSpacing"/><attr format="dimension" name="atg_horizontalPadding"/><attr format="dimension" name="atg_verticalPadding"/></declare-styleable>
<declare-styleable name="Themes"><attr format="reference" name="tagGroupStyle"/></declare-styleable>
<declare-styleable name="TagGroup">
<attr name="atg_isAppendMode" format="boolean" />
<attr name="atg_inputHint" format="string" />
<attr name="atg_borderColor" format="color" />
<attr name="atg_textColor" format="color" />
<attr name="atg_backgroundColor" format="color" />
<attr name="atg_dashBorderColor" format="color" />
<attr name="atg_inputHintColor" format="color" />
<attr name="atg_inputTextColor" format="color" />
<attr name="atg_checkedBorderColor" format="color" />
<attr name="atg_checkedTextColor" format="color" />
<attr name="atg_checkedMarkerColor" format="color" />
<attr name="atg_checkedBackgroundColor" format="color" />
<attr name="atg_pressedBackgroundColor" format="color" />
<attr name="atg_borderStrokeWidth" format="dimension" />
<attr name="atg_textSize" format="dimension" />
<attr name="atg_horizontalSpacing" format="dimension" />
<attr name="atg_verticalSpacing" format="dimension" />
<attr name="atg_horizontalPadding" format="dimension" />
<attr name="atg_verticalPadding" format="dimension" />
</declare-styleable>
<declare-styleable name="Themes">
<attr name="tagGroupStyle" format="reference" />
</declare-styleable>
<!-- From: file:/D:/WorkSpace/AndroidStudioProjects/AndroidTagGroup/library/src/main/res/values/strings.xml -->
<eat-comment />
<string name="add_tag">Add Tag</string>
<!-- From: file:/D:/WorkSpace/AndroidStudioProjects/AndroidTagGroup/library/src/main/res/values/styles.xml -->
<eat-comment />
<style name="TagGroup">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
@ -300,12 +325,14 @@
<item name="atg_horizontalPadding">12dp</item>
<item name="atg_verticalPadding">3dp</item>
</style>
<style name="TagGroup.Beauty_Red" parent="TagGroup">
<item name="atg_borderColor">#FF3D7F</item>
<item name="atg_textColor">#FF3D7F</item>
<item name="atg_checkedBorderColor">#FF3D7F</item>
<item name="atg_checkedBackgroundColor">#FF3D7F</item>
</style>
<style name="TagGroup.Beauty_Red.Inverse" parent="TagGroup">
<item name="atg_borderColor">#FF3D7F</item>
<item name="atg_textColor">#FFFFFF</item>
@ -318,6 +345,7 @@
<item name="atg_checkedBackgroundColor">#FFFFFF</item>
<item name="atg_pressedBackgroundColor">#FF99B1</item>
</style>
<style name="TagGroup.Large" parent="TagGroup">
<item name="atg_borderStrokeWidth">0.7dp</item>
<item name="atg_textSize">15sp</item>
@ -326,6 +354,7 @@
<item name="atg_horizontalPadding">14dp</item>
<item name="atg_verticalPadding">4dp</item>
</style>
<style name="TagGroup.Small" parent="TagGroup">
<item name="atg_textSize">10sp</item>
<item name="atg_horizontalSpacing">6dp</item>
@ -333,4 +362,15 @@
<item name="atg_horizontalPadding">8dp</item>
<item name="atg_verticalPadding">2dp</item>
</style>
<style name="LoadingIndicatorView">
<item name="minWidth">54dip</item>
<item name="maxWidth">54dip</item>
<item name="minHeight">54dip</item>
<item name="maxHeight">54dip</item>
<item name="indicatorName">BallTrianglePathIndicator</item>
<item name="indicatorColor">@color/colorAccent</item>
</style>
</resources>

Loading…
Cancel
Save