├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── qy │ │ └── reader │ │ ├── App.java │ │ ├── MainActivity.java │ │ ├── book │ │ ├── BookInfoActivity.java │ │ ├── BookInfoAdapter.java │ │ └── read │ │ │ ├── ReadActivity.java │ │ │ ├── ReadContract.java │ │ │ └── ReadPresenter.java │ │ ├── discover │ │ ├── DiscoverActivity.java │ │ └── DiscoverFragment.java │ │ ├── home │ │ ├── HomeActivity.java │ │ ├── HomeFragment.java │ │ └── SplashActivity.java │ │ ├── mine │ │ ├── MineActivity.java │ │ └── MineFragment.java │ │ ├── search │ │ ├── SearchActivity.java │ │ ├── SearchFragment.kt │ │ ├── result │ │ │ ├── SearchResultActivity.java │ │ │ └── SearchResultAdapter.java │ │ └── source │ │ │ ├── SourceSettingActivity.java │ │ │ └── SourceSettingAdapter.java │ │ ├── support │ │ └── DividerItemDecoration.java │ │ └── widgets │ │ └── CustomSearchBar.kt │ └── res │ ├── anim │ ├── fade_in.xml │ └── fade_out.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xxhdpi │ ├── ic_common_back_white.png │ ├── ic_default_author_left.png │ ├── ic_default_cover.jpg │ └── ic_default_source_left.png │ ├── drawable │ ├── fast_scroll_line.xml │ ├── fast_scroll_line_selector.xml │ ├── fast_scroll_thumb.xml │ ├── fast_scroll_thumb_selector.xml │ ├── ic_launcher_background.xml │ ├── shape_search_keyword_bg.xml │ └── shape_splash_skip_btn.xml │ ├── layout │ ├── activity_book_info.xml │ ├── activity_main.xml │ ├── activity_read.xml │ ├── activity_search_result.xml │ ├── activity_search_source_setting.xml │ ├── activity_splash.xml │ ├── fragment_main_home.xml │ ├── fragment_search.xml │ ├── item_book_chapter_list.xml │ ├── item_search_list.xml │ └── item_search_source_setting.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_round.png │ └── splash.jpg │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── common ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── zh2Hans.properties │ └── zh2Hant.properties │ ├── java │ └── com │ │ └── qy │ │ └── reader │ │ └── common │ │ ├── Global.java │ │ ├── base │ │ ├── BaseActivity.java │ │ └── BaseTabActivity.java │ │ ├── entity │ │ ├── book │ │ │ ├── BookDetail.java │ │ │ └── SearchBook.java │ │ ├── chapter │ │ │ └── Chapter.java │ │ └── source │ │ │ ├── Source.java │ │ │ ├── SourceConfig.java │ │ │ ├── SourceEnable.java │ │ │ └── SourceID.java │ │ ├── utils │ │ ├── AndroidRomUtil.java │ │ ├── AssetsUtils.java │ │ ├── BitmapUtils.java │ │ ├── CacheUtils.java │ │ ├── CleanUtils.java │ │ ├── CloseUtils.java │ │ ├── EncryptUtils.java │ │ ├── FileIOUtils.java │ │ ├── FileUtils.java │ │ ├── HandlerFactory.java │ │ ├── LogUtils.java │ │ ├── Nav.java │ │ ├── NetworkUtils.java │ │ ├── SPUtils.java │ │ ├── ScreenUtils.java │ │ ├── StatusBarCompat.java │ │ ├── StringUtils.java │ │ ├── ToastUtils.java │ │ └── ZHConverter.java │ │ └── widgets │ │ ├── CornerImageView.java │ │ ├── ListDialog.java │ │ ├── Sneaker.java │ │ ├── TagGroup.java │ │ └── reader │ │ ├── BaseSlider.java │ │ ├── BookManager.java │ │ ├── BookPageView.java │ │ ├── FollowSlider.java │ │ ├── OnPageStateChangedListener.java │ │ ├── OverlapSlider.java │ │ ├── PageFactory.java │ │ ├── ReadView.java │ │ ├── SettingManager.java │ │ ├── Slider.java │ │ └── annotation │ │ ├── ChapterType.java │ │ ├── DensityLevel.java │ │ ├── DrawPageType.java │ │ ├── FontType.java │ │ └── SlideMode.java │ └── res │ ├── anim │ ├── sneaker_popup_hide.xml │ └── sneaker_popup_show.xml │ ├── drawable-xxhdpi │ ├── ic_book_read_power.9.png │ ├── ic_common_back.png │ ├── ic_search_bar.png │ ├── ic_search_delete.png │ ├── ic_search_source_setting.png │ ├── ic_tab_discover_normal.png │ ├── ic_tab_discover_pressed.png │ ├── ic_tab_home_normal.png │ ├── ic_tab_home_pressed.png │ ├── ic_tab_mine_normal.png │ ├── ic_tab_mine_pressed.png │ ├── ic_tab_search_normal.png │ └── ic_tab_search_pressed.png │ ├── drawable │ ├── ic_error.xml │ ├── ic_success.xml │ ├── ic_warning.xml │ ├── seekbar_battery_progress.xml │ ├── selector_tab_discover.xml │ ├── selector_tab_home.xml │ ├── selector_tab_mine.xml │ ├── selector_tab_search.xml │ ├── selector_tab_text_color.xml │ ├── shape_search_bar.xml │ ├── shape_tab_top.xml │ └── shape_toast_bg.xml │ ├── layout │ ├── activity_base_tab.xml │ ├── common_status_bar.xml │ ├── common_toast_layout.xml │ ├── common_tool_bar.xml │ ├── layout_read_power.xml │ └── view_search_bar.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── config.gradle ├── crawler ├── .gitignore ├── book_catagory.config ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── SourceEnable.json │ └── Template.json │ ├── java │ └── com │ │ └── qy │ │ └── reader │ │ └── crawler │ │ ├── Crawler.java │ │ ├── api │ │ └── API.java │ │ ├── source │ │ ├── SourceManager.java │ │ └── callback │ │ │ ├── ChapterCallback.java │ │ │ ├── ContentCallback.java │ │ │ └── SearchCallback.java │ │ └── xpath │ │ ├── core │ │ ├── AxisSelector.java │ │ ├── Functions.java │ │ ├── NodeTreeBuilderStateMachine.java │ │ ├── SingletonProducer.java │ │ ├── XContext.java │ │ └── XpathEvaluator.java │ │ ├── exception │ │ ├── NoSuchAxisException.java │ │ ├── NoSuchFunctionException.java │ │ └── XpathSyntaxErrorException.java │ │ ├── model │ │ ├── JXDocument.java │ │ ├── JXNode.java │ │ ├── Node.java │ │ ├── Predicate.java │ │ └── XpathResult.java │ │ └── util │ │ ├── CommonUtil.java │ │ ├── EmMap.java │ │ ├── OpEm.java │ │ └── ScopeEm.java │ └── res │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── detail.png ├── search.png ├── search_result.png └── source.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.android.compileSdkVersion 6 | buildToolsVersion rootProject.ext.android.buildToolsVersion 7 | 8 | defaultConfig { 9 | applicationId rootProject.ext.android.applicationId 10 | minSdkVersion rootProject.ext.android.minSdkVersion 11 | targetSdkVersion rootProject.ext.android.targetSdkVersion 12 | versionCode rootProject.ext.android.versionCode 13 | versionName rootProject.ext.android.versionName 14 | } 15 | buildTypes { 16 | release { 17 | postprocessing { 18 | removeUnusedCode false 19 | removeUnusedResources false 20 | obfuscate false 21 | optimizeCode false 22 | proguardFile 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | aaptOptions { 28 | cruncherEnabled false 29 | } 30 | 31 | dexOptions { 32 | preDexLibraries true 33 | javaMaxHeapSize "2g" 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(include: ['*.jar'], dir: 'libs') 39 | 40 | implementation project(path: ':common') 41 | implementation project(path: ':crawler') 42 | 43 | implementation rootProject.ext.dependencies["appcompat-v7"] 44 | implementation rootProject.ext.dependencies["design"] 45 | implementation rootProject.ext.dependencies["recyclerview-v7"] 46 | implementation rootProject.ext.dependencies["kotlin"] 47 | implementation rootProject.ext.dependencies["glide"] 48 | implementation rootProject.ext.dependencies["gson"] 49 | implementation rootProject.ext.dependencies["easy-adapter"] 50 | } 51 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/App.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader; 2 | 3 | import android.app.Application; 4 | 5 | import com.qy.reader.common.Global; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/8. 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | 16 | Global.init(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.TextView; 6 | 7 | import com.qy.reader.common.entity.book.SearchBook; 8 | import com.qy.reader.common.entity.chapter.Chapter; 9 | import com.qy.reader.common.utils.StatusBarCompat; 10 | import com.qy.reader.crawler.Crawler; 11 | import com.qy.reader.crawler.source.SourceManager; 12 | import com.qy.reader.crawler.source.callback.ChapterCallback; 13 | import com.qy.reader.crawler.source.callback.ContentCallback; 14 | 15 | import java.util.List; 16 | 17 | 18 | public class MainActivity extends AppCompatActivity { 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | 24 | StatusBarCompat.compat(this); 25 | 26 | setContentView(R.layout.activity_main); 27 | final TextView textView = findViewById(R.id.tv_Content); 28 | 29 | new Thread(new Runnable() { 30 | @Override 31 | public void run() { 32 | // Crawler.search("你好", new SearchCallback() { 33 | // @Override 34 | // public void onResponse(String keyword, List appendList) { 35 | // Log.e("key" + keyword, "" + appendList); 36 | // } 37 | // 38 | // @Override 39 | // public void onFinish() { 40 | // 41 | // } 42 | // 43 | // @Override 44 | // public void onError(String msg) { 45 | // 46 | // } 47 | // }); 48 | Crawler.catalog(new SearchBook.SL("https://www.liewen.cc/b/24/24934/", SourceManager.SOURCES.get(1)), new ChapterCallback() { 49 | @Override 50 | public void onResponse(List chapters) { 51 | 52 | } 53 | 54 | @Override 55 | public void onError(String msg) { 56 | 57 | } 58 | }); 59 | 60 | Crawler.content(new SearchBook.SL("https://www.liewen.cc/b/24/24934/", SourceManager.SOURCES.get(1)), "/b/24/24934/12212511.html", new ContentCallback() { 61 | @Override 62 | public void onResponse(String content) { 63 | 64 | } 65 | 66 | @Override 67 | public void onError(String msg) { 68 | 69 | } 70 | }); 71 | } 72 | }).start(); 73 | 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/book/BookInfoAdapter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.book; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | import com.qy.reader.R; 7 | import com.qy.reader.common.entity.chapter.Chapter; 8 | import com.qy.reader.common.utils.StringUtils; 9 | import com.yuyh.easyadapter.recyclerview.EasyRVAdapter; 10 | import com.yuyh.easyadapter.recyclerview.EasyRVHolder; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by quezhongsang on 2018/1/13. 17 | */ 18 | public class BookInfoAdapter extends EasyRVAdapter { 19 | 20 | private boolean mIsAsc = true; 21 | 22 | public BookInfoAdapter(Context context, List list) { 23 | super(context, list, R.layout.item_book_chapter_list); 24 | } 25 | 26 | @Override 27 | protected void onBindData(EasyRVHolder viewHolder, int position, Chapter item) { 28 | TextView textView = viewHolder.getView(R.id.tv_content); 29 | if (item != null) { 30 | textView.setText(StringUtils.getStr(item.title)); 31 | } else { 32 | textView.setText(""); 33 | } 34 | } 35 | 36 | public void orderByDesc() { 37 | if (mIsAsc) { 38 | transformData(); 39 | mIsAsc = false; 40 | } 41 | } 42 | 43 | public void orderByAsc() { 44 | if (!mIsAsc) { 45 | transformData(); 46 | mIsAsc = true; 47 | } 48 | 49 | } 50 | 51 | private void transformData() { 52 | if (mList != null) { 53 | List chapterList = new ArrayList<>(mList.size()); 54 | for (int i = mList.size() - 1; i >= 0; i--) { 55 | chapterList.add(mList.get(i)); 56 | } 57 | mList.clear(); 58 | mList.addAll(chapterList); 59 | notifyDataSetChanged(); 60 | } 61 | } 62 | 63 | public boolean isAsc() { 64 | return mIsAsc; 65 | } 66 | 67 | @Override 68 | public void clear() { 69 | mIsAsc = true; 70 | super.clear(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/book/read/ReadContract.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.book.read; 2 | 3 | import com.qy.reader.common.entity.book.SearchBook; 4 | import com.qy.reader.common.entity.chapter.Chapter; 5 | 6 | /** 7 | * Created by yuyuhang on 2018/1/14. 8 | */ 9 | public interface ReadContract { 10 | 11 | interface Presenter { 12 | void getChapterContent(String bookNum, SearchBook.SL source, Chapter chapter); 13 | } 14 | 15 | interface View { 16 | void showContent(Chapter chapter, String content); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/book/read/ReadPresenter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.book.read; 2 | 3 | import com.qy.reader.common.entity.book.SearchBook; 4 | import com.qy.reader.common.entity.chapter.Chapter; 5 | import com.qy.reader.common.utils.HandlerFactory; 6 | import com.qy.reader.common.widgets.reader.BookManager; 7 | import com.qy.reader.crawler.Crawler; 8 | import com.qy.reader.crawler.source.callback.ContentCallback; 9 | 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.LinkedBlockingQueue; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * Created by yuyuhang on 2018/1/14. 17 | */ 18 | public class ReadPresenter implements ReadContract.Presenter { 19 | 20 | private static final int mCorePoolSize = 1; 21 | private static final int mMaximumPoolSize = 1; 22 | private static final long mKeepAliveTime = 5L; 23 | private ThreadPoolExecutor mPool; 24 | 25 | private ReadContract.View view; 26 | 27 | public ReadPresenter(ReadContract.View view) { 28 | this.view = view; 29 | mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, 30 | new LinkedBlockingQueue(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); 31 | } 32 | 33 | @Override 34 | public void getChapterContent(final String bookNum, final SearchBook.SL source, final Chapter chapter) { 35 | Runnable runnable = new Runnable() { 36 | @Override 37 | public void run() { 38 | Crawler.content(source, chapter.link, new ContentCallback() { 39 | @Override 40 | public void onResponse(final String content) { 41 | BookManager.getInstance().saveContentFile(source.source.id, bookNum, chapter.title, content); 42 | HandlerFactory.getUIHandler().post(new Runnable() { 43 | @Override 44 | public void run() { 45 | view.showContent(chapter, content); 46 | } 47 | }); 48 | } 49 | 50 | @Override 51 | public void onError(String msg) { 52 | 53 | } 54 | }); 55 | } 56 | }; 57 | mPool.execute(runnable); 58 | } 59 | 60 | public void release() { 61 | if (mPool != null) { 62 | if (!mPool.isShutdown() || mPool.isTerminating()) { 63 | mPool.shutdownNow(); 64 | } 65 | mPool.getQueue().clear(); 66 | } 67 | mPool = null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/discover/DiscoverActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.discover; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import com.qy.reader.common.base.BaseTabActivity; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/9. 9 | */ 10 | public class DiscoverActivity extends BaseTabActivity { 11 | 12 | @Override 13 | protected int getCurrentIndex() { 14 | return DISCOVER_INDEX; 15 | } 16 | 17 | @Override 18 | protected Fragment fragmentInstance() { 19 | return new DiscoverFragment(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/discover/DiscoverFragment.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.discover; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | /** 6 | * Created by yuyuhang on 2018/1/9. 7 | */ 8 | public class DiscoverFragment extends Fragment { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/home/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.home; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import com.qy.reader.common.base.BaseTabActivity; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/9. 9 | */ 10 | public class HomeActivity extends BaseTabActivity { 11 | 12 | @Override 13 | protected int getCurrentIndex() { 14 | return HOME_INDEX; 15 | } 16 | 17 | @Override 18 | protected Fragment fragmentInstance() { 19 | return new HomeFragment(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/home/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.home; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.qy.reader.R; 13 | 14 | /** 15 | * Created by yuyuhang on 2018/1/9. 16 | */ 17 | public class HomeFragment extends Fragment { 18 | 19 | private View mRootView; 20 | 21 | @Override 22 | public void onCreate(@Nullable Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | @Nullable 27 | @Override 28 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 29 | mRootView = inflater.inflate(R.layout.fragment_main_home, container, false); 30 | 31 | return mRootView; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/home/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.home; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import com.qy.reader.R; 10 | import com.qy.reader.common.base.BaseActivity; 11 | import com.qy.reader.common.utils.Nav; 12 | import com.qy.reader.common.utils.StatusBarCompat; 13 | 14 | /** 15 | * Created by yuyuhang on 2018/1/8. 16 | */ 17 | public class SplashActivity extends BaseActivity { 18 | 19 | private TextView mTvSkip; 20 | 21 | @Override 22 | protected void onCreate(@Nullable Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | 25 | StatusBarCompat.compatTransNavigationBar(this); 26 | 27 | setContentView(R.layout.activity_splash); 28 | 29 | mTvSkip = findViewById(R.id.tv_splash_skip); 30 | mTvSkip.setOnClickListener(new View.OnClickListener() { 31 | @Override 32 | public void onClick(View view) { 33 | end(); 34 | } 35 | }); 36 | 37 | mTvSkip.postDelayed(runnable, 500); 38 | 39 | } 40 | 41 | private Runnable runnable = new Runnable() { 42 | @Override 43 | public void run() { 44 | end(); 45 | } 46 | }; 47 | 48 | private void end() { 49 | Nav.from(this).start("qyreader://home"); 50 | 51 | finish(); 52 | } 53 | 54 | @Override 55 | public void finish() { 56 | mTvSkip.removeCallbacks(runnable); 57 | super.finish(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/mine/MineActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.mine; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import com.qy.reader.common.base.BaseTabActivity; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/9. 9 | */ 10 | public class MineActivity extends BaseTabActivity { 11 | 12 | @Override 13 | protected int getCurrentIndex() { 14 | return MINE_INDEX; 15 | } 16 | 17 | @Override 18 | protected Fragment fragmentInstance() { 19 | return new MineFragment(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/mine/MineFragment.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.mine; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | /** 6 | * Created by yuyuhang on 2018/1/9. 7 | */ 8 | public class MineFragment extends Fragment { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/search/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.search; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import com.qy.reader.common.base.BaseTabActivity; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/9. 9 | */ 10 | public class SearchActivity extends BaseTabActivity { 11 | 12 | @Override 13 | protected int getCurrentIndex() { 14 | return SEARCH_INDEX; 15 | } 16 | 17 | @Override 18 | protected Fragment fragmentInstance() { 19 | return new SearchFragment(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/search/SearchFragment.kt: -------------------------------------------------------------------------------- 1 | package com.qy.reader.search 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.support.v7.widget.Toolbar 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import com.qy.reader.R 10 | import com.qy.reader.widgets.CustomSearchBar 11 | 12 | /** 13 | * Created by xiaoshu on 2018/1/9. 14 | */ 15 | class SearchFragment : Fragment() { 16 | 17 | private lateinit var mToolBar: Toolbar 18 | 19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 20 | val contentView = inflater.inflate(R.layout.fragment_search, container, false) 21 | mToolBar = contentView.findViewById(R.id.common_toolbar) as Toolbar 22 | mToolBar.navigationIcon = null 23 | val mSearchBar: CustomSearchBar? = CustomSearchBar(context) 24 | mToolBar.addView(mSearchBar) 25 | return contentView 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/search/result/SearchResultAdapter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.search.result; 2 | 3 | import android.content.Context; 4 | import android.text.SpannableString; 5 | import android.text.Spanned; 6 | import android.text.TextUtils; 7 | import android.text.style.ForegroundColorSpan; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.qy.reader.R; 13 | import com.qy.reader.common.entity.book.SearchBook; 14 | import com.qy.reader.common.entity.source.Source; 15 | import com.qy.reader.crawler.source.SourceManager; 16 | import com.yuyh.easyadapter.recyclerview.EasyRVAdapter; 17 | import com.yuyh.easyadapter.recyclerview.EasyRVHolder; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * Created by yuyuhang on 2018/1/11. 23 | */ 24 | public class SearchResultAdapter extends EasyRVAdapter { 25 | 26 | private String title; 27 | 28 | public SearchResultAdapter(Context context, List list) { 29 | super(context, list, R.layout.item_search_list); 30 | } 31 | 32 | public void setTitle(String title) { 33 | this.title = title; 34 | } 35 | 36 | @Override 37 | protected void onBindData(EasyRVHolder viewHolder, int position, SearchBook item) { 38 | 39 | TextView tvTitle = viewHolder.getView(R.id.tv_search_item_title); 40 | tvTitle.setText(changeTxtColor(item.title, title, 0xFFF08080)); 41 | 42 | TextView tvAuthor = viewHolder.getView(R.id.tv_search_item_author); 43 | tvAuthor.setText(changeTxtColor(item.author, title, 0xFFF08080)); 44 | 45 | TextView tvDesc = viewHolder.getView(R.id.tv_search_item_desc); 46 | tvDesc.setText(changeTxtColor(item.desc, title, 0xFFF08080)); 47 | 48 | TextView tvSource = viewHolder.getView(R.id.tv_search_item_source); 49 | StringBuilder builder = new StringBuilder(); 50 | for (SearchBook.SL sl : item.sources) { 51 | if (sl.source != null) { 52 | Source source = SourceManager.SOURCES.get(sl.source.id); 53 | if (source != null) { 54 | if (builder.length() > 0) { 55 | builder.append(" | "); 56 | } 57 | builder.append(source.name); 58 | } 59 | } 60 | } 61 | tvSource.setText(builder.toString()); 62 | 63 | ImageView ivCover = viewHolder.getView(R.id.iv_search_item_cover); 64 | Glide.with(mContext).load(item.cover).into(ivCover); 65 | } 66 | 67 | public SpannableString changeTxtColor(String content, String splitText, int color) { 68 | int start = 0, end; 69 | SpannableString result = new SpannableString(content = (content == null ? "" : content)); 70 | if (TextUtils.isEmpty(splitText)) { 71 | return result; 72 | } 73 | if (!TextUtils.isEmpty(splitText) && (content.length() >= splitText.length())) { 74 | while ((start = content.indexOf(splitText, start)) >= 0) { 75 | end = start + splitText.length(); 76 | result.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); 77 | start = end; 78 | } 79 | } 80 | return result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/search/source/SourceSettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.search.source; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | 8 | import com.qy.reader.R; 9 | import com.qy.reader.common.base.BaseActivity; 10 | import com.qy.reader.common.entity.source.Source; 11 | import com.qy.reader.crawler.source.SourceManager; 12 | import com.qy.reader.support.DividerItemDecoration; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by yuyuhang on 2018/1/12. 19 | */ 20 | public class SourceSettingActivity extends BaseActivity { 21 | 22 | private RecyclerView mRecyclerView; 23 | private List mList = new ArrayList<>(); 24 | private SourceSettingAdapter mAdapter; 25 | 26 | @Override 27 | public String getToolbarTitle() { 28 | return "搜索源"; 29 | } 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_search_source_setting); 35 | initToolbar(); 36 | 37 | mRecyclerView = findViewById(R.id.rv_source_setting_list); 38 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 39 | mRecyclerView.addItemDecoration(new DividerItemDecoration()); 40 | for (int i = 0; i < SourceManager.SOURCES.size(); i++) { 41 | mList.add(SourceManager.SOURCES.valueAt(i)); 42 | } 43 | mAdapter = new SourceSettingAdapter(this, mList); 44 | mRecyclerView.setAdapter(mAdapter); 45 | } 46 | 47 | @Override 48 | protected void onPause() { 49 | super.onPause(); 50 | 51 | SourceManager.saveSourceEnable(mAdapter.getCheckedMap()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/search/source/SourceSettingAdapter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.search.source; 2 | 3 | import android.content.Context; 4 | import android.util.SparseBooleanArray; 5 | import android.widget.CheckBox; 6 | import android.widget.CompoundButton; 7 | 8 | import com.google.gson.Gson; 9 | import com.google.gson.reflect.TypeToken; 10 | import com.qy.reader.R; 11 | import com.qy.reader.common.entity.source.Source; 12 | import com.qy.reader.common.entity.source.SourceEnable; 13 | import com.qy.reader.common.utils.SPUtils; 14 | import com.qy.reader.crawler.source.SourceManager; 15 | import com.yuyh.easyadapter.recyclerview.EasyRVAdapter; 16 | import com.yuyh.easyadapter.recyclerview.EasyRVHolder; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * Created by yuyuhang on 2018/1/12. 22 | */ 23 | public class SourceSettingAdapter extends EasyRVAdapter { 24 | 25 | private SparseBooleanArray checkedMap = new SparseBooleanArray(); 26 | 27 | public SourceSettingAdapter(Context context, List list) { 28 | super(context, list, R.layout.item_search_source_setting); 29 | checkedMap = SourceManager.getSourceEnableSparseArray(); 30 | } 31 | 32 | @Override 33 | protected void onBindData(EasyRVHolder viewHolder, int position, final Source item) { 34 | CheckBox box = viewHolder.getView(R.id.cb_item_source_setting); 35 | box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 36 | @Override 37 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 38 | checkedMap.put(item.id, isChecked); 39 | } 40 | }); 41 | box.setChecked(checkedMap.get(item.id)); 42 | box.setText(item.name); 43 | } 44 | 45 | public SparseBooleanArray getCheckedMap() { 46 | return checkedMap; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/support/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.support; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Rect; 8 | import android.graphics.drawable.ColorDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.util.Log; 13 | import android.view.View; 14 | 15 | import com.qy.reader.common.utils.ScreenUtils; 16 | 17 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 18 | 19 | public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL; 20 | 21 | public static final int VERTICAL = LinearLayoutManager.VERTICAL; 22 | 23 | private Drawable mDivider; 24 | 25 | private int mOrientation; 26 | 27 | private boolean showLastLine = false; 28 | 29 | public DividerItemDecoration() { 30 | this(VERTICAL); 31 | } 32 | 33 | public DividerItemDecoration(int orientation) { 34 | this(orientation, false); 35 | } 36 | 37 | public DividerItemDecoration(int orientation, boolean showLastLine) { 38 | this.showLastLine = showLastLine; 39 | mDivider = new ColorDrawable(0xffd3d3d3); 40 | mDivider.setBounds(0, 0, ScreenUtils.getScreenWidth(), 1); 41 | setOrientation(orientation); 42 | } 43 | 44 | public void setOrientation(int orientation) { 45 | if (orientation != HORIZONTAL && orientation != VERTICAL) { 46 | throw new IllegalArgumentException("invalid orientation"); 47 | } 48 | mOrientation = orientation; 49 | } 50 | 51 | @Override 52 | public void onDraw(Canvas c, RecyclerView parent) { 53 | if (mOrientation == VERTICAL) { 54 | drawVertical(c, parent); 55 | } else { 56 | drawHorizontal(c, parent); 57 | } 58 | } 59 | 60 | 61 | public void drawVertical(Canvas c, RecyclerView parent) { 62 | final int left = parent.getPaddingLeft(); 63 | final int right = parent.getWidth() - parent.getPaddingRight(); 64 | 65 | int childCount = parent.getChildCount(); 66 | childCount = showLastLine ? childCount : childCount - 1; 67 | for (int i = 0; i < childCount; i++) { 68 | final View child = parent.getChildAt(i); 69 | RecyclerView v = new RecyclerView(parent.getContext()); 70 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 71 | .getLayoutParams(); 72 | final int top = child.getBottom() + params.bottomMargin; 73 | final int bottom = top + 1; 74 | mDivider.setBounds(left, top, right, bottom); 75 | mDivider.draw(c); 76 | } 77 | } 78 | 79 | public void drawHorizontal(Canvas c, RecyclerView parent) { 80 | final int top = parent.getPaddingTop(); 81 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 82 | 83 | int childCount = parent.getChildCount(); 84 | childCount = showLastLine ? childCount : childCount - 1; 85 | for (int i = 0; i < childCount; i++) { 86 | final View child = parent.getChildAt(i); 87 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 88 | .getLayoutParams(); 89 | final int left = child.getRight() + params.rightMargin; 90 | final int right = left + 1; 91 | mDivider.setBounds(left, top, right, bottom); 92 | mDivider.draw(c); 93 | } 94 | } 95 | 96 | @Override 97 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 98 | if (mOrientation == VERTICAL) { 99 | outRect.set(0, 0, 0, 1); 100 | } else { 101 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/qy/reader/widgets/CustomSearchBar.kt: -------------------------------------------------------------------------------- 1 | package com.qy.reader.widgets 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.util.AttributeSet 7 | import android.view.Gravity 8 | import android.view.KeyEvent 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.widget.* 12 | import com.qy.reader.common.R 13 | import com.qy.reader.common.utils.Nav 14 | import com.qy.reader.search.result.SearchResultActivity 15 | import com.qy.reader.search.source.SourceSettingActivity 16 | 17 | 18 | /** 19 | * Created by xiaoshu on 2018/1/9. 20 | */ 21 | class CustomSearchBar @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyle: Int = 0) : LinearLayout(context, attrs, defStyle) { 22 | 23 | private var mEtSearch: EditText? = null 24 | private var mIvDelete: ImageView? = null 25 | private var mTvSearch: TextView? = null 26 | private var mIvSource: ImageView? = null 27 | 28 | init { 29 | initView(context) 30 | } 31 | 32 | private fun initView(context: Context?) { 33 | val contentView = LayoutInflater.from(context).inflate(R.layout.view_search_bar, this, true) 34 | mEtSearch = contentView.findViewById(R.id.et_search_bar_view) as EditText 35 | mIvDelete = contentView.findViewById(R.id.iv_search_bar_delete) as ImageView 36 | mTvSearch = contentView.findViewById(R.id.tv_search_bar_search) as TextView 37 | mIvSource = contentView.findViewById(R.id.iv_search_bar_source) as ImageView 38 | gravity = Gravity.CENTER_VERTICAL 39 | orientation = HORIZONTAL 40 | layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) 41 | 42 | mEtSearch!!.setOnKeyListener(View.OnKeyListener { _, _, event -> 43 | if (event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) { 44 | search() 45 | return@OnKeyListener true 46 | } 47 | return@OnKeyListener false 48 | }) 49 | 50 | mIvDelete!!.setOnClickListener({ mEtSearch!!.setText("") }) 51 | 52 | mTvSearch!!.setOnClickListener({ search() }) 53 | 54 | mIvSource!!.setOnClickListener({ 55 | val intent = Intent(context, SourceSettingActivity::class.java) 56 | context?.startActivity(intent) 57 | }) 58 | } 59 | 60 | private fun search() { 61 | val text = mEtSearch!!.text.toString() 62 | if (text.isEmpty()) { 63 | Toast.makeText(context, "请输入搜索内容", Toast.LENGTH_SHORT).show() 64 | return 65 | } 66 | 67 | val bundle = Bundle() 68 | bundle.putString("text", text) 69 | Nav.from(context).setExtras(bundle).start("qyreader://searchresult") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_common_back_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/drawable-xxhdpi/ic_common_back_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_default_author_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/drawable-xxhdpi/ic_default_author_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_default_cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/drawable-xxhdpi/ic_default_cover.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_default_source_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/drawable-xxhdpi/ic_default_source_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/fast_scroll_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fast_scroll_line_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fast_scroll_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fast_scroll_thumb_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_search_keyword_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_splash_skip_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_read.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 17 | 18 | 29 | 30 | 31 | 32 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search_source_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 23 | 24 | 35 | 36 | 47 | 48 | 59 | 60 | 71 | 72 | 83 | 84 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_book_chapter_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_search_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 24 | 25 | 35 | 36 | 47 | 48 | 61 | 62 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_search_source_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xxhdpi/splash.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #333333 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 全阅 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from:'config.gradle' 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.1.0-alpha04' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.10" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.android.compileSdkVersion 7 | buildToolsVersion rootProject.ext.android.buildToolsVersion 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.ext.android.minSdkVersion 11 | targetSdkVersion rootProject.ext.android.targetSdkVersion 12 | versionCode rootProject.ext.android.versionCode 13 | versionName rootProject.ext.android.versionName 14 | } 15 | 16 | buildTypes { 17 | release { 18 | postprocessing { 19 | removeUnusedCode false 20 | removeUnusedResources false 21 | obfuscate false 22 | optimizeCode false 23 | proguardFile 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation rootProject.ext.dependencies["appcompat-v7"] 33 | implementation rootProject.ext.dependencies["kotlin"] 34 | 35 | compile 'io.reactivex:rxandroid:1.1.0' 36 | compile 'io.reactivex:rxjava:1.1.5' 37 | compile 'com.trello:rxlifecycle:0.3.0' 38 | compile 'com.trello:rxlifecycle-components:0.3.0' 39 | // https://github.com/drakeet/MaterialDialog 40 | compile 'me.drakeet.materialdialog:library:1.3.1' 41 | } 42 | -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/Global.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Created by yuyuhang on 2018/1/8. 7 | */ 8 | public class Global { 9 | 10 | private static Application application; 11 | 12 | public static void init(Application application) { 13 | Global.application = application; 14 | } 15 | 16 | public static Application getApplication() { 17 | return application; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.base; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.content.ContextCompat; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.text.TextUtils; 9 | import android.view.View; 10 | import android.view.WindowManager; 11 | import android.widget.TextView; 12 | 13 | import com.qy.reader.common.R; 14 | import com.qy.reader.common.utils.StatusBarCompat; 15 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity; 16 | 17 | /** 18 | * Created by yuyuhang on 2018/1/8. 19 | */ 20 | public class BaseActivity extends RxAppCompatActivity { 21 | 22 | protected BaseActivity mContext; 23 | 24 | protected TextView mTvBack; 25 | protected TextView mTvTitle; 26 | 27 | protected View statusBarView; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | this.mContext = this; 32 | super.onCreate(savedInstanceState); 33 | 34 | if (enableStatusBarCompat()) { 35 | statusBarView = StatusBarCompat.compat(this); 36 | } 37 | } 38 | 39 | protected void initToolbar() { 40 | mTvBack = findViewById(R.id.toolbar_back); 41 | if (enableBackIcon()) { 42 | mTvBack.setVisibility(View.VISIBLE); 43 | mTvBack.setOnClickListener(new View.OnClickListener() { 44 | @Override 45 | public void onClick(View v) { 46 | finish(); 47 | } 48 | }); 49 | } else { 50 | mTvBack.setVisibility(View.GONE); 51 | } 52 | 53 | mTvTitle = findViewById(R.id.toolbar_title); 54 | if (mTvTitle != null) { 55 | String title = getToolbarTitle(); 56 | if (!TextUtils.isEmpty(title)) { 57 | mTvTitle.setText(title); 58 | } 59 | } 60 | } 61 | 62 | public boolean enableStatusBarCompat() { 63 | return true; 64 | } 65 | 66 | public boolean enableBackIcon() { 67 | return true; 68 | } 69 | 70 | public String getToolbarTitle() { 71 | return ""; 72 | } 73 | 74 | protected void hideStatusBar() { 75 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 76 | attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 77 | getWindow().setAttributes(attrs); 78 | if (statusBarView != null) { 79 | statusBarView.setBackgroundColor(Color.TRANSPARENT); 80 | } 81 | } 82 | 83 | protected void showStatusBar() { 84 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 85 | attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; 86 | getWindow().setAttributes(attrs); 87 | if (statusBarView != null) { 88 | statusBarView.setBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimaryDark)); 89 | } 90 | } 91 | 92 | protected boolean isVisible(View view) { 93 | return view.getVisibility() == View.VISIBLE; 94 | } 95 | 96 | protected void gone(final View... views) { 97 | if (views != null && views.length > 0) { 98 | for (View view : views) { 99 | if (view != null) { 100 | view.setVisibility(View.GONE); 101 | } 102 | } 103 | } 104 | } 105 | 106 | protected void invisible(final View... views) { 107 | if (views != null && views.length > 0) { 108 | for (View view : views) { 109 | if (view != null) { 110 | view.setVisibility(View.INVISIBLE); 111 | } 112 | } 113 | } 114 | } 115 | 116 | protected void visible(final View... views) { 117 | if (views != null && views.length > 0) { 118 | for (View view : views) { 119 | if (view != null) { 120 | view.setVisibility(View.VISIBLE); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/base/BaseTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.base; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | import com.qy.reader.common.R; 12 | import com.qy.reader.common.utils.Nav; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by yuyuhang on 2018/1/9. 19 | */ 20 | public abstract class BaseTabActivity extends BaseActivity implements View.OnClickListener { 21 | 22 | protected static final int HOME_INDEX = 0; 23 | protected static final int SEARCH_INDEX = 1; 24 | protected static final int DISCOVER_INDEX = 2; 25 | protected static final int MINE_INDEX = 3; 26 | 27 | protected Fragment mFragment; 28 | 29 | protected TextView mTvHome, mTvSearch, mTvDiscover, mTvMine; 30 | 31 | private List mTabList = new ArrayList<>(); 32 | 33 | private static int currentIndex = -1; 34 | 35 | protected int pageIndex = -1; 36 | 37 | @Override 38 | protected void onCreate(@Nullable Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | initContentView(); 42 | } 43 | 44 | private void initContentView() { 45 | pageIndex = getCurrentIndex(); 46 | mFragment = fragmentInstance(); 47 | 48 | setContentView(R.layout.activity_base_tab); 49 | 50 | mTvHome = findViewById(R.id.tv_tab_home); 51 | mTvSearch = findViewById(R.id.tv_tab_search); 52 | mTvDiscover = findViewById(R.id.tv_tab_discover); 53 | mTvMine = findViewById(R.id.tv_tab_mine); 54 | 55 | mTvHome.setOnClickListener(this); 56 | mTvSearch.setOnClickListener(this); 57 | mTvDiscover.setOnClickListener(this); 58 | mTvMine.setOnClickListener(this); 59 | 60 | mTabList.clear(); 61 | mTabList.add(new TAB(mTvHome, "qyreader://home")); 62 | mTabList.add(new TAB(mTvSearch, "qyreader://search")); 63 | mTabList.add(new TAB(mTvDiscover, "qyreader://discover")); 64 | mTabList.add(new TAB(mTvMine, "qyreader://mine")); 65 | 66 | getSupportFragmentManager().beginTransaction().replace(R.id.fl_tab_content, mFragment).commitNowAllowingStateLoss(); 67 | } 68 | 69 | protected abstract int getCurrentIndex(); 70 | 71 | protected abstract Fragment fragmentInstance(); 72 | 73 | @Override 74 | protected void onResume() { 75 | super.onResume(); 76 | currentIndex = pageIndex; 77 | for (int i = 0; i < mTabList.size(); i++) { 78 | mTabList.get(i).tabView.setSelected(i == currentIndex); 79 | } 80 | } 81 | 82 | @Override 83 | public void onClick(View v) { 84 | for (int i = 0; i < mTabList.size(); i++) { 85 | TAB tab = mTabList.get(i); 86 | if (tab.tabView == v) { 87 | if (currentIndex != i) { 88 | Nav.from(this) 89 | .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) 90 | .overridePendingTransition(0, 0) 91 | .start(tab.url); 92 | } 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public void finish() { 99 | super.finish(); 100 | overridePendingTransition(0, 0); 101 | } 102 | 103 | public static class TAB { 104 | 105 | View tabView; 106 | 107 | String url; 108 | 109 | public TAB(View tabView, String url) { 110 | this.tabView = tabView; 111 | this.url = url; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/book/BookDetail.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.book; 2 | 3 | import com.qy.reader.common.entity.chapter.Chapter; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 书籍详情 9 | *

10 | * Created by yuyuhang on 2018/1/7. 11 | */ 12 | public class BookDetail extends SearchBook { 13 | 14 | public List chapters; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/book/SearchBook.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.book; 2 | 3 | import com.qy.reader.common.entity.source.Source; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * 搜索结果(搜索时,书名和作者名一样时,认为是同一本书,合并起来,同时增加一个源) 11 | *

12 | * Created by yuyuhang on 2018/1/7. 13 | */ 14 | public class SearchBook implements Serializable{ 15 | 16 | /** 17 | * 封面图 18 | */ 19 | public String cover; 20 | 21 | /** 22 | * 书名 23 | */ 24 | public String title; 25 | 26 | /** 27 | * 作者名 28 | */ 29 | public String author; 30 | 31 | /** 32 | * 描述 33 | */ 34 | public String desc; 35 | 36 | /** 37 | * 源&目录列表链接 38 | */ 39 | public List sources = new ArrayList<>(); 40 | 41 | /** 42 | * 源和对应链接 43 | */ 44 | public static class SL implements Serializable{ 45 | public String link; 46 | 47 | public Source source; 48 | 49 | public SL(String link, Source source) { 50 | this.link = link; 51 | this.source = source; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/chapter/Chapter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.chapter; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 章节 7 | *

8 | * Created by yuyuhang on 2018/1/7. 9 | */ 10 | public class Chapter implements Serializable { 11 | 12 | public String title; 13 | 14 | public String lastUpdateTime; 15 | 16 | public String link; 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/source/Source.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.source; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 源 7 | *

8 | * Created by yuyuhang on 2018/1/7. 9 | */ 10 | public class Source implements Serializable { 11 | 12 | @SourceID 13 | public int id; 14 | 15 | public String name; 16 | 17 | public String searchURL; 18 | 19 | /** 20 | * 最少输入字数 21 | */ 22 | public int minKeywords = 1; 23 | 24 | public Source(@SourceID int id, String name, String searchURL) { 25 | this.id = id; 26 | this.name = name; 27 | this.searchURL = searchURL; 28 | } 29 | 30 | public Source(@SourceID int id, String name, String searchURL, int minKeywords) { 31 | this.id = id; 32 | this.name = name; 33 | this.searchURL = searchURL; 34 | this.minKeywords = minKeywords; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/source/SourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.source; 2 | 3 | /** 4 | * 默认配置 5 | * 可能有部分源,比较复杂,需要多个xpath,那就继承重写 6 | *

7 | * Created by quezhongsang on 2018/1/7. 8 | */ 9 | public class SourceConfig { 10 | 11 | @SourceID 12 | public int id; 13 | 14 | /** 15 | * 搜索 16 | */ 17 | public Search search; 18 | 19 | /** 20 | * 小说目录内容 21 | */ 22 | public Catalog catalog; 23 | 24 | /** 25 | * 小说内容 26 | */ 27 | public Content content; 28 | 29 | public SourceConfig(@SourceID int id) { 30 | this.id = id; 31 | } 32 | 33 | @SourceID 34 | public int getId() { 35 | return id; 36 | } 37 | 38 | public void setId(@SourceID int id) { 39 | this.id = id; 40 | } 41 | 42 | public static class Search { 43 | 44 | public String charset; 45 | 46 | public String xpath; 47 | 48 | public String coverXpath; 49 | 50 | public String titleXpath; 51 | 52 | public String linkXpath; 53 | 54 | public String authorXpath; 55 | 56 | public String descXpath; 57 | } 58 | 59 | public static class Catalog { 60 | public String xpath; 61 | 62 | public String titleXpath; 63 | 64 | public String linkXpath; 65 | } 66 | 67 | public static class Content { 68 | public String xpath; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/source/SourceEnable.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.source; 2 | 3 | /** 4 | * Created by yuyuhang on 2018/1/12. 5 | */ 6 | public class SourceEnable { 7 | 8 | @SourceID 9 | public int id; 10 | 11 | public boolean enable; 12 | 13 | public SourceEnable(int id, boolean enable) { 14 | this.id = id; 15 | this.enable = enable; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/entity/source/SourceID.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.entity.source; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | SourceID.LIEWEN, 10 | SourceID.CHINESE81, 11 | SourceID.ZHUISHU, 12 | SourceID.BIQUG, 13 | SourceID.WENXUEMI, 14 | SourceID.CHINESEXIAOSHUO, 15 | SourceID.DINGDIAN, 16 | SourceID.BIQUGER, 17 | SourceID.CHINESEZHUOBI, 18 | SourceID.DASHUBAO, 19 | SourceID.CHINESEWUZHOU, 20 | SourceID.UCSHUMENG, 21 | SourceID.QUANXIAOSHUO, 22 | SourceID.YANMOXUAN, 23 | SourceID.AIQIWENXUE, 24 | SourceID.QIANQIANXIAOSHUO, 25 | SourceID.PIAOTIANWENXUE, 26 | SourceID.SUIMENGXIAOSHUO, 27 | SourceID.DAJIADUSHUYUAN, 28 | SourceID.SHUQIBA, 29 | SourceID.XIAOSHUO52, 30 | 31 | }) 32 | @Retention(RetentionPolicy.SOURCE) 33 | public @interface SourceID { 34 | 35 | /** 36 | * 猎文网 37 | */ 38 | int LIEWEN = 1; 39 | 40 | /** 41 | * 81中文网 42 | */ 43 | int CHINESE81 = 2; 44 | 45 | /** 46 | * 追书网 47 | */ 48 | int ZHUISHU = 3; 49 | 50 | /** 51 | * 笔趣阁 52 | */ 53 | int BIQUG = 4; 54 | 55 | /** 56 | * 文学迷 57 | */ 58 | int WENXUEMI = 5; 59 | 60 | /** 61 | * 小说中文网 62 | */ 63 | int CHINESEXIAOSHUO = 6; 64 | 65 | /** 66 | * 顶点小说 67 | */ 68 | int DINGDIAN = 7; 69 | 70 | /** 71 | * 笔趣阁儿 72 | */ 73 | int BIQUGER = 8; 74 | 75 | /** 76 | * 着笔中文网 77 | */ 78 | int CHINESEZHUOBI = 9; 79 | 80 | /** 81 | * 大书包 82 | */ 83 | int DASHUBAO = 10; 84 | 85 | /** 86 | * 梧州中文台 87 | */ 88 | int CHINESEWUZHOU = 11; 89 | 90 | /** 91 | * UC书盟 92 | */ 93 | int UCSHUMENG = 12; 94 | 95 | /** 96 | * 全小说 97 | */ 98 | int QUANXIAOSHUO = 13; 99 | 100 | /** 101 | * 衍墨轩 102 | */ 103 | int YANMOXUAN = 14; 104 | 105 | /** 106 | * 爱奇文学 107 | */ 108 | int AIQIWENXUE = 15; 109 | 110 | /** 111 | * 千千小说 112 | */ 113 | int QIANQIANXIAOSHUO = 16; 114 | 115 | /** 116 | * 飘天文学网 117 | */ 118 | int PIAOTIANWENXUE = 17; 119 | 120 | /** 121 | * 随梦小说网 122 | */ 123 | int SUIMENGXIAOSHUO = 18; 124 | 125 | /** 126 | * 大家读书苑 127 | */ 128 | int DAJIADUSHUYUAN = 19; 129 | 130 | /** 131 | * 书旗吧 132 | */ 133 | int SHUQIBA = 20; 134 | 135 | /** 136 | * 小说52 137 | */ 138 | int XIAOSHUO52 = 21; 139 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/AndroidRomUtil.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.os.Build; 4 | import android.os.Environment; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.lang.reflect.Method; 10 | import java.util.Collection; 11 | import java.util.Enumeration; 12 | import java.util.Map; 13 | import java.util.Properties; 14 | import java.util.Set; 15 | 16 | /** 17 | * 判断国产rom 18 | *

19 | * Created by yuyuhang on 2018/1/8. 20 | */ 21 | public class AndroidRomUtil { 22 | 23 | private static final String KEY_EMUI_VERSION_CODE = "ro.build.version.emui"; 24 | private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; 25 | private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; 26 | private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage"; 27 | 28 | 29 | /** 30 | * 华为rom 31 | * 32 | * @return 33 | */ 34 | public static boolean isEMUI() { 35 | try { 36 | final BuildProperties prop = BuildProperties.newInstance(); 37 | return prop.getProperty(KEY_EMUI_VERSION_CODE, null) != null; 38 | } catch (final IOException e) { 39 | return false; 40 | } 41 | } 42 | 43 | /** 44 | * 小米rom 45 | * 46 | * @return 47 | */ 48 | public static boolean isMIUI() { 49 | try { 50 | final BuildProperties prop = BuildProperties.newInstance(); 51 | /*String rom = "" + prop.getProperty(KEY_MIUI_VERSION_CODE, null) + prop.getProperty(KEY_MIUI_VERSION_NAME, null)+prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null); 52 | Log.d("Android_Rom", rom);*/ 53 | return prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null 54 | || prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null 55 | || prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null; 56 | } catch (final IOException e) { 57 | return false; 58 | } 59 | } 60 | 61 | /** 62 | * 魅族rom 63 | * 64 | * @return 65 | */ 66 | public static boolean isFlyme() { 67 | try { 68 | final Method method = Build.class.getMethod("hasSmartBar"); 69 | return method != null; 70 | } catch (final Exception e) { 71 | return false; 72 | } 73 | } 74 | 75 | public static class BuildProperties { 76 | 77 | private final Properties properties; 78 | 79 | private BuildProperties() throws IOException { 80 | properties = new Properties(); 81 | properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); 82 | } 83 | 84 | public boolean containsKey(final Object key) { 85 | return properties.containsKey(key); 86 | } 87 | 88 | public boolean containsValue(final Object value) { 89 | return properties.containsValue(value); 90 | } 91 | 92 | public Set> entrySet() { 93 | return properties.entrySet(); 94 | } 95 | 96 | public String getProperty(final String name) { 97 | return properties.getProperty(name); 98 | } 99 | 100 | public String getProperty(final String name, final String defaultValue) { 101 | return properties.getProperty(name, defaultValue); 102 | } 103 | 104 | public boolean isEmpty() { 105 | return properties.isEmpty(); 106 | } 107 | 108 | public Enumeration keys() { 109 | return properties.keys(); 110 | } 111 | 112 | public Set keySet() { 113 | return properties.keySet(); 114 | } 115 | 116 | public int size() { 117 | return properties.size(); 118 | } 119 | 120 | public Collection values() { 121 | return properties.values(); 122 | } 123 | 124 | public static BuildProperties newInstance() throws IOException { 125 | return new BuildProperties(); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/AssetsUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | /** 9 | * Created by yuyuhang on 2018/1/8. 10 | */ 11 | public class AssetsUtils { 12 | 13 | public static String readAssetsTxt(Context context, String fileName) { 14 | try { 15 | InputStream is = context.getAssets().open(fileName); 16 | int size = is.available(); 17 | byte[] buffer = new byte[size]; 18 | is.read(buffer); 19 | is.close(); 20 | return new String(buffer, "utf-8"); 21 | } catch (IOException e) { 22 | e.printStackTrace(); 23 | } 24 | return ""; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/CleanUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.os.Environment; 4 | 5 | import com.qy.reader.common.Global; 6 | 7 | import java.io.File; 8 | 9 | public final class CleanUtils { 10 | 11 | private CleanUtils() { 12 | throw new UnsupportedOperationException("u can't instantiate me..."); 13 | } 14 | 15 | /** 16 | * 清除内部缓存 17 | *

/data/data/com.xxx.xxx/cache

18 | * 19 | * @return {@code true}: 清除成功
{@code false}: 清除失败 20 | */ 21 | public static boolean cleanInternalCache() { 22 | return deleteFilesInDir(Global.getApplication().getCacheDir()); 23 | } 24 | 25 | /** 26 | * 清除内部文件 27 | *

/data/data/com.xxx.xxx/files

28 | * 29 | * @return {@code true}: 清除成功
{@code false}: 清除失败 30 | */ 31 | public static boolean cleanInternalFiles() { 32 | return deleteFilesInDir(Global.getApplication().getFilesDir()); 33 | } 34 | 35 | /** 36 | * 清除内部数据库 37 | *

/data/data/com.xxx.xxx/databases

38 | * 39 | * @return {@code true}: 清除成功
{@code false}: 清除失败 40 | */ 41 | public static boolean cleanInternalDbs() { 42 | return deleteFilesInDir(new File(Global.getApplication().getFilesDir().getParent(), "databases")); 43 | } 44 | 45 | /** 46 | * 根据名称清除数据库 47 | *

/data/data/com.xxx.xxx/databases/dbName

48 | * 49 | * @param dbName 数据库名称 50 | * @return {@code true}: 清除成功
{@code false}: 清除失败 51 | */ 52 | public static boolean cleanInternalDbByName(final String dbName) { 53 | return Global.getApplication().deleteDatabase(dbName); 54 | } 55 | 56 | /** 57 | * 清除内部 SP 58 | *

/data/data/com.xxx.xxx/shared_prefs

59 | * 60 | * @return {@code true}: 清除成功
{@code false}: 清除失败 61 | */ 62 | public static boolean cleanInternalSp() { 63 | return deleteFilesInDir(new File(Global.getApplication().getFilesDir().getParent(), "shared_prefs")); 64 | } 65 | 66 | /** 67 | * 清除外部缓存 68 | *

/storage/emulated/0/android/data/com.xxx.xxx/cache

69 | * 70 | * @return {@code true}: 清除成功
{@code false}: 清除失败 71 | */ 72 | public static boolean cleanExternalCache() { 73 | return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) 74 | && deleteFilesInDir(Global.getApplication().getExternalCacheDir()); 75 | } 76 | 77 | /** 78 | * 清除自定义目录下的文件 79 | * 80 | * @param dirPath 目录路径 81 | * @return {@code true}: 清除成功
{@code false}: 清除失败 82 | */ 83 | public static boolean cleanCustomCache(final String dirPath) { 84 | return deleteFilesInDir(dirPath); 85 | } 86 | 87 | /** 88 | * 清除自定义目录下的文件 89 | * 90 | * @param dir 目录 91 | * @return {@code true}: 清除成功
{@code false}: 清除失败 92 | */ 93 | public static boolean cleanCustomCache(final File dir) { 94 | return deleteFilesInDir(dir); 95 | } 96 | 97 | public static boolean deleteFilesInDir(final String dirPath) { 98 | return deleteFilesInDir(getFileByPath(dirPath)); 99 | } 100 | 101 | private static boolean deleteFilesInDir(final File dir) { 102 | if (dir == null) return false; 103 | // 目录不存在返回 true 104 | if (!dir.exists()) return true; 105 | // 不是目录返回 false 106 | if (!dir.isDirectory()) return false; 107 | // 现在文件存在且是文件夹 108 | File[] files = dir.listFiles(); 109 | if (files != null && files.length != 0) { 110 | for (File file : files) { 111 | if (file.isFile()) { 112 | if (!file.delete()) return false; 113 | } else if (file.isDirectory()) { 114 | if (!deleteDir(file)) return false; 115 | } 116 | } 117 | } 118 | return true; 119 | } 120 | 121 | private static boolean deleteDir(final File dir) { 122 | if (dir == null) return false; 123 | // 目录不存在返回 true 124 | if (!dir.exists()) return true; 125 | // 不是目录返回 false 126 | if (!dir.isDirectory()) return false; 127 | // 现在文件存在且是文件夹 128 | File[] files = dir.listFiles(); 129 | if (files != null && files.length != 0) { 130 | for (File file : files) { 131 | if (file.isFile()) { 132 | if (!file.delete()) return false; 133 | } else if (file.isDirectory()) { 134 | if (!deleteDir(file)) return false; 135 | } 136 | } 137 | } 138 | return dir.delete(); 139 | } 140 | 141 | private static File getFileByPath(final String filePath) { 142 | return isSpace(filePath) ? null : new File(filePath); 143 | } 144 | 145 | private static boolean isSpace(final String s) { 146 | if (s == null) return true; 147 | for (int i = 0, len = s.length(); i < len; ++i) { 148 | if (!Character.isWhitespace(s.charAt(i))) { 149 | return false; 150 | } 151 | } 152 | return true; 153 | } 154 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/CloseUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | public final class CloseUtils { 7 | 8 | private CloseUtils() { 9 | throw new UnsupportedOperationException(""); 10 | } 11 | 12 | /** 13 | * 关闭 IO 14 | * 15 | * @param closeables closeables 16 | */ 17 | public static void closeIO(final Closeable... closeables) { 18 | if (closeables == null) return; 19 | for (Closeable closeable : closeables) { 20 | if (closeable != null) { 21 | try { 22 | closeable.close(); 23 | } catch (IOException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/EncryptUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * Created by yuyuhang on 2018/1/11. 8 | */ 9 | public class EncryptUtils { 10 | 11 | private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 12 | 13 | /** 14 | * MD5 加密 15 | * 16 | * @param data 明文字符串 17 | * @return 16 进制密文 18 | */ 19 | public static String encryptMD5ToString(final String data) { 20 | return encryptMD5ToString(data.getBytes()); 21 | } 22 | 23 | /** 24 | * MD5 加密 25 | * 26 | * @param data 明文字符串 27 | * @param salt 盐 28 | * @return 16 进制加盐密文 29 | */ 30 | public static String encryptMD5ToString(final String data, final String salt) { 31 | return bytes2HexString(encryptMD5((data + salt).getBytes())); 32 | } 33 | 34 | /** 35 | * MD5 加密 36 | * 37 | * @param data 明文字节数组 38 | * @return 16 进制密文 39 | */ 40 | public static String encryptMD5ToString(final byte[] data) { 41 | return bytes2HexString(encryptMD5(data)); 42 | } 43 | 44 | /** 45 | * MD5 加密 46 | * 47 | * @param data 明文字节数组 48 | * @param salt 盐字节数组 49 | * @return 16 进制加盐密文 50 | */ 51 | public static String encryptMD5ToString(final byte[] data, final byte[] salt) { 52 | if (data == null || salt == null) return null; 53 | byte[] dataSalt = new byte[data.length + salt.length]; 54 | System.arraycopy(data, 0, dataSalt, 0, data.length); 55 | System.arraycopy(salt, 0, dataSalt, data.length, salt.length); 56 | return bytes2HexString(encryptMD5(dataSalt)); 57 | } 58 | 59 | /** 60 | * MD5 加密 61 | * 62 | * @param data 明文字节数组 63 | * @return 密文字节数组 64 | */ 65 | public static byte[] encryptMD5(final byte[] data) { 66 | return hashTemplate(data, "MD5"); 67 | } 68 | 69 | /** 70 | * hash 加密模板 71 | * 72 | * @param data 数据 73 | * @param algorithm 加密算法 74 | * @return 密文字节数组 75 | */ 76 | private static byte[] hashTemplate(final byte[] data, final String algorithm) { 77 | if (data == null || data.length <= 0) return null; 78 | try { 79 | MessageDigest md = MessageDigest.getInstance(algorithm); 80 | md.update(data); 81 | return md.digest(); 82 | } catch (NoSuchAlgorithmException e) { 83 | e.printStackTrace(); 84 | return null; 85 | } 86 | } 87 | 88 | private static String bytes2HexString(final byte[] bytes) { 89 | if (bytes == null) return null; 90 | int len = bytes.length; 91 | if (len <= 0) return null; 92 | char[] ret = new char[len << 1]; 93 | for (int i = 0, j = 0; i < len; i++) { 94 | ret[j++] = hexDigits[bytes[i] >>> 4 & 0x0f]; 95 | ret[j++] = hexDigits[bytes[i] & 0x0f]; 96 | } 97 | return new String(ret); 98 | } 99 | 100 | private static byte[] hexString2Bytes(String hexString) { 101 | if (isSpace(hexString)) return null; 102 | int len = hexString.length(); 103 | if (len % 2 != 0) { 104 | hexString = "0" + hexString; 105 | len = len + 1; 106 | } 107 | char[] hexBytes = hexString.toUpperCase().toCharArray(); 108 | byte[] ret = new byte[len >> 1]; 109 | for (int i = 0; i < len; i += 2) { 110 | ret[i >> 1] = (byte) (hex2Dec(hexBytes[i]) << 4 | hex2Dec(hexBytes[i + 1])); 111 | } 112 | return ret; 113 | } 114 | 115 | private static int hex2Dec(final char hexChar) { 116 | if (hexChar >= '0' && hexChar <= '9') { 117 | return hexChar - '0'; 118 | } else if (hexChar >= 'A' && hexChar <= 'F') { 119 | return hexChar - 'A' + 10; 120 | } else { 121 | throw new IllegalArgumentException(); 122 | } 123 | } 124 | 125 | private static boolean isSpace(final String s) { 126 | if (s == null) return true; 127 | for (int i = 0, len = s.length(); i < len; ++i) { 128 | if (!Character.isWhitespace(s.charAt(i))) { 129 | return false; 130 | } 131 | } 132 | return true; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/11. 9 | */ 10 | public class FileUtils { 11 | 12 | public static boolean isSdCardAvailable() { 13 | return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); 14 | } 15 | 16 | public static String createBookRootPath() { 17 | String bookPath = ""; 18 | if (isSdCardAvailable()) { 19 | bookPath = Environment.getExternalStorageDirectory().getPath() + "/qyreader"; 20 | createDir(bookPath); 21 | } 22 | return bookPath; 23 | } 24 | 25 | /** 26 | * 递归创建文件夹 27 | * 28 | * @param dirPath 29 | * @return 创建失败返回"" 30 | */ 31 | public static String createDir(String dirPath) { 32 | try { 33 | File file = new File(dirPath); 34 | if (file.getParentFile().exists()) { 35 | if (!file.exists()) { 36 | LogUtils.i("----- 创建文件夹" + file.getAbsolutePath()); 37 | file.mkdir(); 38 | } else { 39 | LogUtils.i("----- 文件夹已存在" + file.getAbsolutePath()); 40 | } 41 | return file.getAbsolutePath(); 42 | } else { 43 | createDir(file.getParentFile().getAbsolutePath()); 44 | LogUtils.i("----- 创建文件夹" + file.getAbsolutePath()); 45 | file.mkdir(); 46 | } 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | return dirPath; 51 | } 52 | 53 | /** 54 | * 递归创建文件夹 55 | * 56 | * @param file 57 | * @return 创建失败返回"" 58 | */ 59 | public static String createFile(File file) { 60 | try { 61 | if (file.getParentFile().exists()) { 62 | LogUtils.i("----- 创建文件" + file.getAbsolutePath()); 63 | file.createNewFile(); 64 | return file.getAbsolutePath(); 65 | } else { 66 | createDir(file.getParentFile().getAbsolutePath()); 67 | file.createNewFile(); 68 | LogUtils.i("----- 创建文件" + file.getAbsolutePath()); 69 | } 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | } 73 | return ""; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/HandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | /** 7 | * Created by yuyuhang on 2017/12/4. 8 | */ 9 | public class HandlerFactory { 10 | 11 | private static Handler sUIHandler; 12 | 13 | static { 14 | sUIHandler = new Handler(Looper.getMainLooper()); 15 | } 16 | 17 | public static Handler getUIHandler() { 18 | return sUIHandler; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/Nav.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | 10 | import java.net.URI; 11 | import java.net.URLDecoder; 12 | 13 | /** 14 | * 页面路由跳转 15 | *

16 | * Created by yuyuhang on 2018/1/13. 17 | */ 18 | public class Nav { 19 | 20 | private static final int INVALID = -1; 21 | 22 | private Context mContext; 23 | 24 | private Bundle mBundle; 25 | private int flags = INVALID; 26 | private int inAnim = INVALID, outAnim = INVALID; 27 | 28 | public Nav(Context context) { 29 | this.mContext = context; 30 | mBundle = new Bundle(); 31 | } 32 | 33 | public static Nav from(Context context) { 34 | return new Nav(context); 35 | } 36 | 37 | public Nav setExtras(Bundle bundle) { 38 | if (bundle != null) { 39 | mBundle.putAll(bundle); 40 | } 41 | return this; 42 | } 43 | 44 | public Nav setFlags(int flags) { 45 | this.flags = flags; 46 | return this; 47 | } 48 | 49 | public Nav overridePendingTransition(int enterAnim, int exitAnim) { 50 | this.inAnim = enterAnim; 51 | this.outAnim = exitAnim; 52 | return this; 53 | } 54 | 55 | public void start(String url) { 56 | start(url, -1); 57 | } 58 | 59 | public void start(String url, int reqCode) { 60 | if (TextUtils.isEmpty(url)) { 61 | recycle(); 62 | return; 63 | } 64 | 65 | try { 66 | URI uri = new URI(url); 67 | 68 | String schema = uri.getScheme(); 69 | String query = uri.getQuery(); 70 | if (!TextUtils.isEmpty(query)) { 71 | String[] params = query.split("&"); 72 | for (String param : params) { 73 | String[] kv = param.split("="); 74 | if (kv.length == 2) { 75 | String key = kv[0]; 76 | String value = kv[1]; 77 | mBundle.putString(key, URLDecoder.decode(value, "UTF-8")); 78 | } 79 | } 80 | } 81 | 82 | if (schema.equals("http") || schema.equals("https")) { 83 | 84 | } else { 85 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 86 | intent.putExtras(mBundle); 87 | if (flags >= 0) { 88 | intent.setFlags(flags); 89 | } 90 | if (reqCode >= 0 && mContext instanceof Activity) { 91 | ((Activity) mContext).startActivityForResult(intent, reqCode); 92 | } else { 93 | mContext.startActivity(intent); 94 | } 95 | if (mContext instanceof Activity && (inAnim >= 0 || outAnim >= 0)) { 96 | ((Activity) mContext).overridePendingTransition(inAnim, outAnim); 97 | } 98 | } 99 | } catch (Exception e) { 100 | LogUtils.e(e); 101 | } finally { 102 | recycle(); 103 | } 104 | } 105 | 106 | private void recycle() { 107 | mContext = null; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * Created by quezhongsang on 2018/1/13. 7 | */ 8 | public class StringUtils { 9 | 10 | public static boolean isEmpty(String s) { 11 | return TextUtils.isEmpty(s); 12 | } 13 | 14 | public static String getStr(String str) { 15 | return getStr(str, ""); 16 | } 17 | 18 | public static String getStr(Object str, String def) { 19 | return str == null || StringUtils.isEmpty(str.toString()) ? def : str.toString(); 20 | } 21 | 22 | public static String getStr(Object object) { 23 | return getStr(object, ""); 24 | } 25 | 26 | 27 | /** 28 | * 新增增了中文的空格 29 | * @param str 30 | * @return 31 | */ 32 | public static String trim(String str) { 33 | if (isEmpty(str)) 34 | return ""; 35 | 36 | int len = str.length(); 37 | int st = 0; 38 | 39 | while ((st < len) && (str.charAt(st) <= ' ' || str.charAt(st) == ' ')) { 40 | st++; 41 | } 42 | while ((st < len) && (str.charAt(len - 1) <= ' ' || str.charAt(st) == ' ')) { 43 | len--; 44 | } 45 | return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import android.view.Gravity; 4 | import android.view.LayoutInflater; 5 | import android.widget.Toast; 6 | 7 | import com.qy.reader.common.Global; 8 | import com.qy.reader.common.R; 9 | 10 | /** 11 | * Toast工具类,防止多个Toast连续显示 12 | * 13 | * @author yuyh. 14 | * @date 17/2/10. 15 | */ 16 | public class ToastUtils { 17 | 18 | private static Toast mToast; 19 | 20 | /********************** 非连续弹出的Toast ***********************/ 21 | public static void showSingleToast(int resId) { //R.string.** 22 | getSingleToast(resId, Toast.LENGTH_SHORT).show(); 23 | } 24 | 25 | public static void showSingleToast(String text) { 26 | getSingleToast(text, Toast.LENGTH_SHORT).show(); 27 | } 28 | 29 | public static void showSingleLongToast(int resId) { 30 | getSingleToast(resId, Toast.LENGTH_LONG).show(); 31 | } 32 | 33 | public static void showSingleLongToast(String text) { 34 | getSingleToast(text, Toast.LENGTH_LONG).show(); 35 | } 36 | 37 | /*********************** 连续弹出的Toast ************************/ 38 | public static void showToast(int resId) { 39 | getToast(resId, Toast.LENGTH_SHORT).show(); 40 | } 41 | 42 | public static void showToast(String text) { 43 | getToast(text, Toast.LENGTH_SHORT).show(); 44 | } 45 | 46 | public static void showLongToast(int resId) { 47 | getToast(resId, Toast.LENGTH_LONG).show(); 48 | } 49 | 50 | public static void showLongToast(String text) { 51 | getToast(text, Toast.LENGTH_LONG).show(); 52 | } 53 | 54 | public static Toast getSingleToast(int resId, int duration) { // 连续调用不会连续弹出,只是替换文本 55 | return getSingleToast(Global.getApplication().getResources().getText(resId).toString(), duration); 56 | } 57 | 58 | public static Toast getSingleToast(String text, int duration) { 59 | if (mToast == null) { 60 | mToast = new Toast(Global.getApplication()); 61 | mToast.setView(LayoutInflater.from(Global.getApplication()).inflate(R.layout.common_toast_layout, null)); 62 | mToast.setText(text); 63 | mToast.setGravity(Gravity.BOTTOM, 0, ScreenUtils.dpToPxInt(100)); 64 | } else { 65 | mToast.setText(text); 66 | } 67 | return mToast; 68 | } 69 | 70 | public static Toast getToast(int resId, int duration) { // 连续调用会连续弹出 71 | return getToast(Global.getApplication().getResources().getText(resId).toString(), duration); 72 | } 73 | 74 | public static Toast getToast(String text, int duration) { 75 | return Toast.makeText(Global.getApplication(), text, duration); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/utils/ZHConverter.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.utils; 2 | 3 | import com.qy.reader.common.Global; 4 | import com.qy.reader.common.widgets.reader.annotation.FontType; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Iterator; 13 | import java.util.Map; 14 | import java.util.Properties; 15 | import java.util.Set; 16 | 17 | 18 | public class ZHConverter { 19 | 20 | private Properties charMap = new Properties(); 21 | private Set conflictingSets = new HashSet<>(); 22 | private static final ZHConverter[] converters = new ZHConverter[2]; 23 | private static final String[] propertyFiles = new String[]{"zh2Hant.properties", "zh2Hans.properties"}; 24 | 25 | public static ZHConverter getInstance(@FontType int converterType) { 26 | if (converterType >= 0 && converterType < 2) { 27 | if (converters[converterType] == null) { 28 | synchronized (ZHConverter.class) { 29 | if (converters[converterType] == null) { 30 | converters[converterType] = new ZHConverter(propertyFiles[converterType]); 31 | } 32 | } 33 | } 34 | return converters[converterType]; 35 | } else { 36 | return null; 37 | } 38 | } 39 | 40 | public static String convert(String text, @FontType int converterType) { 41 | ZHConverter instance = getInstance(converterType); 42 | return instance.convert(text); 43 | } 44 | 45 | 46 | private ZHConverter(String propertyFile) { 47 | InputStream is = null; 48 | try { 49 | is = Global.getApplication().getAssets().open(propertyFile); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | } 53 | if (is != null) { 54 | BufferedReader reader = null; 55 | try { 56 | reader = new BufferedReader(new InputStreamReader(is)); 57 | charMap.load(reader); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } finally { 61 | try { 62 | if (reader != null) 63 | reader.close(); 64 | is.close(); 65 | } catch (IOException ignored) { 66 | } 67 | } 68 | } 69 | initializeHelper(); 70 | } 71 | 72 | @SuppressWarnings("rawtypes") 73 | private void initializeHelper() { 74 | Map stringPossibilities = new HashMap<>(); 75 | Iterator iter = charMap.keySet().iterator(); 76 | while (iter.hasNext()) { 77 | String key = (String) iter.next(); 78 | if (key.length() >= 1) { 79 | for (int i = 0; i < (key.length()); i++) { 80 | String keySubstring = key.substring(0, i + 1); 81 | if (stringPossibilities.containsKey(keySubstring)) { 82 | Integer integer = stringPossibilities.get(keySubstring); 83 | stringPossibilities.put(keySubstring, integer + 1); 84 | } else { 85 | stringPossibilities.put(keySubstring, 1); 86 | } 87 | } 88 | } 89 | } 90 | iter = stringPossibilities.keySet().iterator(); 91 | while (iter.hasNext()) { 92 | String key = (String) iter.next(); 93 | if (stringPossibilities.get(key) > 1) { 94 | conflictingSets.add(key); 95 | } 96 | } 97 | } 98 | 99 | public String convert(String in) { 100 | StringBuilder outString = new StringBuilder(); 101 | StringBuilder stackString = new StringBuilder(); 102 | for (int i = 0; i < in.length(); i++) { 103 | char c = in.charAt(i); 104 | String key = "" + c; 105 | stackString.append(key); 106 | if (conflictingSets.contains(stackString.toString())) { 107 | } else if (charMap.containsKey(stackString.toString())) { 108 | outString.append(charMap.get(stackString.toString())); 109 | stackString.setLength(0); 110 | } else { 111 | CharSequence sequence = stackString.subSequence(0, stackString.length() - 1); 112 | stackString.delete(0, stackString.length() - 1); 113 | flushStack(outString, new StringBuilder(sequence)); 114 | } 115 | } 116 | flushStack(outString, stackString); 117 | return outString.toString(); 118 | } 119 | 120 | private void flushStack(StringBuilder outString, StringBuilder stackString) { 121 | while (stackString.length() > 0) { 122 | if (charMap.containsKey(stackString.toString())) { 123 | outString.append(charMap.get(stackString.toString())); 124 | stackString.setLength(0); 125 | } else { 126 | outString.append(stackString.charAt(0)); 127 | stackString.delete(0, 1); 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/ListDialog.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.AdapterView; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.ListView; 10 | 11 | 12 | import me.drakeet.materialdialog.MaterialDialog; 13 | 14 | /** 15 | * Created by quezhongsang on 2018/1/13. 16 | */ 17 | public class ListDialog { 18 | 19 | MaterialDialog materialDialog; 20 | 21 | public ListDialog(MaterialDialog materialDialog) { 22 | this.materialDialog = materialDialog; 23 | } 24 | 25 | public static class Builder { 26 | MaterialDialog realBuilder; 27 | Context context; 28 | 29 | public Builder(Context context) { 30 | this.context = context; 31 | realBuilder = new MaterialDialog(context); 32 | } 33 | 34 | public ListDialog.Builder setTitle(@Nullable CharSequence title) { 35 | realBuilder.setTitle(title); 36 | return this; 37 | } 38 | 39 | public ListDialog.Builder setList(final String[] titles, final OnItemClickListener onItemClickListener) { 40 | if (titles == null || titles.length <= 0) 41 | return this; 42 | final ArrayAdapter arrayAdapter = new ArrayAdapter<>(this.context, android.R.layout.simple_list_item_1); 43 | for (String title : titles) { 44 | arrayAdapter.add(title + ""); 45 | } 46 | 47 | ListView listView = new ListView(context); 48 | listView.setLayoutParams(new ViewGroup.LayoutParams( 49 | ViewGroup.LayoutParams.MATCH_PARENT, 50 | ViewGroup.LayoutParams.MATCH_PARENT)); 51 | float scale = context.getApplicationContext().getResources().getDisplayMetrics().density; 52 | int dpAsPixels = (int) (8 * scale + 0.5f); 53 | listView.setPadding(0, dpAsPixels, 0, dpAsPixels); 54 | listView.setDividerHeight(0); 55 | listView.setAdapter(arrayAdapter); 56 | 57 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 58 | @Override 59 | public void onItemClick(AdapterView parent, View view, int position, long id) { 60 | if (onItemClickListener != null) { 61 | onItemClickListener.onItemClick(realBuilder, position, titles[position]); 62 | } 63 | realBuilder.dismiss(); 64 | } 65 | }); 66 | 67 | realBuilder.setContentView(listView); 68 | realBuilder.setCanceledOnTouchOutside(true); 69 | return this; 70 | } 71 | 72 | public ListDialog show() { 73 | realBuilder.show(); 74 | return new ListDialog(realBuilder); 75 | } 76 | } 77 | 78 | public void show() { 79 | if (materialDialog != null) { 80 | materialDialog.show(); 81 | } 82 | } 83 | 84 | 85 | public void dismiss() { 86 | if (materialDialog != null) 87 | materialDialog.dismiss(); 88 | } 89 | 90 | public interface OnItemClickListener { 91 | void onItemClick(MaterialDialog materialDialog, int position, String content); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/TagGroup.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.qy.reader.common.utils.ScreenUtils; 9 | 10 | /** 11 | * Created by yuyuhang on 2018/1/9. 12 | */ 13 | public class TagGroup extends ViewGroup { 14 | 15 | private static final int SIDE_MARGIN = ScreenUtils.dpToPxInt(10);//布局边距 16 | private static final int VERTICAL_SPACING = ScreenUtils.dpToPxInt(6); 17 | private static final int HORIZONTAL_SPACING = ScreenUtils.dpToPxInt(8); 18 | 19 | private int verticalSpacing = VERTICAL_SPACING; 20 | private int horizontalSpacing = HORIZONTAL_SPACING; 21 | 22 | public TagGroup(Context context) { 23 | this(context, null); 24 | } 25 | 26 | public TagGroup(Context context, AttributeSet attrs) { 27 | this(context, attrs, 0); 28 | } 29 | 30 | public TagGroup(Context context, AttributeSet attrs, int defStyle) { 31 | super(context, attrs, defStyle); 32 | } 33 | 34 | @Override 35 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 36 | int x = SIDE_MARGIN;//横坐标 37 | int y = 0;//纵坐标 38 | int rows = 1;//总行数 39 | 40 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 41 | 42 | int specWidth = MeasureSpec.getSize(widthMeasureSpec); 43 | int actualWidth = specWidth - SIDE_MARGIN * 2;//实际宽度; 44 | int childCount = getChildCount(); 45 | 46 | // 处理宽高都为 wrap_content 的情况 47 | if (widthSpecMode == MeasureSpec.AT_MOST) { 48 | for (int index = 0; index < childCount; index++) { 49 | View child = getChildAt(index); 50 | child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 51 | int width = child.getMeasuredWidth(); 52 | int height = child.getMeasuredHeight(); 53 | x += width + horizontalSpacing; 54 | if (x > actualWidth) {//换行 55 | x = width; 56 | rows++; 57 | } 58 | y = rows * (height + verticalSpacing); 59 | } 60 | if (rows == 1) { 61 | actualWidth = x; 62 | } 63 | } else { 64 | for (int index = 0; index < childCount; index++) { 65 | View child = getChildAt(index); 66 | child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 67 | int width = child.getMeasuredWidth(); 68 | int height = child.getMeasuredHeight(); 69 | x += width + horizontalSpacing; 70 | if (x > actualWidth) {//换行 71 | x = width; 72 | rows++; 73 | } 74 | y = rows * (height + verticalSpacing); 75 | } 76 | } 77 | setMeasuredDimension(actualWidth, y); 78 | } 79 | 80 | @Override 81 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 82 | int childCount = getChildCount(); 83 | int autualWidth = r - l; 84 | int x = SIDE_MARGIN;// 横坐标开始 85 | int y = 0;//纵坐标开始 86 | int rows = 1; 87 | for (int i = 0; i < childCount; i++) { 88 | View view = getChildAt(i); 89 | int width = view.getMeasuredWidth(); 90 | int height = view.getMeasuredHeight(); 91 | x += width; 92 | if (i != 0) 93 | x += horizontalSpacing; 94 | if (x > autualWidth) { 95 | x = width + SIDE_MARGIN; 96 | rows++; 97 | } 98 | y = rows * (height + verticalSpacing); 99 | view.layout(x - width, y - height, x, y); 100 | } 101 | } 102 | 103 | public int getVerticalSpacing() { 104 | return verticalSpacing; 105 | } 106 | 107 | public int getHorizontalSpacing() { 108 | return horizontalSpacing; 109 | } 110 | 111 | public void setVerticalSpacing(int verticalSpacing) { 112 | this.verticalSpacing = verticalSpacing; 113 | } 114 | 115 | public void setHorizontalSpacing(int horizontalSpacing) { 116 | this.horizontalSpacing = horizontalSpacing; 117 | } 118 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/BookManager.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.qy.reader.common.entity.source.SourceID; 6 | import com.qy.reader.common.utils.FileIOUtils; 7 | import com.qy.reader.common.utils.FileUtils; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * Created by yuyuhang on 2018/1/11. 13 | */ 14 | public class BookManager { 15 | 16 | private static BookManager instance; 17 | 18 | public static BookManager getInstance() { 19 | if (instance == null) { 20 | synchronized (BookManager.class) { 21 | instance = new BookManager(); 22 | } 23 | } 24 | return instance; 25 | } 26 | 27 | public String getBookNum(String title, String author) { 28 | StringBuilder result = new StringBuilder(); 29 | if (!TextUtils.isEmpty(title)) { 30 | result.append(title.trim()); 31 | } 32 | result.append("||"); 33 | if (!TextUtils.isEmpty(author)) { 34 | result.append(author.trim()); 35 | } 36 | return result.toString(); 37 | } 38 | 39 | public File getContentFile(@SourceID int sourceId, String bookNum, String chapterName) { 40 | String path = FileUtils.createBookRootPath() + File.separator 41 | + sourceId + File.separator 42 | + bookNum + File.separator + chapterName; 43 | File file = new File(path); 44 | if (!file.exists()) { 45 | FileUtils.createFile(file); 46 | } 47 | 48 | return file; 49 | } 50 | 51 | public boolean saveContentFile(@SourceID int sourceId, String bookNum, String chapterName, String content) { 52 | File file = getContentFile(sourceId, bookNum, chapterName); 53 | return FileIOUtils.writeFileFromString(file, content); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/FollowSlider.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Path; 5 | import android.view.ViewConfiguration; 6 | 7 | /** 8 | * 滑动翻页 9 | * 10 | * @author yuyh. 11 | * @date 17/3/25. 12 | */ 13 | public class FollowSlider extends BaseSlider { 14 | 15 | private Path mPath; 16 | 17 | public FollowSlider() { 18 | mPath = new Path(); 19 | } 20 | 21 | @Override 22 | void drawCurrentPageArea(Canvas canvas) { 23 | mPath.reset(); 24 | 25 | canvas.save(); 26 | 27 | // 手指滑动状态 28 | if (mMode == MODE_MOVE && mDirection != MOVE_NO_RESULT) { // 有效滑动(手指触摸滑动过程) 29 | mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); 30 | if (mDirection == MOVE_TO_LEFT) { 31 | mPath.addRect(0, 0, mScreenWidth - distance, mScreenHeight, Path.Direction.CW); 32 | canvas.clipPath(mPath); 33 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -distance, 0, null); 34 | } else if (mDirection == MOVE_TO_RIGHT) { 35 | mPath.addRect(-distance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 36 | canvas.clipPath(mPath); 37 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -distance, 0, null); 38 | } 39 | } else if (autoScrollLeft != SCROLL_NONE) { // 自动左滑 40 | mPath.addRect(0, 0, -scrollDistance, mScreenHeight, Path.Direction.CW); 41 | canvas.clipPath(mPath); 42 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -mScreenWidth - scrollDistance, 0, null); 43 | } else if (autoScrollRight != SCROLL_NONE) { // 自动右滑 44 | mPath.addRect(-scrollDistance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 45 | canvas.clipPath(mPath); 46 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -scrollDistance, 0, null); 47 | } else { // 初始状态或无效滑动(eg: 先往左滑,又往右滑到初始状态,继续往右就不动) 48 | mPath.addRect(0, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 49 | canvas.clipPath(mPath); 50 | canvas.drawBitmap(mReadView.getCurPageBitmap(), 0, 0, null); 51 | } 52 | canvas.restore(); 53 | } 54 | 55 | @Override 56 | void drawTempPageArea(Canvas canvas) { 57 | mPath.reset(); 58 | 59 | canvas.save(); 60 | 61 | if (mMode == MODE_MOVE && mDirection != MOVE_NO_RESULT) { 62 | mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); 63 | if (mDirection == MOVE_TO_LEFT) { 64 | mPath.addRect(mScreenWidth - distance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 65 | canvas.clipPath(mPath); 66 | canvas.drawBitmap(mReadView.getNextPageBitmap(), mScreenWidth - distance, 0, null); 67 | } else if (mDirection == MOVE_TO_RIGHT) { 68 | mPath.addRect(-mScreenWidth - distance, 0, -distance, mScreenHeight, Path.Direction.CW); 69 | canvas.clipPath(mPath); 70 | canvas.drawBitmap(mReadView.getPrePageBitmap(), -mScreenWidth - distance, 0, null); 71 | } 72 | } else { 73 | if (autoScrollLeft != SCROLL_NONE) { 74 | mPath.addRect(-scrollDistance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 75 | canvas.clipPath(mPath); 76 | canvas.drawBitmap(mReadView.getNextPageBitmap(), -scrollDistance, 0, null); 77 | } else if (autoScrollRight != SCROLL_NONE) { 78 | mPath.addRect(-mScreenWidth - scrollDistance, 0, -scrollDistance, mScreenHeight, Path.Direction.CW); 79 | canvas.clipPath(mPath); 80 | canvas.drawBitmap(mReadView.getPrePageBitmap(), -mScreenWidth - scrollDistance, 0, null); 81 | } 82 | } 83 | canvas.restore(); 84 | } 85 | 86 | @Override 87 | void drawShadow(Canvas canvas) { 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/OnPageStateChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | public interface OnPageStateChangedListener { 4 | 5 | void onCenterClick(); 6 | 7 | void onChapterChanged(int currentChapter, int fromChapter, boolean fromUser); 8 | 9 | void onPageChanged(int currentPage, int currentChapter); 10 | 11 | void onChapterLoadFailure(int currentChapter); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/OverlapSlider.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Path; 5 | import android.graphics.drawable.GradientDrawable; 6 | import android.view.ViewConfiguration; 7 | 8 | /** 9 | * @author yuyh. 10 | * @date 17/3/26. 11 | */ 12 | public class OverlapSlider extends BaseSlider { 13 | 14 | private Path mPath; 15 | 16 | private GradientDrawable mShadowDrawable; 17 | 18 | public OverlapSlider() { 19 | mPath = new Path(); 20 | 21 | int[] mBackShadowColors = new int[]{0xaa666666, 0x666666}; 22 | 23 | mShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors); 24 | mShadowDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT); 25 | } 26 | 27 | /** 28 | * 绘制当前页 29 | * 30 | * @param canvas 31 | */ 32 | void drawCurrentPageArea(Canvas canvas) { 33 | mPath.reset(); 34 | 35 | canvas.save(); 36 | 37 | // 手指滑动状态 38 | if (mMode == MODE_MOVE && mDirection != MOVE_NO_RESULT) { // 有效滑动(手指触摸滑动过程) 39 | mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); 40 | if (mDirection == MOVE_TO_LEFT) { 41 | mPath.addRect(0, 0, mScreenWidth - distance, mScreenHeight, Path.Direction.CW); 42 | canvas.clipPath(mPath); 43 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -distance, 0, null); 44 | } else if (mDirection == MOVE_TO_RIGHT) { 45 | mPath.addRect(-distance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 46 | canvas.clipPath(mPath); 47 | canvas.drawBitmap(mReadView.getCurPageBitmap(), 0, 0, null); 48 | } 49 | } else if (autoScrollLeft != SCROLL_NONE) { // 自动左滑 50 | mPath.addRect(0, 0, -scrollDistance, mScreenHeight, Path.Direction.CW); 51 | canvas.clipPath(mPath); 52 | canvas.drawBitmap(mReadView.getCurPageBitmap(), -mScreenWidth - scrollDistance, 0, null); 53 | } else if (autoScrollRight != SCROLL_NONE) { // 自动右滑 54 | mPath.addRect(-scrollDistance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 55 | canvas.clipPath(mPath); 56 | canvas.drawBitmap(mReadView.getCurPageBitmap(), 0, 0, null); 57 | } else { // 初始状态或无效滑动(eg: 先往左滑,又往右滑到初始状态,继续往右就不动) 58 | mPath.addRect(0, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 59 | canvas.clipPath(mPath); 60 | canvas.drawBitmap(mReadView.getCurPageBitmap(), 0, 0, null); 61 | } 62 | canvas.restore(); 63 | } 64 | 65 | /** 66 | * 绘制阴影 67 | * 68 | * @param canvas 69 | */ 70 | void drawShadow(Canvas canvas) { 71 | canvas.save(); 72 | if (mDirection != MOVE_NO_RESULT) { 73 | if (mDirection == MOVE_TO_LEFT) { 74 | mShadowDrawable.setBounds(mScreenWidth - distance, 0, mScreenWidth - distance + shadowSize, mScreenHeight); 75 | } else { 76 | mShadowDrawable.setBounds(-distance, 0, -distance + shadowSize, mScreenHeight); 77 | } 78 | } else { 79 | mShadowDrawable.setBounds(-scrollDistance, 0, -scrollDistance + shadowSize, mScreenHeight); 80 | } 81 | mShadowDrawable.draw(canvas); 82 | canvas.restore(); 83 | } 84 | 85 | /** 86 | * 绘制临时页(类似绘制当前页,只有在滑动过程需要绘制,否则只绘制当前页) 87 | * 88 | * @param canvas 89 | */ 90 | void drawTempPageArea(Canvas canvas) { 91 | mPath.reset(); 92 | 93 | canvas.save(); 94 | 95 | if (mMode == MODE_MOVE && mDirection != MOVE_NO_RESULT) { 96 | mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); 97 | if (mDirection == MOVE_TO_LEFT) { 98 | mPath.addRect(mScreenWidth - distance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 99 | canvas.clipPath(mPath); 100 | canvas.drawBitmap(mReadView.getNextPageBitmap(), 0, 0, null); 101 | } else if (mDirection == MOVE_TO_RIGHT) { 102 | mPath.addRect(-mScreenWidth - distance, 0, -distance, mScreenHeight, Path.Direction.CW); 103 | canvas.clipPath(mPath); 104 | canvas.drawBitmap(mReadView.getPrePageBitmap(), -mScreenWidth - distance, 0, null); 105 | } 106 | } else { 107 | if (autoScrollLeft != SCROLL_NONE) { 108 | mPath.addRect(-scrollDistance, 0, mScreenWidth, mScreenHeight, Path.Direction.CW); 109 | canvas.clipPath(mPath); 110 | canvas.drawBitmap(mReadView.getNextPageBitmap(), 0, 0, null); 111 | } else if (autoScrollRight != SCROLL_NONE) { 112 | mPath.addRect(-mScreenWidth - scrollDistance, 0, -scrollDistance, mScreenHeight, Path.Direction.CW); 113 | canvas.clipPath(mPath); 114 | canvas.drawBitmap(mReadView.getPrePageBitmap(), -mScreenWidth - scrollDistance, 0, null); 115 | } 116 | } 117 | canvas.restore(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/SettingManager.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | import com.qy.reader.common.Global; 4 | import com.qy.reader.common.utils.SPUtils; 5 | import com.qy.reader.common.utils.ScreenUtils; 6 | import com.qy.reader.common.widgets.reader.annotation.DensityLevel; 7 | import com.qy.reader.common.widgets.reader.annotation.FontType; 8 | 9 | public class SettingManager { 10 | 11 | @FontType 12 | public static int getFontType() { 13 | return SPUtils.getInstance().getInt("read_font_type", FontType.SIMPLIFIED); 14 | } 15 | 16 | public static void setFontType(@FontType int fontType) { 17 | SPUtils.getInstance().put("read_font_type", fontType); 18 | } 19 | 20 | public static boolean isTraditionalFont() { 21 | return getFontType() == FontType.TRADITIONAL; 22 | } 23 | 24 | public static int getTextSizeSP() { 25 | return SPUtils.getInstance().getInt("read_text_size_sp", 16); 26 | } 27 | 28 | public static void setTextSizeSP(int textSizeSP) { 29 | SPUtils.getInstance().put("read_text_size_sp", textSizeSP); 30 | } 31 | 32 | @DensityLevel 33 | public static int getDensityLevel() { 34 | return SPUtils.getInstance().getInt("read_density_level", DensityLevel.LEVEL_3); 35 | } 36 | 37 | public static void setDensityLevel(@DensityLevel int level) { 38 | SPUtils.getInstance().put("read_density_level", level); 39 | } 40 | 41 | public static boolean isLandscape() { 42 | return SPUtils.getInstance().getBoolean("read_lanscape", false); 43 | } 44 | 45 | public static void setLandscape(boolean isLandscape) { 46 | SPUtils.getInstance().put("read_lanscape", isLandscape); 47 | } 48 | 49 | 50 | public static int getBrightness() { 51 | return SPUtils.getInstance().getInt("read_brightness", (int) ScreenUtils.getScreenBrightness(Global.getApplication())); 52 | } 53 | 54 | public static void setBrightness(int brightness) { 55 | SPUtils.getInstance().put("read_brightness", brightness); 56 | } 57 | 58 | public static boolean isUseSystemBrightness() { 59 | return SPUtils.getInstance().getBoolean("read_use_system_brightness", true); 60 | } 61 | 62 | public static void setUseSystemBrightness(boolean useSystemBrightness) { 63 | SPUtils.getInstance().put("read_use_system_brightness", useSystemBrightness); 64 | } 65 | 66 | public static boolean isNightMode() { 67 | return SPUtils.getInstance().getBoolean("read_night_mode", false); 68 | } 69 | 70 | public static void setNightMode(boolean isNightMode) { 71 | SPUtils.getInstance().put("read_night_mode", isNightMode); 72 | } 73 | 74 | public static int getThemeColorIndex() { 75 | return SPUtils.getInstance().getInt("read_theme_color", 0); 76 | } 77 | 78 | public static void setThemeColorIndex(int index) { 79 | SPUtils.getInstance().put("read_theme_color", index); 80 | } 81 | 82 | public static void setEnableVolumePage(boolean enable) { 83 | SPUtils.getInstance().put("read_volume_page", enable); 84 | } 85 | 86 | public static boolean isEnableVolumePage() { 87 | return SPUtils.getInstance().getBoolean("read_volume_page", false); 88 | } 89 | 90 | public static void saveLastReadChapter(String bookNum, String chapterNum) { 91 | SPUtils.getInstance().put("last_read_chapter" + bookNum, chapterNum); 92 | } 93 | 94 | public static String getLastReadChapter(String bookNum) { 95 | return SPUtils.getInstance().getString("last_read_chapter" + bookNum); 96 | } 97 | 98 | 99 | public static void saveLastReadPage(String bookNum, int currentPage) { 100 | SPUtils.getInstance().put("last_read_page" + bookNum, currentPage); 101 | } 102 | 103 | public static int getLastReadPage(String bookNum) { 104 | return SPUtils.getInstance().getInt("last_read_page" + bookNum, 1); 105 | } 106 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/Slider.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader; 2 | 3 | import android.graphics.Canvas; 4 | import android.view.MotionEvent; 5 | 6 | /** 7 | * @author yuyh. 8 | * @date 17/3/24. 9 | */ 10 | 11 | public interface Slider { 12 | 13 | void bind(ReadView readView); 14 | 15 | void initListener(OnPageStateChangedListener listener); 16 | 17 | boolean onTouchEvent(MotionEvent event); 18 | 19 | void computeScroll(); 20 | 21 | void onDraw(Canvas canvas); 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/annotation/ChapterType.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader.annotation; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | ChapterType.PREVIOUS, 10 | ChapterType.CURRENT, 11 | ChapterType.NEXT 12 | }) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface ChapterType { 15 | 16 | /** 17 | * 上一章 18 | */ 19 | int PREVIOUS = 1; 20 | /** 21 | * 当前章 22 | */ 23 | int CURRENT = 2; 24 | /** 25 | * 下一章 26 | */ 27 | int NEXT = 3; 28 | 29 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/annotation/DensityLevel.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader.annotation; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | DensityLevel.LEVEL_1, 10 | DensityLevel.LEVEL_2, 11 | DensityLevel.LEVEL_3 12 | }) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface DensityLevel { 15 | int LEVEL_1 = 20; 16 | int LEVEL_2 = 14; 17 | int LEVEL_3 = 8; 18 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/annotation/DrawPageType.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader.annotation; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | DrawPageType.DRAW_CUR_PAGE, 10 | DrawPageType.DRAW_NEXT_PAGE, 11 | DrawPageType.DRAW_PRE_PAGE 12 | }) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface DrawPageType { 15 | 16 | /** 17 | * 绘制上一页 18 | */ 19 | int DRAW_PRE_PAGE = 1; 20 | 21 | /** 22 | * 绘制当前页 23 | */ 24 | int DRAW_CUR_PAGE = 2; 25 | 26 | /** 27 | * 绘制下一页 28 | */ 29 | int DRAW_NEXT_PAGE = 3; 30 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/annotation/FontType.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader.annotation; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | FontType.SIMPLIFIED, 10 | FontType.TRADITIONAL, 11 | 12 | }) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface FontType { 15 | 16 | /** 17 | * 简体 18 | */ 19 | int TRADITIONAL = 0; 20 | 21 | /** 22 | * 繁体 23 | */ 24 | int SIMPLIFIED = 1; 25 | } -------------------------------------------------------------------------------- /common/src/main/java/com/qy/reader/common/widgets/reader/annotation/SlideMode.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.common.widgets.reader.annotation; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | SlideMode.OVERLAP, 10 | SlideMode.FOLLOW 11 | }) 12 | @Retention(RetentionPolicy.SOURCE) 13 | public @interface SlideMode { 14 | /** 15 | * 滑动覆盖模式 16 | */ 17 | int OVERLAP = 1; 18 | 19 | /** 20 | * 滑动跟随模式 21 | */ 22 | int FOLLOW = 2; 23 | } -------------------------------------------------------------------------------- /common/src/main/res/anim/sneaker_popup_hide.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/anim/sneaker_popup_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_book_read_power.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_book_read_power.9.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_common_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_common_back.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_search_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_search_bar.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_search_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_search_delete.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_search_source_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_search_source_setting.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_discover_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_discover_normal.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_discover_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_discover_pressed.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_home_normal.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_home_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_home_pressed.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_mine_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_mine_normal.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_mine_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_mine_pressed.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_search_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_search_normal.png -------------------------------------------------------------------------------- /common/src/main/res/drawable-xxhdpi/ic_tab_search_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/common/src/main/res/drawable-xxhdpi/ic_tab_search_pressed.png -------------------------------------------------------------------------------- /common/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/ic_success.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/ic_warning.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/seekbar_battery_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/selector_tab_discover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/selector_tab_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/selector_tab_mine.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/selector_tab_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/selector_tab_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_search_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_tab_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/shape_toast_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/layout/activity_base_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 | 24 | 25 | 31 | 32 | 44 | 45 | 57 | 58 | 70 | 71 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /common/src/main/res/layout/common_status_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /common/src/main/res/layout/common_toast_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 18 | 19 | -------------------------------------------------------------------------------- /common/src/main/res/layout/common_tool_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 24 | 25 | 33 | 34 | 35 | 36 | 40 | -------------------------------------------------------------------------------- /common/src/main/res/layout/layout_read_power.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_search_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 22 | 23 | 39 | 40 | 47 | 48 | 49 | 55 | 56 | 62 | 63 | -------------------------------------------------------------------------------- /common/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #FFFFFF 5 | #26ADDA 6 | 7 | #999999 8 | #787878 9 | #585858 10 | #F3F3F3 11 | #CCCCCC 12 | 13 | #333333 14 | #40c6bf 15 | 16 | #666666 17 | 18 | #999999 19 | 20 | 21 | -------------------------------------------------------------------------------- /common/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3dp 5 | 6dp 6 | 10dp 7 | 15dp 8 | 20dp 9 | 30dp 10 | 40dp 11 | 50dp 12 | 60dp 13 | 70dp 14 | 80dp 15 | 90dp 16 | 100dp 17 | 110dp 18 | 19 | 0.5dp 20 | 1dp 21 | 2dp 22 | 10dp 23 | 12dp 24 | 14dp 25 | 16dp 26 | 18dp 27 | 20dp 28 | 29 | 30 | 0.5dp 31 | 32 | -------------------------------------------------------------------------------- /common/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | common 3 | 4 | -------------------------------------------------------------------------------- /common/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 31 | 32 | 39 | 40 | 41 | 49 | 50 | 58 | 59 | 66 | 67 | 74 | 75 | 82 | 83 | 84 | 90 | 91 | 96 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | 3 | android = [compileSdkVersion: 26, 4 | buildToolsVersion: "27.0.1", 5 | applicationId : "com.qy.reader", 6 | minSdkVersion : 14, 7 | targetSdkVersion : 26, 8 | versionCode : 100, 9 | versionName : "1.0.0"] 10 | 11 | dependencies = ["appcompat-v7" : "com.android.support:appcompat-v7:27.0.2", 12 | "design" : "com.android.support:design:27.0.2", 13 | "recyclerview-v7" : "com.android.support:recyclerview-v7:27.0.2", 14 | "support-annotation" : "com.android.support:support-annotations:27.0.2", 15 | "kotlin" : "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.10", 16 | "gson" : "com.google.code.gson:gson:2.8.2", 17 | "glide" : "com.github.bumptech.glide:glide:3.7.0", 18 | "easy-adapter" : "com.yuyh.easyadapter:library:1.1.5" 19 | ] 20 | } -------------------------------------------------------------------------------- /crawler/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /crawler/book_catagory.config: -------------------------------------------------------------------------------- 1 | 2 | // 猎文网 3 | http://zhannei.baidu.com/cse/search?s=10474238079390279291&q=%E7%88%B1 4 | https://www.liewen.cc/search.php?keyword=%E4%BD%A0%E5%A5%BD 5 | 6 | // 八一中文网 7 | http://zhannei.baidu.com/cse/search?s=15390153038627446418&ie=utf-8&q=%E7%88%B1 8 | https://www.zwdu.com/search.php?keyword=%E4%BD%A0%E5%A5%BD 9 | 10 | // 追书网 11 | http://zhannei.baidu.com/cse/search?s=4742658073044713703&ie=utf-8&q=%E7%88%B1 12 | https://www.zhuishu.tw/search.aspx?keyword=%E4%BD%A0%E5%A5%BD 13 | 14 | // 笔趣阁 15 | http://zhannei.baidu.com/cse/search?s=1393206249994657467&q=%E7%88%B1 16 | http://zhannei.baidu.com/cse/search?s=1393206249994657467&q=%E4%BD%A0%E5%A5%BD 17 | 18 | // 文学迷 19 | http://zhannei.baidu.com/cse/search?s=678186666498191015&ie=utf-8&q=%E7%88%B1 20 | http://www.wenxuemi.com/search.php?keyword=%E4%BD%A0%E5%A5%BD 21 | 22 | // 小说中文网 23 | http://zhannei.baidu.com/cse/search?s=10385337132858012269&q=%E7%88%B1 24 | http://www.xszww.com/s.php?ie=gbk&s=10385337132858012269&q=%C4%E3%BA%C3 25 | 26 | // 顶点小说 27 | http://zhannei.baidu.com/cse/search?s=1682272515249779940&q=%E7%88%B1 28 | 29 | // 笔趣阁2 30 | http://zhannei.baidu.com/cse/search?s=7928441616248544648&ie=utf-8&q=%E7%88%B1 31 | 32 | // 着笔中文网 33 | http://www.zbzw.com/s.php?ie=utf-8&s=4619765769851182557&q=%E7%88%B1 34 | 35 | // 大书包 36 | http://zn.dashubao.net/cse/search?s=9410583021346449776&entry=1&ie=utf-8&q=%E7%88%B1 37 | 38 | // 梧州中文台 39 | http://www.gxwztv.com/search.htm?keyword=%E7%88%B1 40 | 41 | // UC书盟(大于四个字才行) 42 | http://www.uctxt.com/modules/article/search.php?searchkey=%BF%D6%C1%FA%CA%B1%B4%FA 43 | 44 | // 全小说 45 | http://qxs.la/s_%E7%88%B1 46 | 47 | // 衍墨轩 48 | http://www.ymoxuan.com/search.htm?keyword=%E7%88%B1 49 | 50 | // 爱奇文学 51 | http://m.i7wx.com/?m=book%2Fsearch&keyword=%E7%88%B1 52 | 53 | // 千千小说(大于四个字才行) 54 | http://www.xqqxs.com/modules/article/search.php?searchkey=%BF%D6%C1%FA%CA%B1%B4%FA 55 | 56 | // 飘天文学网 57 | http://www.piaotian.com/modules/article/search.php?searchkey=%B0%AE 58 | 59 | // 随梦小说网(大于四个字才行) 60 | http://m.suimeng.la/modules/article/search.php?searchkey=%BF%D6%C1%FA%CA%B1%B4%FA 61 | 62 | // 大家读书苑 63 | http://www.dajiadu.net/modules/article/searchab.php?searchkey=%B0%AE 64 | 65 | // 书旗吧(大于四个字才行) 66 | http://www.shuqiba.com/modules/article/search.php?searchkey=%B0%AE 67 | 68 | // 小说52 69 | http://m.xs52.com/search.php?searchkey=%E7%88%B1 -------------------------------------------------------------------------------- /crawler/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.android.compileSdkVersion 6 | buildToolsVersion rootProject.ext.android.buildToolsVersion 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.ext.android.minSdkVersion 10 | targetSdkVersion rootProject.ext.android.targetSdkVersion 11 | versionCode rootProject.ext.android.versionCode 12 | versionName rootProject.ext.android.versionName 13 | } 14 | 15 | buildTypes { 16 | release { 17 | postprocessing { 18 | removeUnusedCode false 19 | removeUnusedResources false 20 | obfuscate false 21 | optimizeCode false 22 | proguardFile 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(include: ['*.jar'], dir: 'libs') 31 | 32 | implementation rootProject.ext.dependencies["support-annotation"] 33 | implementation rootProject.ext.dependencies["gson"] 34 | 35 | implementation project(path: ':common') 36 | 37 | implementation 'org.jsoup:jsoup:1.11.1' 38 | implementation 'org.apache.commons:commons-lang3:3.3.2' 39 | } 40 | -------------------------------------------------------------------------------- /crawler/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /crawler/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /crawler/src/main/assets/SourceEnable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "enable": "true" 5 | }, 6 | { 7 | "id": "4", 8 | "enable": "true" 9 | }, 10 | { 11 | "id": "7", 12 | "enable": "true" 13 | }, 14 | { 15 | "id": "8", 16 | "enable": "true" 17 | }, 18 | { 19 | "id": "12", 20 | "enable": "true" 21 | }, 22 | { 23 | "id": "14", 24 | "enable": "true" 25 | } 26 | ] -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/api/API.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.api; 2 | 3 | /** 4 | * 暂定 5 | *

6 | * Created by yuyuhang on 2018/1/7. 7 | */ 8 | public class API { 9 | } 10 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/source/callback/ChapterCallback.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.source.callback; 2 | 3 | import com.qy.reader.common.entity.chapter.Chapter; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by yuyuhang on 2018/1/8. 9 | */ 10 | public interface ChapterCallback { 11 | 12 | void onResponse(List chapters); 13 | 14 | void onError(String msg); 15 | } 16 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/source/callback/ContentCallback.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.source.callback; 2 | 3 | /** 4 | * Created by yuyuhang on 2018/1/8. 5 | */ 6 | public interface ContentCallback { 7 | 8 | void onResponse(String content); 9 | 10 | void onError(String msg); 11 | } 12 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/source/callback/SearchCallback.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.source.callback; 2 | 3 | import com.qy.reader.common.entity.book.SearchBook; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 搜索回调 9 | *

10 | * Created by yuyuhang on 2018/1/8. 11 | */ 12 | public interface SearchCallback { 13 | 14 | /** 15 | * @param keyword 搜索词 16 | * @param appendList 因为有很多源,所以分批回调回去,否则搜索等待时间很长 17 | */ 18 | void onResponse(String keyword, List appendList); 19 | 20 | /** 21 | * 所有源都查询完回调 22 | */ 23 | void onFinish(); 24 | 25 | void onError(String msg); 26 | } 27 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/core/AxisSelector.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.core; 2 | 3 | import org.jsoup.nodes.Element; 4 | import org.jsoup.select.Elements; 5 | 6 | /** 7 | * 通过轴选出对应作用域的全部节点 8 | * 去掉不实用的轴,不支持namespace,attribute(可用 /@*代替),preceding(preceding-sibling支持),following(following-sibling支持) 9 | * 添加 preceding-sibling-one,following-sibling-one,即只选前一个或后一个兄弟节点,添加 sibling 选取全部兄弟节点 10 | */ 11 | public class AxisSelector { 12 | /** 13 | * 自身 14 | * 15 | * @param e 16 | * @return 17 | */ 18 | public Elements self(Element e) { 19 | return new Elements(e); 20 | } 21 | 22 | /** 23 | * 父节点 24 | * 25 | * @param e 26 | * @return 27 | */ 28 | public Elements parent(Element e) { 29 | return new Elements(e.parent()); 30 | } 31 | 32 | /** 33 | * 直接子节点 34 | * 35 | * @param e 36 | * @return 37 | */ 38 | public Elements child(Element e) { 39 | return e.children(); 40 | } 41 | 42 | /** 43 | * 全部祖先节点 父亲,爷爷 , 爷爷的父亲... 44 | * 45 | * @param e 46 | * @return 47 | */ 48 | public Elements ancestor(Element e) { 49 | return e.parents(); 50 | } 51 | 52 | /** 53 | * 全部祖先节点和自身节点 54 | * 55 | * @param e 56 | * @return 57 | */ 58 | public Elements ancestorOrSelf(Element e) { 59 | Elements rs = e.parents(); 60 | rs.add(e); 61 | return rs; 62 | } 63 | 64 | /** 65 | * 全部子代节点 儿子,孙子,孙子的儿子... 66 | * 67 | * @param e 68 | * @return 69 | */ 70 | public Elements descendant(Element e) { 71 | return e.getAllElements(); 72 | } 73 | 74 | /** 75 | * 全部子代节点和自身 76 | * 77 | * @param e 78 | * @return 79 | */ 80 | public Elements descendantOrSelf(Element e) { 81 | Elements rs = e.getAllElements(); 82 | rs.add(e); 83 | return rs; 84 | } 85 | 86 | /** 87 | * 节点前面的全部同胞节点,preceding-sibling 88 | * 89 | * @param e 90 | * @return 91 | */ 92 | public Elements precedingSibling(Element e) { 93 | Elements rs = new Elements(); 94 | Element tmp = e.previousElementSibling(); 95 | while (tmp != null) { 96 | rs.add(tmp); 97 | tmp = tmp.previousElementSibling(); 98 | } 99 | return rs; 100 | } 101 | 102 | /** 103 | * 返回前一个同胞节点(扩展),语法 preceding-sibling-one 104 | * 105 | * @param e 106 | * @return 107 | */ 108 | public Elements precedingSiblingOne(Element e) { 109 | Elements rs = new Elements(); 110 | if (e.previousElementSibling() != null) { 111 | rs.add(e.previousElementSibling()); 112 | } 113 | return rs; 114 | } 115 | 116 | /** 117 | * 节点后面的全部同胞节点following-sibling 118 | * 119 | * @param e 120 | * @return 121 | */ 122 | public Elements followingSibling(Element e) { 123 | Elements rs = new Elements(); 124 | Element tmp = e.nextElementSibling(); 125 | while (tmp != null) { 126 | rs.add(tmp); 127 | tmp = tmp.nextElementSibling(); 128 | } 129 | return rs; 130 | } 131 | 132 | /** 133 | * 返回下一个同胞节点(扩展) 语法 following-sibling-one 134 | * 135 | * @param e 136 | * @return 137 | */ 138 | public Elements followingSiblingOne(Element e) { 139 | Elements rs = new Elements(); 140 | if (e.nextElementSibling() != null) { 141 | rs.add(e.nextElementSibling()); 142 | } 143 | return rs; 144 | } 145 | 146 | /** 147 | * 全部同胞(扩展) 148 | * 149 | * @param e 150 | * @return 151 | */ 152 | public Elements sibling(Element e) { 153 | return e.siblingElements(); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/core/Functions.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.core; 2 | /* 3 | Copyright 2014 Wang Haomiao 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | import com.qy.reader.crawler.xpath.model.JXNode; 19 | import com.qy.reader.crawler.xpath.util.CommonUtil; 20 | 21 | import org.jsoup.nodes.Element; 22 | import org.jsoup.select.Elements; 23 | 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | import java.util.regex.Matcher; 27 | import java.util.regex.Pattern; 28 | 29 | /** 30 | * xpath解析器的支持的全部函数集合,如需扩展按形式添加即可 31 | */ 32 | public class Functions { 33 | /** 34 | * 只获取节点自身的子文本 35 | * 36 | * @param context 37 | * @return 38 | */ 39 | public List text(Elements context) { 40 | List res = new LinkedList(); 41 | if (context != null && context.size() > 0) { 42 | for (Element e : context) { 43 | if (e.nodeName().equals("script")) { 44 | res.add(JXNode.t(e.data())); 45 | } else { 46 | res.add(JXNode.t(e.ownText())); 47 | } 48 | } 49 | } 50 | return res; 51 | } 52 | 53 | /** 54 | * 递归获取节点内全部的纯文本 55 | * 56 | * @param context 57 | * @return 58 | */ 59 | public List allText(Elements context) { 60 | List res = new LinkedList(); 61 | if (context != null && context.size() > 0) { 62 | for (Element e : context) { 63 | res.add(JXNode.t(e.text())); 64 | } 65 | } 66 | return res; 67 | } 68 | 69 | /** 70 | * 获取全部节点的内部的html 71 | * 72 | * @param context 73 | * @return 74 | */ 75 | public List html(Elements context) { 76 | List res = new LinkedList(); 77 | if (context != null && context.size() > 0) { 78 | for (Element e : context) { 79 | res.add(JXNode.t(e.html())); 80 | } 81 | } 82 | return res; 83 | } 84 | 85 | /** 86 | * 获取全部节点的 包含节点本身在内的全部html 87 | * 88 | * @param context 89 | * @return 90 | */ 91 | public List outerHtml(Elements context) { 92 | List res = new LinkedList(); 93 | if (context != null && context.size() > 0) { 94 | for (Element e : context) { 95 | res.add(JXNode.t(e.outerHtml())); 96 | } 97 | } 98 | return res; 99 | } 100 | 101 | /** 102 | * 获取全部节点 103 | * 104 | * @param context 105 | * @return 106 | */ 107 | public List node(Elements context) { 108 | return html(context); 109 | } 110 | 111 | /** 112 | * 抽取节点自有文本中全部数字 113 | * 114 | * @param context 115 | * @return 116 | */ 117 | public List num(Elements context) { 118 | List res = new LinkedList(); 119 | if (context != null) { 120 | Pattern pattern = Pattern.compile("\\d+"); 121 | for (Element e : context) { 122 | Matcher matcher = pattern.matcher(e.ownText()); 123 | if (matcher.find()) { 124 | res.add(JXNode.t(matcher.group())); 125 | } 126 | } 127 | } 128 | return res; 129 | } 130 | 131 | /** 132 | * ===================== 133 | * 下面是用于过滤器的函数 134 | */ 135 | 136 | /** 137 | * 获取元素自己的子文本 138 | * 139 | * @param e 140 | * @return 141 | */ 142 | public String text(Element e) { 143 | return e.ownText(); 144 | } 145 | 146 | /** 147 | * 获取元素下面的全部文本 148 | * 149 | * @param e 150 | * @return 151 | */ 152 | public String allText(Element e) { 153 | return e.text(); 154 | } 155 | 156 | /** 157 | * 判断一个元素是不是最后一个同名同胞中的 158 | * 159 | * @param e 160 | * @return 161 | */ 162 | public boolean last(Element e) { 163 | return CommonUtil.getElIndexInSameTags(e) == CommonUtil.sameTagElNums(e); 164 | } 165 | 166 | /** 167 | * 判断一个元素是不是同名同胞中的第一个 168 | * 169 | * @param e 170 | * @return 171 | */ 172 | public boolean first(Element e) { 173 | return CommonUtil.getElIndexInSameTags(e) == 1; 174 | } 175 | 176 | /** 177 | * 返回一个元素在同名兄弟节点中的位置 178 | * 179 | * @param e 180 | * @return 181 | */ 182 | public int position(Element e) { 183 | return CommonUtil.getElIndexInSameTags(e); 184 | } 185 | 186 | /** 187 | * 判断是否包含 188 | * 189 | * @param left 190 | * @param right 191 | * @return 192 | */ 193 | public boolean contains(String left, String right) { 194 | return left.contains(right); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/core/SingletonProducer.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.core; 2 | 3 | 4 | public class SingletonProducer { 5 | private static SingletonProducer producer = new SingletonProducer(); 6 | private AxisSelector axisSelector = new AxisSelector(); 7 | private Functions functions = new Functions(); 8 | 9 | private SingletonProducer() { 10 | } 11 | 12 | public static SingletonProducer getInstance() { 13 | return producer; 14 | } 15 | 16 | public AxisSelector getAxisSelector() { 17 | return axisSelector; 18 | } 19 | 20 | public Functions getFunctions() { 21 | return functions; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/core/XContext.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.core; 2 | 3 | import com.qy.reader.crawler.xpath.model.Node; 4 | 5 | import java.util.LinkedList; 6 | 7 | public class XContext { 8 | public LinkedList xpathTr; 9 | 10 | public XContext() { 11 | if (xpathTr == null) { 12 | xpathTr = new LinkedList(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/exception/NoSuchAxisException.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.exception; 2 | 3 | /** 4 | * 使用不存在的轴语法则抛出此异常 5 | */ 6 | public class NoSuchAxisException extends Exception { 7 | public NoSuchAxisException(String msg) { 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/exception/NoSuchFunctionException.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.exception; 2 | 3 | public class NoSuchFunctionException extends Exception { 4 | public NoSuchFunctionException(String msg) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/exception/XpathSyntaxErrorException.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.exception; 2 | 3 | public class XpathSyntaxErrorException extends Exception { 4 | public XpathSyntaxErrorException(String msg) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/model/JXDocument.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.model; 2 | 3 | import com.qy.reader.crawler.xpath.core.XpathEvaluator; 4 | import com.qy.reader.crawler.xpath.exception.NoSuchAxisException; 5 | import com.qy.reader.crawler.xpath.exception.NoSuchFunctionException; 6 | import com.qy.reader.crawler.xpath.exception.XpathSyntaxErrorException; 7 | 8 | import org.jsoup.Jsoup; 9 | import org.jsoup.nodes.Document; 10 | import org.jsoup.select.Elements; 11 | 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | public class JXDocument { 16 | private Elements elements; 17 | private XpathEvaluator xpathEva = new XpathEvaluator(); 18 | 19 | public JXDocument(Document doc) { 20 | elements = doc.children(); 21 | } 22 | 23 | public JXDocument(String html) { 24 | elements = Jsoup.parse(html).children(); 25 | } 26 | 27 | public JXDocument(Elements els) { 28 | elements = els; 29 | } 30 | 31 | public List sel(String xpath) throws XpathSyntaxErrorException { 32 | List res = new LinkedList(); 33 | try { 34 | List jns = xpathEva.xpathParser(xpath, elements); 35 | for (JXNode j : jns) { 36 | if (j.isText()) { 37 | res.add(j.getTextVal()); 38 | } else { 39 | res.add(j.getElement()); 40 | } 41 | } 42 | } catch (Exception e) { 43 | String msg = "please check the xpath syntax"; 44 | if (e instanceof NoSuchAxisException || e instanceof NoSuchFunctionException) { 45 | msg = e.getMessage(); 46 | } 47 | throw new XpathSyntaxErrorException(msg); 48 | } 49 | return res; 50 | } 51 | 52 | public List selN(String xpath) throws XpathSyntaxErrorException { 53 | try { 54 | return xpathEva.xpathParser(xpath, elements); 55 | } catch (Exception e) { 56 | String msg = "please check the xpath syntax"; 57 | if (e instanceof NoSuchAxisException || e instanceof NoSuchFunctionException) { 58 | msg = e.getMessage(); 59 | } 60 | throw new XpathSyntaxErrorException(msg); 61 | } 62 | } 63 | 64 | public Object selOne(String xpath) throws XpathSyntaxErrorException { 65 | JXNode jxNode = selNOne(xpath); 66 | if (jxNode != null) { 67 | if (jxNode.isText()) { 68 | return jxNode.getTextVal(); 69 | } else { 70 | return jxNode.getElement(); 71 | } 72 | } 73 | return null; 74 | } 75 | 76 | public JXNode selNOne(String xpath) throws XpathSyntaxErrorException { 77 | List jxNodeList = selN(xpath); 78 | if (jxNodeList != null && jxNodeList.size() > 0) { 79 | return jxNodeList.get(0); 80 | } 81 | return null; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/model/JXNode.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.model; 2 | 3 | import com.qy.reader.crawler.xpath.exception.XpathSyntaxErrorException; 4 | 5 | import org.jsoup.nodes.Element; 6 | import org.jsoup.select.Elements; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * XPath提取后的 12 | */ 13 | public class JXNode { 14 | private Element element; 15 | private boolean isText; 16 | private String textVal; 17 | 18 | public static JXNode e(Element element) { 19 | JXNode n = new JXNode(); 20 | n.setElement(element).setText(false); 21 | return n; 22 | } 23 | 24 | public static JXNode t(String txt) { 25 | JXNode n = new JXNode(); 26 | n.setTextVal(txt).setText(true); 27 | return n; 28 | } 29 | 30 | public Element getElement() { 31 | return element; 32 | } 33 | 34 | public JXNode setElement(Element element) { 35 | this.element = element; 36 | return this; 37 | } 38 | 39 | public boolean isText() { 40 | return isText; 41 | } 42 | 43 | public JXNode setText(boolean text) { 44 | isText = text; 45 | return this; 46 | } 47 | 48 | public String getTextVal() { 49 | return textVal; 50 | } 51 | 52 | public JXNode setTextVal(String textVal) { 53 | this.textVal = textVal; 54 | return this; 55 | } 56 | 57 | public List sel(String xpath) throws XpathSyntaxErrorException { 58 | if (element == null) { 59 | return null; 60 | } 61 | JXDocument doc = new JXDocument(new Elements(element)); 62 | return doc.selN(xpath); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | if (isText) { 68 | return textVal; 69 | } else { 70 | return element.toString(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/model/Node.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.model; 2 | 3 | import com.qy.reader.crawler.xpath.util.ScopeEm; 4 | 5 | /** 6 | * xpath语法链的一个基本节点 7 | */ 8 | public class Node { 9 | private ScopeEm scopeEm; 10 | private String axis; 11 | private String tagName; 12 | private Predicate predicate; 13 | 14 | public ScopeEm getScopeEm() { 15 | return scopeEm; 16 | } 17 | 18 | public void setScopeEm(ScopeEm scopeEm) { 19 | this.scopeEm = scopeEm; 20 | } 21 | 22 | public String getAxis() { 23 | return axis; 24 | } 25 | 26 | public void setAxis(String axis) { 27 | this.axis = axis; 28 | } 29 | 30 | public String getTagName() { 31 | return tagName; 32 | } 33 | 34 | public void setTagName(String tagName) { 35 | this.tagName = tagName; 36 | } 37 | 38 | public Predicate getPredicate() { 39 | return predicate; 40 | } 41 | 42 | public void setPredicate(Predicate predicate) { 43 | this.predicate = predicate; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/model/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.model; 2 | 3 | 4 | import com.qy.reader.crawler.xpath.util.OpEm; 5 | 6 | /** 7 | * xpath语法节点的谓语部分,即要满足的限定条件 8 | */ 9 | public class Predicate { 10 | 11 | private OpEm opEm; 12 | private String left; 13 | private String right; 14 | private String value; 15 | 16 | public OpEm getOpEm() { 17 | return opEm; 18 | } 19 | 20 | public void setOpEm(OpEm opEm) { 21 | this.opEm = opEm; 22 | } 23 | 24 | public String getLeft() { 25 | return left; 26 | } 27 | 28 | public void setLeft(String left) { 29 | this.left = left; 30 | } 31 | 32 | public String getRight() { 33 | return right; 34 | } 35 | 36 | public void setRight(String right) { 37 | this.right = right; 38 | } 39 | 40 | public String getValue() { 41 | return value; 42 | } 43 | 44 | public void setValue(String value) { 45 | this.value = value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/model/XpathResult.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.model; 2 | 3 | public class XpathResult { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/util/CommonUtil.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.util; 2 | 3 | import org.jsoup.nodes.Element; 4 | import org.jsoup.select.Elements; 5 | 6 | public class CommonUtil { 7 | public static String getJMethodNameFromStr(String str) { 8 | if (str.contains("-")) { 9 | String[] pies = str.split("-"); 10 | StringBuilder sb = new StringBuilder(pies[0]); 11 | for (int i = 1; i < pies.length; i++) { 12 | sb.append(pies[i].substring(0, 1).toUpperCase()).append(pies[i].substring(1)); 13 | } 14 | return sb.toString(); 15 | } 16 | return str; 17 | } 18 | 19 | /** 20 | * 获取同名元素在同胞中的index 21 | * 22 | * @param e 23 | * @return 24 | */ 25 | public static int getElIndexInSameTags(Element e) { 26 | Elements chs = e.parent().children(); 27 | int index = 1; 28 | for (int i = 0; i < chs.size(); i++) { 29 | Element cur = chs.get(i); 30 | if (e.tagName().equals(cur.tagName())) { 31 | if (e.equals(cur)) { 32 | break; 33 | } else { 34 | index += 1; 35 | } 36 | } 37 | } 38 | return index; 39 | } 40 | 41 | /** 42 | * 获取同胞中同名元素的数量 43 | * 44 | * @param e 45 | * @return 46 | */ 47 | public static int sameTagElNums(Element e) { 48 | Elements els = e.parent().getElementsByTag(e.tagName()); 49 | return els.size(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/util/EmMap.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class EmMap { 9 | private static EmMap ourInstance = new EmMap(); 10 | public Map scopeEmMap = new HashMap(); 11 | public Map opEmMap = new HashMap(); 12 | public Set commOpChar = new HashSet(); 13 | 14 | private EmMap() { 15 | scopeEmMap.put("/", ScopeEm.INCHILREN); 16 | scopeEmMap.put("//", ScopeEm.RECURSIVE); 17 | scopeEmMap.put("./", ScopeEm.CUR); 18 | scopeEmMap.put(".//", ScopeEm.CURREC); 19 | 20 | opEmMap.put("+", OpEm.PLUS); 21 | opEmMap.put("-", OpEm.MINUS); 22 | opEmMap.put("=", OpEm.EQ); 23 | opEmMap.put("!=", OpEm.NE); 24 | opEmMap.put(">", OpEm.GT); 25 | opEmMap.put("<", OpEm.LT); 26 | opEmMap.put(">=", OpEm.GE); 27 | opEmMap.put("<=", OpEm.LE); 28 | opEmMap.put("^=", OpEm.STARTWITH); 29 | opEmMap.put("$=", OpEm.ENDWITH); 30 | opEmMap.put("*=", OpEm.CONTAIN); 31 | opEmMap.put("~=", OpEm.REGEX); 32 | opEmMap.put("!~", OpEm.NOTMATCH); 33 | 34 | commOpChar.add('+'); 35 | commOpChar.add('-'); 36 | commOpChar.add('='); 37 | commOpChar.add('*'); 38 | commOpChar.add('^'); 39 | commOpChar.add('$'); 40 | commOpChar.add('~'); 41 | commOpChar.add('>'); 42 | commOpChar.add('<'); 43 | commOpChar.add('!'); 44 | } 45 | 46 | public static EmMap getInstance() { 47 | return ourInstance; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/util/OpEm.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * 操作符 7 | */ 8 | public enum OpEm { 9 | PLUS("+") { 10 | @Override 11 | public Object execute(String left, String right) { 12 | int li = 0; 13 | if (StringUtils.isNotBlank(left)) { 14 | li = Integer.parseInt(left); 15 | } 16 | int ri = 0; 17 | if (StringUtils.isNotBlank(right)) { 18 | ri = Integer.parseInt(right); 19 | } 20 | return li + ri; 21 | } 22 | }, 23 | MINUS("-") { 24 | @Override 25 | public Object execute(String left, String right) { 26 | int li = 0; 27 | if (StringUtils.isNotBlank(left)) { 28 | li = Integer.parseInt(left); 29 | } 30 | int ri = 0; 31 | if (StringUtils.isNotBlank(right)) { 32 | ri = Integer.parseInt(right); 33 | } 34 | return li - ri; 35 | } 36 | }, 37 | EQ("=") { 38 | @Override 39 | public Object execute(String left, String right) { 40 | return left.equals(right); 41 | } 42 | }, 43 | NE("!=") { 44 | @Override 45 | public Object execute(String left, String right) { 46 | return !left.equals(right); 47 | } 48 | }, 49 | GT(">") { 50 | @Override 51 | public Object execute(String left, String right) { 52 | int li = 0; 53 | if (StringUtils.isNotBlank(left)) { 54 | li = Integer.parseInt(left); 55 | } 56 | int ri = 0; 57 | if (StringUtils.isNotBlank(right)) { 58 | ri = Integer.parseInt(right); 59 | } 60 | return li > ri; 61 | } 62 | }, 63 | LT("<") { 64 | @Override 65 | public Object execute(String left, String right) { 66 | int li = 0; 67 | if (StringUtils.isNotBlank(left)) { 68 | li = Integer.parseInt(left); 69 | } 70 | int ri = 0; 71 | if (StringUtils.isNotBlank(right)) { 72 | ri = Integer.parseInt(right); 73 | } 74 | return li < ri; 75 | } 76 | }, 77 | GE(">=") { 78 | @Override 79 | public Object execute(String left, String right) { 80 | int li = 0; 81 | if (StringUtils.isNotBlank(left)) { 82 | li = Integer.parseInt(left); 83 | } 84 | int ri = 0; 85 | if (StringUtils.isNotBlank(right)) { 86 | ri = Integer.parseInt(right); 87 | } 88 | return li >= ri; 89 | } 90 | }, 91 | LE("<=") { 92 | @Override 93 | public Object execute(String left, String right) { 94 | int li = 0; 95 | if (StringUtils.isNotBlank(left)) { 96 | li = Integer.parseInt(left); 97 | } 98 | int ri = 0; 99 | if (StringUtils.isNotBlank(right)) { 100 | ri = Integer.parseInt(right); 101 | } 102 | return li <= ri; 103 | } 104 | }, 105 | STARTWITH("^=") { 106 | @Override 107 | public Object execute(String left, String right) { 108 | return left.startsWith(right); 109 | } 110 | }, 111 | ENDWITH("$=") { 112 | @Override 113 | public Object execute(String left, String right) { 114 | return left.endsWith(right); 115 | } 116 | }, 117 | CONTAIN("*=") { 118 | @Override 119 | public Object execute(String left, String right) { 120 | return left.contains(right); 121 | } 122 | }, 123 | REGEX("~=") { 124 | @Override 125 | public Object execute(String left, String right) { 126 | return left.matches(right); 127 | } 128 | }, 129 | NOTMATCH("!~") { 130 | @Override 131 | public Object execute(String left, String right) { 132 | return !left.matches(right); 133 | } 134 | }; 135 | private String val; 136 | 137 | private OpEm(String type) { 138 | this.val = type; 139 | } 140 | 141 | public String val() { 142 | return this.val; 143 | } 144 | 145 | public Object execute(String left, String right) { 146 | return null; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /crawler/src/main/java/com/qy/reader/crawler/xpath/util/ScopeEm.java: -------------------------------------------------------------------------------- 1 | package com.qy.reader.crawler.xpath.util; 2 | 3 | /** 4 | * 筛选作用域 5 | */ 6 | public enum ScopeEm { 7 | INCHILREN("/"), //默认只在子代中筛选,有轴时由轴定义筛选域 8 | RECURSIVE("//"), //向下递归查找 9 | CUR("./"), //当前节点下 10 | CURREC(".//"); //当前节点向下递归 11 | private String val; 12 | 13 | private ScopeEm(String type) { 14 | this.val = type; 15 | } 16 | 17 | public String val() { 18 | return this.val; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crawler/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | crawler 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | org.gradle.daemon=true 20 | org.gradle.parallel=true 21 | org.gradle.configureondemand=true 22 | 23 | android.enableAapt2=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 07 14:02:01 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-4.4-20171031235950+0000-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /screenshot/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/screenshot/detail.png -------------------------------------------------------------------------------- /screenshot/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/screenshot/search.png -------------------------------------------------------------------------------- /screenshot/search_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/screenshot/search_result.png -------------------------------------------------------------------------------- /screenshot/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smuyyh/CrawlerForReader/fc52a470291fef3b0d15c4554b2d6845e8bfa713/screenshot/source.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':common', ':crawler' 2 | --------------------------------------------------------------------------------