Преглед на файлове

1,基本完成数据存取设计

YIN-PC\yin преди 7 години
родител
ревизия
527b21fa56
променени са 79 файла, в които са добавени 4168 реда и са изтрити 0 реда
  1. 19 0
      ReadBook.iml
  2. 1 0
      app/.gitignore
  3. 44 0
      app/CMakeLists.txt
  4. 186 0
      app/app.iml
  5. 90 0
      app/build.gradle
  6. 21 0
      app/proguard-rules.pro
  7. 27 0
      app/src/androidTest/java/top/yinxueqin/readbook/ExampleInstrumentedTest.java
  8. 25 0
      app/src/main/AndroidManifest.xml
  9. 11 0
      app/src/main/cpp/native-lib.cpp
  10. 63 0
      app/src/main/java/top/yinxueqin/readbook/AppExecutors.java
  11. 37 0
      app/src/main/java/top/yinxueqin/readbook/BasicApp.java
  12. 202 0
      app/src/main/java/top/yinxueqin/readbook/DataRepository.java
  13. 189 0
      app/src/main/java/top/yinxueqin/readbook/bean/BookInfo.java
  14. 77 0
      app/src/main/java/top/yinxueqin/readbook/bean/ChapterInfo.java
  15. 40 0
      app/src/main/java/top/yinxueqin/readbook/bean/SearchResult.java
  16. 106 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/DBDatabase.java
  17. 49 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/DataGenerator.java
  18. 24 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/converter/DateConverter.java
  19. 42 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/DBBookInfoDao.java
  20. 37 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/DBChapterInfoDao.java
  21. 31 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/ReadSettingConfigDao.java
  22. 37 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/SearchHistoryDao.java
  23. 30 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/SettingConfigDao.java
  24. 208 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/DBBookInfoEntity.java
  25. 116 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/DBChapterInfoEntity.java
  26. 205 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/ReadSettingConfigEntity.java
  27. 60 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/SearchHistoryEntity.java
  28. 94 0
      app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/SettingConfigEntity.java
  29. 194 0
      app/src/main/java/top/yinxueqin/readbook/data/local/file/DSDiskStorage.java
  30. 117 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/GetBookInfo.java
  31. 12 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/GetSearchInfo.java
  32. 12 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/GetUiBookSearchResult.java
  33. 192 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Biquge.java
  34. 209 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/BookNext9.java
  35. 52 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/BookWeb.java
  36. 40 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Dingdian.java
  37. 37 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Ixscc.java
  38. 37 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Shuku56.java
  39. 37 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Xiaoxiaoshuoshuo.java
  40. 38 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/book/Yetianzi.java
  41. 11 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/search/Baidu.java
  42. 12 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/search/Google.java
  43. 22 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/search/SearchWeb.java
  44. 198 0
      app/src/main/java/top/yinxueqin/readbook/data/remote/search/Shenma.java
  45. 42 0
      app/src/main/java/top/yinxueqin/readbook/model/DBBookInfo.java
  46. 25 0
      app/src/main/java/top/yinxueqin/readbook/model/DBChapterInfo.java
  47. 42 0
      app/src/main/java/top/yinxueqin/readbook/model/ReadSettingConfig.java
  48. 16 0
      app/src/main/java/top/yinxueqin/readbook/model/SearchHistory.java
  49. 17 0
      app/src/main/java/top/yinxueqin/readbook/model/SettingConfig.java
  50. 31 0
      app/src/main/java/top/yinxueqin/readbook/ui/MainActivity.java
  51. 41 0
      app/src/main/java/top/yinxueqin/readbook/ui/activity/BaseActivity.java
  52. 37 0
      app/src/main/java/top/yinxueqin/readbook/utils/Constants.java
  53. 42 0
      app/src/main/java/top/yinxueqin/readbook/utils/ErrorCodeConstants.java
  54. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  55. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  56. 19 0
      app/src/main/res/layout/activity_main.xml
  57. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  58. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  59. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  60. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  61. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  62. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  63. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  64. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  65. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  66. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  67. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  68. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  69. 6 0
      app/src/main/res/values/colors.xml
  70. 3 0
      app/src/main/res/values/strings.xml
  71. 11 0
      app/src/main/res/values/styles.xml
  72. 20 0
      app/src/test/java/top/yinxueqin/readbook/ExampleUnitTest.java
  73. 27 0
      build.gradle
  74. 16 0
      gradle.properties
  75. BIN
      gradle/wrapper/gradle-wrapper.jar
  76. 6 0
      gradle/wrapper/gradle-wrapper.properties
  77. 160 0
      gradlew
  78. 90 0
      gradlew.bat
  79. 12 0
      local.properties

+ 19 - 0
ReadBook.iml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.id="ReadBook" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="java-gradle" name="Java-Gradle">
+      <configuration>
+        <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
+        <option name="BUILDABLE" value="false" />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 44 - 0
app/CMakeLists.txt

@@ -0,0 +1,44 @@
+# For more information about using CMake with Android Studio, read the
+# documentation: https://d.android.com/studio/projects/add-native-code.html
+
+# Sets the minimum version of CMake required to build the native library.
+
+cmake_minimum_required(VERSION 3.4.1)
+
+# Creates and names a library, sets it as either STATIC
+# or SHARED, and provides the relative paths to its source code.
+# You can define multiple libraries, and CMake builds them for you.
+# Gradle automatically packages shared libraries with your APK.
+
+add_library( # Sets the name of the library.
+             native-lib
+
+             # Sets the library as a shared library.
+             SHARED
+
+             # Provides a relative path to your source file(s).
+             src/main/cpp/native-lib.cpp )
+
+# Searches for a specified prebuilt library and stores the path as a
+# variable. Because CMake includes system libraries in the search path by
+# default, you only need to specify the name of the public NDK library
+# you want to add. CMake verifies that the library exists before
+# completing its build.
+
+find_library( # Sets the name of the path variable.
+              log-lib
+
+              # Specifies the name of the NDK library that
+              # you want CMake to locate.
+              log )
+
+# Specifies libraries CMake should link to your target library. You
+# can link multiple libraries, such as libraries you define in this
+# build script, prebuilt third-party libraries, or system libraries.
+
+target_link_libraries( # Specifies the target library.
+                       native-lib
+
+                       # Links the target library to the log library
+                       # included in the NDK.
+                       ${log-lib} )

+ 186 - 0
app/app.iml

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="android-gradle" name="Android-Gradle">
+      <configuration>
+        <option name="GRADLE_PROJECT_PATH" value=":app" />
+      </configuration>
+    </facet>
+    <facet type="native-android-gradle" name="Native-Android-Gradle">
+      <configuration>
+        <option name="SELECTED_BUILD_VARIANT" value="debug" />
+      </configuration>
+    </facet>
+    <facet type="android" name="Android">
+      <configuration>
+        <option name="SELECTED_BUILD_VARIANT" value="debug" />
+        <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
+        <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
+        <afterSyncTasks>
+          <task>generateDebugSources</task>
+        </afterSyncTasks>
+        <option name="ALLOW_USER_CONFIGURATION" value="false" />
+        <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
+        <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
+        <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
+        <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
+    <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/cpp" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/test/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/jni" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes-jar" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/cmake" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/data-binding" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/data-binding-info" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
+      <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
+      <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
+    </content>
+    <orderEntry type="jdk" jdkName="Android API 26 Platform" jdkType="Android SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-solver:1.0.2@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.google.guava:guava:23.5-android@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.paging:common:1.0.0-alpha5@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.objenesis:objenesis:2.5@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.mockito:mockito-core:2.7.6@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.codehaus.mojo:animal-sniffer-annotations:1.14@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: com.squareup:javawriter:2.1.1@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: android.arch.persistence.room:migration:1.0.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-media-compat-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-annotations:26.1.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-v4-26.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains:annotations:13.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:recyclerview-v7-26.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:rules-1.0.1" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.google.j2objc:j2objc-annotations:1.1@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.persistence:db-framework-1.0.0" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-1.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.databinding:library-3.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-core-ui-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.persistence:db-1.0.0" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.persistence.room:runtime-1.0.0" level="project" />
+    <orderEntry type="library" name="Gradle: org.checkerframework:checker-qual:2.0.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.databinding:adapters-3.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-core-utils-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.databinding:baseLibrary:3.1.0@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.1.3@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.persistence.room:common:1.0.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.paging:runtime-1.0.0-alpha5" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:common-java8:1.1.0@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: net.sf.kxml:kxml2:2.3.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:runner-1.0.1" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.yanzhenjie.alertdialog:alertdialog-1.0.1" level="project" />
+    <orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:1.3.9@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-fragment-26.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: android.arch.persistence.room:testing-1.0.0" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support:support-compat-26.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:extensions-1.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-core-3.0.1" level="project" />
+    <orderEntry type="library" name="Gradle: com.google.errorprone:error_prone_annotations:2.0.18@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-1.0.2" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: junit:junit:4.12@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.jakewharton:disklrucache:2.0.2@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-core:1.3@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-idling-resource-3.0.1" level="project" />
+    <orderEntry type="library" name="Gradle: com.google.code.gson:gson:2.8.2@jar" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-integration:1.3@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-library:1.3@jar" level="project" />
+    <orderEntry type="library" name="Gradle: com.yanzhenjie:permission-1.1.2" level="project" />
+    <orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.jsoup:jsoup:1.11.2@jar" level="project" />
+    <orderEntry type="library" scope="TEST" name="Gradle: android.arch.core:core-testing-1.1.0" level="project" />
+  </component>
+</module>

+ 90 - 0
app/build.gradle

@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 26
+    buildToolsVersion "26.0.3"
+    defaultConfig {
+        applicationId "top.yinxueqin.readbook"
+        minSdkVersion 21
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        externalNativeBuild {
+            cmake {
+                cppFlags ""
+            }
+        }
+        ndk {
+            abiFilters "armeabi", "x86"
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+
+    dataBinding {
+        enabled = true
+    }
+    productFlavors {
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    compileOptions {
+        targetCompatibility 1.8
+        sourceCompatibility 1.8
+    }
+
+    configurations.all {
+        resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'com.android.support:appcompat-v7:26.1.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.1'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+    compile 'org.jsoup:jsoup:1.11.2'
+    compile 'com.google.code.gson:gson:2.8.2'
+    compile 'com.google.guava:guava:23.5-android'
+    compile 'com.yanzhenjie:permission:1.1.2'
+
+    // ViewModel and LiveData
+    implementation "android.arch.lifecycle:extensions:1.1.0"
+    // alternatively, just ViewModel
+    implementation "android.arch.lifecycle:viewmodel:1.1.0"
+    // alternatively, just LiveData
+    implementation "android.arch.lifecycle:livedata:1.1.0"
+
+    // Java8 support for Lifecycles
+    implementation "android.arch.lifecycle:common-java8:1.1.0"
+
+    // Room (use 1.1.0-alpha1 for latest alpha)
+    implementation "android.arch.persistence.room:runtime:1.0.0"
+    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
+
+    // Paging
+    implementation "android.arch.paging:runtime:1.0.0-alpha5"
+
+    // Test helpers for LiveData
+    testImplementation "android.arch.core:core-testing:1.1.0"
+
+    // Test helpers for Room
+    testImplementation "android.arch.persistence.room:testing:1.0.0"
+
+    compile 'com.jakewharton:disklrucache:2.0.2'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 27 - 0
app/src/androidTest/java/top/yinxueqin/readbook/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package top.yinxueqin.readbook;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.SearchResult;
+import top.yinxueqin.readbook.data.remote.search.Shenma;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() throws IOException {
+        Shenma shenma = new Shenma();
+        SearchResult SearchResult = shenma.searchResult("飞剑问道");
+        Log.i("booklink", SearchResult.toString());
+    }
+}

+ 25 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="top.yinxueqin.readbook">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name=".ui.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".ui.activity.BaseActivity" />
+    </application>
+
+</manifest>

+ 11 - 0
app/src/main/cpp/native-lib.cpp

@@ -0,0 +1,11 @@
+#include <jni.h>
+#include <string>
+
+extern "C"
+JNIEXPORT jstring JNICALL
+Java_top_yinxueqin_readbook_ui_MainActivity_stringFromJNI(
+        JNIEnv *env,
+        jobject /* this */) {
+    std::string hello = "Hello from C++";
+    return env->NewStringUTF(hello.c_str());
+}

+ 63 - 0
app/src/main/java/top/yinxueqin/readbook/AppExecutors.java

@@ -0,0 +1,63 @@
+package top.yinxueqin.readbook;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class AppExecutors {
+
+    private final Executor mDbIO;
+
+    private final Executor mDiskIO;
+
+    private final Executor mNetworkIO;
+
+    private final Executor mMainThread;
+
+    private AppExecutors(Executor dbIO, Executor diskIO, Executor networkIO, Executor mainThread) {
+        this.mDbIO = dbIO;
+        this.mDiskIO = diskIO;
+        this.mNetworkIO = networkIO;
+        this.mMainThread = mainThread;
+    }
+
+    public AppExecutors() {
+        this(Executors.newSingleThreadExecutor(), Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3),
+                new MainThreadExecutor());
+    }
+
+    public Executor dbIO() {
+        return mDbIO;
+    }
+
+    public Executor diskIO() {
+        return mDiskIO;
+    }
+
+    public Executor networkIO() {
+        return mNetworkIO;
+    }
+
+    public Executor mainThread() {
+        return mMainThread;
+    }
+
+    private static class MainThreadExecutor implements Executor {
+        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        public void execute(@NonNull Runnable command) {
+            mainThreadHandler.post(command);
+        }
+    }
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/BasicApp.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook;
+
+import android.app.Application;
+
+import top.yinxueqin.readbook.data.local.db.DBDatabase;
+import top.yinxueqin.readbook.data.local.file.DSDiskStorage;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class BasicApp extends Application {
+
+    private AppExecutors mAppExecutors;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mAppExecutors = new AppExecutors();
+    }
+
+    public DBDatabase getDBDatabase() {
+        return DBDatabase.getInstance(this, mAppExecutors);
+    }
+
+    public DSDiskStorage getDSDiskStorage() {
+        return DSDiskStorage.getInstance(this, mAppExecutors);
+    }
+
+    public DataRepository getRepository() {
+        return DataRepository.getInstance(getDBDatabase(), getDSDiskStorage());
+    }
+}

+ 202 - 0
app/src/main/java/top/yinxueqin/readbook/DataRepository.java

@@ -0,0 +1,202 @@
+package top.yinxueqin.readbook;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MediatorLiveData;
+
+import java.util.List;
+
+import top.yinxueqin.readbook.data.local.db.DBDatabase;
+import top.yinxueqin.readbook.data.local.db.entity.DBBookInfoEntity;
+import top.yinxueqin.readbook.data.local.db.entity.DBChapterInfoEntity;
+import top.yinxueqin.readbook.data.local.db.entity.ReadSettingConfigEntity;
+import top.yinxueqin.readbook.data.local.db.entity.SearchHistoryEntity;
+import top.yinxueqin.readbook.data.local.db.entity.SettingConfigEntity;
+import top.yinxueqin.readbook.data.local.file.DSDiskStorage;
+
+/**
+ * Author: yin
+ * Time: 2018/3/8.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class DataRepository {
+
+    private static DataRepository sInstance;
+
+    private final DBDatabase mDBDatabase;
+    private final DSDiskStorage mDSDisStorage;
+
+
+
+    private MediatorLiveData<List<DBBookInfoEntity>> mObservableBookInfo;
+    private MediatorLiveData<ReadSettingConfigEntity> mObservableReadSettingConfig;
+    private MediatorLiveData<SettingConfigEntity> mObservableSettingConfig;
+    private MediatorLiveData<SearchHistoryEntity> mObservableSearchHistory;
+
+    private DataRepository(final DBDatabase database, final DSDiskStorage dsDiskStorage) {
+        mDBDatabase = database;
+        mDSDisStorage = dsDiskStorage;
+        mObservableBookInfo = new MediatorLiveData<>();
+        mObservableReadSettingConfig = new MediatorLiveData<>();
+        mObservableSettingConfig = new MediatorLiveData<>();
+        mObservableSearchHistory = new MediatorLiveData<>();
+
+        mObservableBookInfo.addSource(mDBDatabase.dbBookInfoDao().loadAllBookInfo(),
+                dbBookInfoEntities -> {
+                    if (mDBDatabase.getDatabaseCreated().getValue() != null) {
+                        mObservableBookInfo.postValue(dbBookInfoEntities);
+                    }
+                });
+
+        mObservableReadSettingConfig.addSource(mDBDatabase.readSettingConfigDao().loadReadSettingConfig(),
+                readSettingConfigEntity -> {
+                    if (mDBDatabase.getDatabaseCreated().getValue() != null) {
+                        mObservableReadSettingConfig.postValue(readSettingConfigEntity);
+                    }
+                });
+
+
+        mObservableSettingConfig.addSource(mDBDatabase.settingConfigDao().loadSettingConfig(),
+                settingConfigEntity -> {
+                    if (mDBDatabase.getDatabaseCreated().getValue() != null) {
+                        mObservableSettingConfig.postValue(settingConfigEntity);
+                    }
+                });
+
+        mObservableSearchHistory.addSource(mDBDatabase.searchHistoryDao().loadSearchHistory(),
+                searchHistoryEntity -> {
+                    if (mDBDatabase.getDatabaseCreated().getValue() != null) {
+                        mObservableSearchHistory.postValue(searchHistoryEntity);
+                    }
+                });
+    }
+
+    public static DataRepository getInstance(final DBDatabase database, final DSDiskStorage dsDiskStorage) {
+        if (sInstance == null) {
+            synchronized (DataRepository.class) {
+                if (sInstance == null) {
+                    sInstance = new DataRepository(database, dsDiskStorage);
+                }
+            }
+        }
+
+        return sInstance;
+    }
+
+    /**
+     *
+     * 书籍信息操作
+     *
+     */
+
+    public LiveData<List<DBBookInfoEntity>> getBookInfos() {
+        return mObservableBookInfo;
+    }
+
+    public LiveData<DBBookInfoEntity> getBookInfo(final String bookMD5) {
+        return mDBDatabase.dbBookInfoDao().loadDBBookInfo(bookMD5);
+    }
+
+    public DBBookInfoEntity getBookInfoSync(final String bookMD5) {
+        return mDBDatabase.dbBookInfoDao().loadDBBookInfoSync(bookMD5);
+    }
+
+    public void updateBookInfo(final DBBookInfoEntity bookInfo) {
+        mDBDatabase.dbBookInfoDao().updateBookInfo(bookInfo);
+    }
+
+    public void insertBookInfo(final List<DBBookInfoEntity> bookInfos) {
+        mDBDatabase.dbBookInfoDao().insertAll(bookInfos);
+    }
+
+    public void deleteBookInfo(final List<DBBookInfoEntity> bookInfos) {
+        mDBDatabase.dbBookInfoDao().delete(bookInfos);
+    }
+
+    /**
+     *
+     * 章节信息操作
+     *
+     */
+
+    public LiveData<List<DBChapterInfoEntity>> getBookChapterInfo(int bookId) {
+        return mDBDatabase.dbChapterInfoDao().loadChapterInfos(bookId);
+    }
+
+    public void insertChapterInfo(final DBChapterInfoEntity chapterInfo) {
+        mDBDatabase.dbChapterInfoDao().insertChapterInfo(chapterInfo);
+    }
+
+    public void updataChapterInfo(final DBChapterInfoEntity chapterInfo) {
+        mDBDatabase.dbChapterInfoDao().updateChapterInfo(chapterInfo);
+    }
+
+    public void deleteBookChapterAll(final List<DBChapterInfoEntity> chapterInfos) {
+        mDBDatabase.dbChapterInfoDao().deleteAll(chapterInfos);
+    }
+
+
+
+    /**
+     *
+     * 阅读设置操作
+     *
+     */
+
+    public LiveData<ReadSettingConfigEntity> getReadSettingConfig() {
+        return mObservableReadSettingConfig;
+    }
+
+    public void updateReadSettingConfig(final ReadSettingConfigEntity readSettingConfig) {
+        mDBDatabase.readSettingConfigDao().updateReadSettingConfig(readSettingConfig);
+    }
+
+    public void insertReadSettingConfig(final ReadSettingConfigEntity readSettingConfig) {
+        mDBDatabase.readSettingConfigDao().insertAll(readSettingConfig);
+    }
+
+    /**
+     *
+     * 设置操作
+     *
+     */
+
+    public LiveData<SettingConfigEntity> getSettingConfig() {
+        return mObservableSettingConfig;
+    }
+
+    public void updateSettingConfig(final SettingConfigEntity settingConfig) {
+        mDBDatabase.settingConfigDao().updateSettingConfig(settingConfig);
+    }
+
+    public void insertSettingConfig(final SettingConfigEntity settingConfig) {
+        mDBDatabase.settingConfigDao().insertAll(settingConfig);
+    }
+
+    /**
+     *
+     * 搜索历史记录操作
+     *
+     */
+
+    public LiveData<SearchHistoryEntity> getSearchHistory() {
+        return mObservableSearchHistory;
+    }
+
+    public void updateSearchHistory(final SearchHistoryEntity searchHistory) {
+        mDBDatabase.searchHistoryDao().update(searchHistory);
+    }
+
+    public void insertSearchHistory(final List<SearchHistoryEntity> searchHistores) {
+        mDBDatabase.searchHistoryDao().insertAll(searchHistores);
+    }
+
+    public void deleteSearchHistory(final List<SearchHistoryEntity> searchHistores) {
+        mDBDatabase.searchHistoryDao().deleteAll(searchHistores);
+    }
+
+
+
+
+}

+ 189 - 0
app/src/main/java/top/yinxueqin/readbook/bean/BookInfo.java

@@ -0,0 +1,189 @@
+package top.yinxueqin.readbook.bean;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Author: yin
+ * Time: 2017/11/23.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class BookInfo implements Serializable{
+
+    private static final long serialVersionUID = 5676447764552202880L;
+
+    //书名称
+    private String bookName;
+
+    private String bookAuthor;
+    //书地址
+    private String bookLink;
+    //书最新章节名称
+    private String bookNewestChapterName;
+    //书的封面
+    private String bookImage;
+    //最新更新时间
+    private String bookUpdateNewestChapterTime;
+    //最后阅读章节
+    private String bookReadChapter;
+    //阅读章节目录位置
+    private int bookCatalogPosition;
+    //最后阅读章节位置(当前章节的第几页或者百分比)
+    private int bookReadPosition;
+    //最后阅读本书的时间
+    private String bookReadbookTime;
+    //有无更新
+    private boolean hasNewestChapter;
+    //目录信息
+    private List<ChapterInfo> chapterInfos;
+    //显示在追书区,养肥区,完本保留区(0:追书区,1:养肥区,2:完本保留区)
+    private int bookshowRegional;
+    //错误代码
+    private int errorCode = 1000;
+    //错误原因
+    private String errorMessage = "正常";
+
+
+    public String getBookName() {
+        return bookName;
+    }
+
+    public void setBookName(String bookName) {
+        this.bookName = bookName;
+    }
+
+    public String getBookAuthor() {
+        return bookAuthor;
+    }
+
+    public void setBookAuthor(String bookAuthor) {
+        this.bookAuthor = bookAuthor;
+    }
+
+    public String getBookLink() {
+        return bookLink;
+    }
+
+    public void setBookLink(String bookLink) {
+        this.bookLink = bookLink;
+    }
+
+    public String getBookNewestChapterName() {
+        return bookNewestChapterName;
+    }
+
+    public void setBookNewestChapterName(String bookNewestChapterName) {
+        this.bookNewestChapterName = bookNewestChapterName;
+    }
+
+    public String getBookImage() {
+        return bookImage;
+    }
+
+    public void setBookImage(String bookImage) {
+        this.bookImage = bookImage;
+    }
+
+    public String getBookUpdateNewestChapterTime() {
+        return bookUpdateNewestChapterTime;
+    }
+
+    public void setBookUpdateNewestChapterTime(String bookUpdateNewestChapterTime) {
+        this.bookUpdateNewestChapterTime = bookUpdateNewestChapterTime;
+    }
+
+    public String getBookReadChapter() {
+        return bookReadChapter;
+    }
+
+    public void setBookReadChapter(String bookReadChapter) {
+        this.bookReadChapter = bookReadChapter;
+    }
+
+    public int getBookCatalogPosition() {
+        return bookCatalogPosition;
+    }
+
+    public void setBookCatalogPosition(int bookCatalogPosition) {
+        this.bookCatalogPosition = bookCatalogPosition;
+    }
+
+    public int getBookReadPosition() {
+        return bookReadPosition;
+    }
+
+    public void setBookReadPosition(int bookReadPosition) {
+        this.bookReadPosition = bookReadPosition;
+    }
+
+    public String getBookReadbookTime() {
+        return bookReadbookTime;
+    }
+
+    public void setBookReadbookTime(String bookReadbookTime) {
+        this.bookReadbookTime = bookReadbookTime;
+    }
+
+    public boolean isHasNewestChapter() {
+        return hasNewestChapter;
+    }
+
+    public void setHasNewestChapter(boolean hasNewestChapter) {
+        this.hasNewestChapter = hasNewestChapter;
+    }
+
+    public List<ChapterInfo> getChapterInfos() {
+        return chapterInfos;
+    }
+
+    public void setChapterInfos(List<ChapterInfo> chapterInfos) {
+        this.chapterInfos = chapterInfos;
+    }
+
+    public int getBookshowRegional() {
+        return bookshowRegional;
+    }
+
+    public void setBookshowRegional(int bookshowRegional) {
+        this.bookshowRegional = bookshowRegional;
+    }
+
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    @Override
+    public String toString() {
+        return "BookInfo{" +
+                "bookName='" + bookName + '\'' +
+                ", bookAuthor='" + bookAuthor + '\'' +
+                ", bookLink='" + bookLink + '\'' +
+                ", bookNewestChapterName='" + bookNewestChapterName + '\'' +
+                ", bookImage='" + bookImage + '\'' +
+                ", bookUpdateNewestChapterTime='" + bookUpdateNewestChapterTime + '\'' +
+                ", bookReadChapter='" + bookReadChapter + '\'' +
+                ", bookCatalogPosition=" + bookCatalogPosition +
+                ", bookReadPosition=" + bookReadPosition +
+                ", bookReadbookTime='" + bookReadbookTime + '\'' +
+                ", hasNewestChapter=" + hasNewestChapter +
+                ", chapterInfos=" + chapterInfos +
+                ", bookshowRegional=" + bookshowRegional +
+                ", errorCode=" + errorCode +
+                ", errorMessage='" + errorMessage + '\'' +
+                '}';
+    }
+}

+ 77 - 0
app/src/main/java/top/yinxueqin/readbook/bean/ChapterInfo.java

@@ -0,0 +1,77 @@
+package top.yinxueqin.readbook.bean;
+
+import java.io.Serializable;
+
+/**
+ * Author: yin
+ * Time: 2017/11/23.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class ChapterInfo implements Serializable{
+
+    private static final long serialVersionUID = 6561951697670021907L;
+
+    //章节名称
+    private String chapterName;
+    //章节地址
+    private String chapterLink;
+    //章节内容
+    private String chapterContent;
+    //错误代码
+    private int errorCode = 1000;
+    //错误原因
+    private String errorMessage = "正常";
+
+    public String getChapterName() {
+        return chapterName;
+    }
+
+    public void setChapterName(String chapterName) {
+        this.chapterName = chapterName;
+    }
+
+    public String getChapterLink() {
+        return chapterLink;
+    }
+
+    public void setChapterLink(String chapterLink) {
+        this.chapterLink = chapterLink;
+    }
+
+    public String getChapterContent() {
+        return chapterContent;
+    }
+
+    public void setChapterContent(String chapterContent) {
+        this.chapterContent = chapterContent;
+    }
+
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    @Override
+    public String toString() {
+        return "ChapterInfo{" +
+                "chapterName='" + chapterName + '\'' +
+                ", chapterLink='" + chapterLink + '\'' +
+                ", chapterContent='" + chapterContent + '\'' +
+                ", errorCode=" + errorCode +
+                ", errorMessage='" + errorMessage + '\'' +
+                '}';
+    }
+}

+ 40 - 0
app/src/main/java/top/yinxueqin/readbook/bean/SearchResult.java

@@ -0,0 +1,40 @@
+package top.yinxueqin.readbook.bean;
+
+import android.util.SparseArray;
+
+/**
+ * Author: yin
+ * Time: 2017/11/22.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 神马搜索结果
+ */
+
+public class SearchResult {
+
+    private SparseArray<String> bookLink;
+    private SparseArray<String> bookNewestChapter;
+
+    public SparseArray<String> getBookLink() {
+        return bookLink;
+    }
+
+    public void setBookLink(SparseArray<String> bookLink) {
+        this.bookLink = bookLink;
+    }
+
+    public SparseArray<String> getBookNewestChapter() {
+        return bookNewestChapter;
+    }
+
+    public void setBookNewestChapter(SparseArray<String> bookNewestChapter) {
+        this.bookNewestChapter = bookNewestChapter;
+    }
+
+    @Override
+    public String toString() {
+        return "SearchResult{" +
+                "bookLink=" + bookLink +
+                ", bookNewestChapter=" + bookNewestChapter +
+                '}';
+    }
+}

+ 106 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/DBDatabase.java

@@ -0,0 +1,106 @@
+package top.yinxueqin.readbook.data.local.db;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.TypeConverters;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import top.yinxueqin.readbook.AppExecutors;
+import top.yinxueqin.readbook.data.local.db.converter.DateConverter;
+import top.yinxueqin.readbook.data.local.db.dao.DBBookInfoDao;
+import top.yinxueqin.readbook.data.local.db.dao.DBChapterInfoDao;
+import top.yinxueqin.readbook.data.local.db.dao.ReadSettingConfigDao;
+import top.yinxueqin.readbook.data.local.db.dao.SearchHistoryDao;
+import top.yinxueqin.readbook.data.local.db.dao.SettingConfigDao;
+import top.yinxueqin.readbook.data.local.db.entity.DBChapterInfoEntity;
+import top.yinxueqin.readbook.data.local.db.entity.ReadSettingConfigEntity;
+import top.yinxueqin.readbook.data.local.db.entity.SearchHistoryEntity;
+import top.yinxueqin.readbook.data.local.db.entity.SettingConfigEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Database(entities = {DBChapterInfoEntity.class, DBChapterInfoEntity.class, ReadSettingConfigEntity.class,
+        SearchHistoryEntity.class, SettingConfigEntity.class}, version = 1)
+@TypeConverters(DateConverter.class)
+public abstract class DBDatabase extends RoomDatabase{
+
+    private static DBDatabase sInstance;
+
+    @VisibleForTesting
+    public static final String DATABASE_NAME = "soukanxiaoshuo";
+
+    public abstract DBBookInfoDao dbBookInfoDao();
+
+    public abstract DBChapterInfoDao dbChapterInfoDao();
+
+    public abstract ReadSettingConfigDao readSettingConfigDao();
+
+    public abstract SearchHistoryDao searchHistoryDao();
+
+    public abstract SettingConfigDao settingConfigDao();
+
+    private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();
+
+    public static DBDatabase getInstance(final Context context, final AppExecutors executors) {
+        if (sInstance == null) {
+            synchronized (DBDatabase.class) {
+                if (sInstance == null) {
+                    sInstance = buildDatabase(context.getApplicationContext(), executors);
+                    sInstance.updateDatabaseCreated(context.getApplicationContext());
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    private static DBDatabase buildDatabase(final Context appContext, final  AppExecutors executors) {
+        return Room.databaseBuilder(appContext, DBDatabase.class, DATABASE_NAME)
+                .addCallback(new Callback() {
+                    @Override
+                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
+                        super.onCreate(db);
+                        executors.dbIO().execute(() -> {
+                            DBDatabase database = DBDatabase.getInstance(appContext, executors);
+                            ReadSettingConfigEntity readSettingConfig = DataGenerator.getDefaultReadSettingConfig();
+                            SettingConfigEntity settingConfig = DataGenerator.getDefaultSettingConfig();
+
+                            insertData(database, readSettingConfig, settingConfig);
+
+                            database.setDatabaseCreated();
+                        });
+                    }
+                }).build();
+    }
+
+    private void updateDatabaseCreated(final Context context) {
+        if (context.getDatabasePath(DATABASE_NAME).exists()) {
+            setDatabaseCreated();
+        }
+    }
+
+    private void setDatabaseCreated(){
+        mIsDatabaseCreated.postValue(true);
+    }
+
+    private static void insertData(final DBDatabase database, final ReadSettingConfigEntity readSettingConfig,
+                                   final SettingConfigEntity settingConfig) {
+        database.runInTransaction(() -> {
+            database.readSettingConfigDao().insertAll(readSettingConfig);
+            database.settingConfigDao().insertAll(settingConfig);
+        });
+    }
+
+    public LiveData<Boolean> getDatabaseCreated() {
+        return mIsDatabaseCreated;
+    }
+}

+ 49 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/DataGenerator.java

@@ -0,0 +1,49 @@
+package top.yinxueqin.readbook.data.local.db;
+
+import top.yinxueqin.readbook.data.local.db.entity.ReadSettingConfigEntity;
+import top.yinxueqin.readbook.data.local.db.entity.SettingConfigEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/8.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class DataGenerator {
+
+    public static ReadSettingConfigEntity getDefaultReadSettingConfig() {
+
+        ReadSettingConfigEntity readSettingConfig = new ReadSettingConfigEntity();
+        readSettingConfig.setReadSettingId(1);
+        readSettingConfig.setDisplayBrightness(100);
+        readSettingConfig.setBrightnessFollowingsystem(true);
+        readSettingConfig.setTextSize(12);
+        readSettingConfig.setSimpleTransformation(false);
+        readSettingConfig.setLineSpacingModel(2);
+        readSettingConfig.setAutoPage(false);
+        readSettingConfig.setAutoSteering(false);
+        readSettingConfig.setBackgroundColor(1);
+        readSettingConfig.setVolumeKeyPage(false);
+        readSettingConfig.setFullScreenClickNextPage(false);
+        readSettingConfig.setFlipAnimation(false);
+        readSettingConfig.setFullScreenModel(true);
+        readSettingConfig.setOpenScreenOffTime(false);
+        readSettingConfig.setScreenOffTime(120);
+
+        return readSettingConfig;
+    }
+
+    public static SettingConfigEntity getDefaultSettingConfig() {
+
+        SettingConfigEntity settingConfig = new SettingConfigEntity();
+        settingConfig.setSettingId(1);
+        settingConfig.setThemeModel(1);
+        settingConfig.setBookListSortModel(2);
+        settingConfig.setUpdateNotice(true);
+        settingConfig.setSaveTraffic(true);
+        settingConfig.setSearchModel(1);
+
+        return settingConfig;
+    }
+}

+ 24 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/converter/DateConverter.java

@@ -0,0 +1,24 @@
+package top.yinxueqin.readbook.data.local.db.converter;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Date;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class DateConverter {
+    @TypeConverter
+    public static Date toDate(Long timestamp) {
+        return timestamp == null ? null : new Date(timestamp);
+    }
+
+    @TypeConverter
+    public static Long toTimestamp(Date date) {
+        return date == null ? null : date.getTime();
+    }
+}

+ 42 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/DBBookInfoDao.java

@@ -0,0 +1,42 @@
+package top.yinxueqin.readbook.data.local.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+
+import java.util.List;
+
+import top.yinxueqin.readbook.data.local.db.entity.DBBookInfoEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Dao
+public interface DBBookInfoDao {
+
+    @Query("SELECT * FROM BookInfo")
+    LiveData<List<DBBookInfoEntity>> loadAllBookInfo();
+
+    @Query("SELECT * FROM BookInfo where bookMD5 = :bookMD5")
+    LiveData<DBBookInfoEntity> loadDBBookInfo(String bookMD5);
+
+    @Query("SELECT * FROM BookInfo where bookMD5 = :bookMD5")
+    DBBookInfoEntity loadDBBookInfoSync(String bookMD5);
+
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    void updateBookInfo(DBBookInfoEntity bookInfo);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<DBBookInfoEntity> bookInfos);
+
+    @Delete
+    void delete(List<DBBookInfoEntity> bookInfos);
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/DBChapterInfoDao.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.data.local.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+
+import java.util.List;
+
+import top.yinxueqin.readbook.data.local.db.entity.DBChapterInfoEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Dao
+public interface DBChapterInfoDao {
+
+    @Query("SELECT * FROM ChapterInfo where bookInfoId = :bookId")
+    LiveData<List<DBChapterInfoEntity>> loadChapterInfos(int bookId);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertChapterInfo(DBChapterInfoEntity chapterInfo);
+
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    void updateChapterInfo(DBChapterInfoEntity chapterInfo);
+
+    @Delete
+    void deleteAll(List<DBChapterInfoEntity> chapterInfos);
+
+}

+ 31 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/ReadSettingConfigDao.java

@@ -0,0 +1,31 @@
+package top.yinxueqin.readbook.data.local.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+
+import top.yinxueqin.readbook.data.local.db.entity.ReadSettingConfigEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Dao
+public interface ReadSettingConfigDao {
+
+    @Query("SELECT * FROM ReadSettingConfig")
+    LiveData<ReadSettingConfigEntity> loadReadSettingConfig();
+
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    void updateReadSettingConfig(ReadSettingConfigEntity readSettingConfig);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(ReadSettingConfigEntity readSettingConfig);
+
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/SearchHistoryDao.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.data.local.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+
+import java.util.List;
+
+import top.yinxueqin.readbook.data.local.db.entity.SearchHistoryEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Dao
+public interface SearchHistoryDao {
+
+    @Query("SELECT * FROM SearchHistory")
+    LiveData<SearchHistoryEntity> loadSearchHistory();
+
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    void update(SearchHistoryEntity searchHistory);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<SearchHistoryEntity> searchHistores);
+
+    @Delete
+    void deleteAll(List<SearchHistoryEntity> searchHistoryEntities);
+
+}

+ 30 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/dao/SettingConfigDao.java

@@ -0,0 +1,30 @@
+package top.yinxueqin.readbook.data.local.db.dao;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+
+import top.yinxueqin.readbook.data.local.db.entity.SettingConfigEntity;
+
+/**
+ * Author: yin
+ * Time: 2018/3/7.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+@Dao
+public interface SettingConfigDao {
+
+    @Query("SELECT * FROM SettingConfig")
+    LiveData<SettingConfigEntity> loadSettingConfig();
+
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    void updateSettingConfig(SettingConfigEntity settingConfig);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(SettingConfigEntity settingConfig);
+}

+ 208 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/DBBookInfoEntity.java

@@ -0,0 +1,208 @@
+package top.yinxueqin.readbook.data.local.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.support.annotation.NonNull;
+
+import java.util.Date;
+
+import top.yinxueqin.readbook.model.DBBookInfo;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+@Entity(tableName = "BookInfo")
+public class DBBookInfoEntity implements DBBookInfo {
+
+
+    @PrimaryKey(autoGenerate = true)
+    private int bookId;
+    //书名称
+    private String bookName;
+    //书作者
+    private String bookAuthor;
+    //书地址
+    private String bookLink;
+    //书最新章节名称
+    private String bookNewestChapterName;
+    //书的封面
+    private String bookImage;
+    //最新更新时间
+    private String bookUpdateNewestChapterTime;
+    //最后阅读章节
+    private String bookReadChapter;
+    //阅读章节目录位置
+    private int bookCatalogPosition;
+    //最后阅读章节位置(当前章节的第几页或者百分比)
+    private int bookReadPosition;
+    //最后阅读本书的时间
+    private Date bookReadbookTime;
+    //有无更新
+    private boolean hasNewestChapter;
+    //显示在追书区,养肥区,完本保留区(0:追书区,1:养肥区,2:完本保留区)
+    private int bookshowRegional;
+    //唯一标识(用书名和作者的字符串计算MD5值)
+    private String bookMD5;
+
+    public DBBookInfoEntity() {
+    }
+
+
+    public DBBookInfoEntity(int bookId, @NonNull String bookName, @NonNull String bookAuthor, @NonNull String bookLink,
+                            @NonNull String bookNewestChapterName, @NonNull String bookImage,
+                            @NonNull String bookUpdateNewestChapterTime, @NonNull String bookReadChapter,
+                            int bookCatalogPosition, int bookReadPosition, @NonNull Date bookReadbookTime,
+                            boolean hasNewestChapter, int bookshowRegional, String bookMD5) {
+        this.bookId = bookId;
+        this.bookName = bookName;
+        this.bookAuthor = bookAuthor;
+        this.bookLink = bookLink;
+        this.bookNewestChapterName = bookNewestChapterName;
+        this.bookImage = bookImage;
+        this.bookUpdateNewestChapterTime = bookUpdateNewestChapterTime;
+        this.bookReadChapter = bookReadChapter;
+        this.bookCatalogPosition = bookCatalogPosition;
+        this.bookReadPosition = bookReadPosition;
+        this.bookReadbookTime = bookReadbookTime;
+        this.hasNewestChapter = hasNewestChapter;
+        this.bookshowRegional = bookshowRegional;
+        this.bookMD5 = bookMD5;
+    }
+
+    @Override
+    public int getBookId() {
+        return bookId;
+    }
+
+    public void setBookId(int bookId) {
+        this.bookId = bookId;
+    }
+
+    @Override
+    @NonNull
+    public String getBookName() {
+        return bookName;
+    }
+
+    public void setBookName(@NonNull String bookName) {
+        this.bookName = bookName;
+    }
+
+    @Override
+    @NonNull
+    public String getBookAuthor() {
+        return bookAuthor;
+    }
+
+    public void setBookAuthor(@NonNull String bookAuthor) {
+        this.bookAuthor = bookAuthor;
+    }
+
+    @NonNull
+    @Override
+    public String getBookLink() {
+        return bookLink;
+    }
+
+    public void setBookLink(@NonNull String bookLink) {
+        this.bookLink = bookLink;
+    }
+
+    @NonNull
+    @Override
+    public String getBookNewestChapterName() {
+        return bookNewestChapterName;
+    }
+
+    public void setBookNewestChapterName(@NonNull String bookNewestChapterName) {
+        this.bookNewestChapterName = bookNewestChapterName;
+    }
+
+    @NonNull
+    @Override
+    public String getBookImage() {
+        return bookImage;
+    }
+
+    public void setBookImage(@NonNull String bookImage) {
+        this.bookImage = bookImage;
+    }
+
+    @NonNull
+    @Override
+    public String getBookUpdateNewestChapterTime() {
+        return bookUpdateNewestChapterTime;
+    }
+
+    public void setBookUpdateNewestChapterTime(@NonNull String bookUpdateNewestChapterTime) {
+        this.bookUpdateNewestChapterTime = bookUpdateNewestChapterTime;
+    }
+
+    @NonNull
+    @Override
+    public String getBookReadChapter() {
+        return bookReadChapter;
+    }
+
+    public void setBookReadChapter(@NonNull String bookReadChapter) {
+        this.bookReadChapter = bookReadChapter;
+    }
+
+    @Override
+    public int getBookCatalogPosition() {
+        return bookCatalogPosition;
+    }
+
+    public void setBookCatalogPosition(int bookCatalogPosition) {
+        this.bookCatalogPosition = bookCatalogPosition;
+    }
+
+    @Override
+    public int getBookReadPosition() {
+        return bookReadPosition;
+    }
+
+    public void setBookReadPosition(int bookReadPosition) {
+        this.bookReadPosition = bookReadPosition;
+    }
+
+    @NonNull
+    @Override
+    public Date getBookReadbookTime() {
+        return bookReadbookTime;
+    }
+
+    public void setBookReadbookTime(@NonNull Date bookReadbookTime) {
+        this.bookReadbookTime = bookReadbookTime;
+    }
+
+    @Override
+    public boolean isHasNewestChapter() {
+        return hasNewestChapter;
+    }
+
+    public void setHasNewestChapter(boolean hasNewestChapter) {
+        this.hasNewestChapter = hasNewestChapter;
+    }
+
+    @Override
+    public int getBookshowRegional() {
+        return bookshowRegional;
+    }
+
+    public void setBookshowRegional(int bookshowRegional) {
+        this.bookshowRegional = bookshowRegional;
+    }
+
+    @Override
+    public String getBookMD5() {
+        return bookMD5;
+    }
+
+    public void setBookMD5(String bookMD5) {
+        this.bookMD5 = bookMD5;
+    }
+}

+ 116 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/DBChapterInfoEntity.java

@@ -0,0 +1,116 @@
+package top.yinxueqin.readbook.data.local.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.PrimaryKey;
+
+import top.yinxueqin.readbook.model.DBChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+@Entity(tableName = "ChapterInfo")
+public class DBChapterInfoEntity implements DBChapterInfo {
+
+    @PrimaryKey(autoGenerate = true)
+    private int chapterId;
+    //章节名称
+    private String chapterName;
+    //章节地址
+    private String chapterLink;
+    //该章节是否缓存
+    private boolean chapterCache;
+    //该章节唯一标识码
+    private String chatperMD5;
+    //章节内容
+    private String chapterContent;
+    //目录信息
+    @ForeignKey(
+            entity = DBBookInfoEntity.class,
+            parentColumns = {"bookId"},
+            childColumns = {"bookInfoId"},
+            onDelete = ForeignKey.CASCADE)
+    private int bookInfoId;
+
+
+    public DBChapterInfoEntity() {
+    }
+
+    public DBChapterInfoEntity(int chapterId, String chapterName, String chapterLink,
+                               boolean chapterCache, String chatperMD5, String chapterContent, int bookInfoId) {
+        this.chapterId = chapterId;
+        this.chapterName = chapterName;
+        this.chapterLink = chapterLink;
+        this.chapterCache = chapterCache;
+        this.chatperMD5 = chatperMD5;
+        this.chapterContent = chapterContent;
+        this.bookInfoId = bookInfoId;
+    }
+
+
+    @Override
+    public int getChapterId() {
+        return chapterId;
+    }
+
+    public void setChapterId(int chapterId) {
+        this.chapterId = chapterId;
+    }
+
+    @Override
+    public String getChapterName() {
+        return chapterName;
+    }
+
+    public void setChapterName(String chapterName) {
+        this.chapterName = chapterName;
+    }
+
+    @Override
+    public String getChapterLink() {
+        return chapterLink;
+    }
+
+    public void setChapterLink(String chapterLink) {
+        this.chapterLink = chapterLink;
+    }
+
+    @Override
+    public boolean isChapterCache() {
+        return chapterCache;
+    }
+
+    public void setChapterCache(boolean chapterCache) {
+        this.chapterCache = chapterCache;
+    }
+
+    @Override
+    public String getChatperMD5() {
+        return chatperMD5;
+    }
+
+    public void setChatperMD5(String chatperMD5) {
+        this.chatperMD5 = chatperMD5;
+    }
+
+    @Override
+    public String getChapterContent() {
+        return chapterContent;
+    }
+
+    public void setChapterContent(String chapterContent) {
+        this.chapterContent = chapterContent;
+    }
+
+    @Override
+    public int getBookInfoId() {
+        return bookInfoId;
+    }
+
+    public void setBookInfoId(int bookInfoId) {
+        this.bookInfoId = bookInfoId;
+    }
+}

+ 205 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/ReadSettingConfigEntity.java

@@ -0,0 +1,205 @@
+package top.yinxueqin.readbook.data.local.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+import top.yinxueqin.readbook.model.ReadSettingConfig;
+
+/**
+ * Author: yin
+ * Time: 2018/3/6.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+@Entity(tableName = "ReadSettingConfig")
+public class ReadSettingConfigEntity implements ReadSettingConfig{
+
+    @PrimaryKey(autoGenerate = true)
+    private int readSettingId;
+    //屏幕亮度(0-255或者0.0-1.0之间),默认:-1(系统亮度)
+    private int displayBrightness;
+    //屏幕亮度是否跟随系统,默认:true
+    private boolean brightnessFollowingsystem;
+    //字体大小,默认:12
+    private int textSize;
+    //简繁转换,默认:false
+    private boolean simpleTransformation;
+    //行距模式(1:大间距,2:中等间距,3:小间距),默认:中等间距)
+    private int lineSpacingModel;
+    //是否开始自动翻页,默认:false
+    private boolean autoPage;
+    //自动转向,默认:false
+    private boolean autoSteering;
+    //阅读背景颜色,(1,淡黄色,2,3,4,5),默认:1(淡黄色)
+    private int backgroundColor;
+    //是否开始音乐键翻页,默认:false
+    private boolean volumeKeyPage;
+    //是否开启全屏点击翻下页,默认:false
+    private boolean fullScreenClickNextPage;
+    //是否开始翻页动画,默认:false
+    private boolean flipAnimation;
+    //是否开启全屏阅读模式,默认:true
+    private boolean fullScreenModel;
+    //是否打开自定义熄屏时间,默认:false
+    private boolean openScreenOffTime;
+    //自定义熄屏时间,默认:2分钟(120秒)
+    private int screenOffTime;
+
+    public ReadSettingConfigEntity() {}
+
+    public ReadSettingConfigEntity(int readSettingId, int displayBrightness, boolean brightnessFollowingsystem, int textSize,
+                                   boolean simpleTransformation, int lineSpacingModel, boolean autoPage, boolean autoSteering,
+                                   int backgroundColor, boolean volumeKeyPage, boolean fullScreenClickNextPage, boolean flipAnimation,
+                                   boolean fullScreenModel, boolean openScreenOffTime, int screenOffTime) {
+        this.readSettingId = readSettingId;
+        this.displayBrightness = displayBrightness;
+        this.brightnessFollowingsystem = brightnessFollowingsystem;
+        this.textSize = textSize;
+        this.simpleTransformation = simpleTransformation;
+        this.lineSpacingModel = lineSpacingModel;
+        this.autoPage = autoPage;
+        this.autoSteering = autoSteering;
+        this.backgroundColor = backgroundColor;
+        this.volumeKeyPage = volumeKeyPage;
+        this.fullScreenClickNextPage = fullScreenClickNextPage;
+        this.flipAnimation = flipAnimation;
+        this.fullScreenModel = fullScreenModel;
+        this.openScreenOffTime = openScreenOffTime;
+        this.screenOffTime = screenOffTime;
+    }
+
+    @Override
+    public int getReadSettingId() {
+        return readSettingId;
+    }
+
+    public void setReadSettingId(int readSettingId) {
+        this.readSettingId = readSettingId;
+    }
+
+    @Override
+    public int getDisplayBrightness() {
+        return displayBrightness;
+    }
+
+    public void setDisplayBrightness(int displayBrightness) {
+        this.displayBrightness = displayBrightness;
+    }
+
+    @Override
+    public boolean isBrightnessFollowingsystem() {
+        return brightnessFollowingsystem;
+    }
+
+    public void setBrightnessFollowingsystem(boolean brightnessFollowingsystem) {
+        this.brightnessFollowingsystem = brightnessFollowingsystem;
+    }
+
+    @Override
+    public int getTextSize() {
+        return textSize;
+    }
+
+    public void setTextSize(int textSize) {
+        this.textSize = textSize;
+    }
+
+    @Override
+    public boolean isSimpleTransformation() {
+        return simpleTransformation;
+    }
+
+    public void setSimpleTransformation(boolean simpleTransformation) {
+        this.simpleTransformation = simpleTransformation;
+    }
+
+    @Override
+    public int getLineSpacingModel() {
+        return lineSpacingModel;
+    }
+
+    public void setLineSpacingModel(int lineSpacingModel) {
+        this.lineSpacingModel = lineSpacingModel;
+    }
+
+    @Override
+    public boolean isAutoPage() {
+        return autoPage;
+    }
+
+    public void setAutoPage(boolean autoPage) {
+        this.autoPage = autoPage;
+    }
+
+    @Override
+    public boolean isAutoSteering() {
+        return autoSteering;
+    }
+
+    public void setAutoSteering(boolean autoSteering) {
+        this.autoSteering = autoSteering;
+    }
+
+    @Override
+    public int getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(int backgroundColor) {
+        this.backgroundColor = backgroundColor;
+    }
+
+    @Override
+    public boolean isVolumeKeyPage() {
+        return volumeKeyPage;
+    }
+
+    public void setVolumeKeyPage(boolean volumeKeyPage) {
+        this.volumeKeyPage = volumeKeyPage;
+    }
+
+    @Override
+    public boolean isFullScreenClickNextPage() {
+        return fullScreenClickNextPage;
+    }
+
+    public void setFullScreenClickNextPage(boolean fullScreenClickNextPage) {
+        this.fullScreenClickNextPage = fullScreenClickNextPage;
+    }
+
+    @Override
+    public boolean isFlipAnimation() {
+        return flipAnimation;
+    }
+
+    public void setFlipAnimation(boolean flipAnimation) {
+        this.flipAnimation = flipAnimation;
+    }
+
+    @Override
+    public boolean isFullScreenModel() {
+        return fullScreenModel;
+    }
+
+    public void setFullScreenModel(boolean fullScreenModel) {
+        this.fullScreenModel = fullScreenModel;
+    }
+
+    @Override
+    public boolean isOpenScreenOffTime() {
+        return openScreenOffTime;
+    }
+
+    public void setOpenScreenOffTime(boolean openScreenOffTime) {
+        this.openScreenOffTime = openScreenOffTime;
+    }
+
+    @Override
+    public int getScreenOffTime() {
+        return screenOffTime;
+    }
+
+    public void setScreenOffTime(int screenOffTime) {
+        this.screenOffTime = screenOffTime;
+    }
+}

+ 60 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/SearchHistoryEntity.java

@@ -0,0 +1,60 @@
+package top.yinxueqin.readbook.data.local.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+import java.util.Date;
+
+import top.yinxueqin.readbook.model.SearchHistory;
+
+/**
+ * Author: yin
+ * Time: 2018/3/6.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+@Entity(tableName = "SearchHistory")
+public class SearchHistoryEntity implements SearchHistory {
+
+    @PrimaryKey(autoGenerate = true)
+    private int searchId;
+    //搜索的名称
+    private String searchName;
+    //搜索的时间日期
+    private Date searchDate;
+
+    private SearchHistoryEntity() {}
+
+    public SearchHistoryEntity(int searchId, String searchName, Date searchDate) {
+        this.searchId = searchId;
+        this.searchName = searchName;
+        this.searchDate = searchDate;
+    }
+
+    @Override
+    public int getSearchId() {
+        return searchId;
+    }
+
+    public void setSearchId(int searchId) {
+        this.searchId = searchId;
+    }
+
+    @Override
+    public String getSearchName() {
+        return searchName;
+    }
+
+    public void setSearchName(String searchName) {
+        this.searchName = searchName;
+    }
+
+    @Override
+    public Date getSearchDate() {
+        return searchDate;
+    }
+
+    public void setSearchDate(Date searchDate) {
+        this.searchDate = searchDate;
+    }
+}

+ 94 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/db/entity/SettingConfigEntity.java

@@ -0,0 +1,94 @@
+package top.yinxueqin.readbook.data.local.db.entity;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+import top.yinxueqin.readbook.model.SettingConfig;
+
+/**
+ * Author: yin
+ * Time: 2018/3/6.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+@Entity(tableName = "SettingConfig")
+public class SettingConfigEntity implements SettingConfig {
+
+    @PrimaryKey(autoGenerate = true)
+    private int settingId;
+    //主题样式(1:日间模式, 2:夜间模式),默认:1
+    private int themeModel;
+    //书架排列方式(1:阅读时间, 2:更新时间), 默认:2
+    private int bookListSortModel;
+    //显示更新通知,默认:true
+    private boolean updateNotice;
+    //是否开启省流量模式,默认:true
+    private boolean saveTraffic;
+    //搜索方式(1:神马搜索, 2:追书搜索, 3:百度搜索, 4:宜搜搜索),默认:1
+    private int searchModel;
+
+    public SettingConfigEntity() {}
+
+    public SettingConfigEntity(int settingId, int themeModel, int bookListSortModel, boolean updateNotice, boolean saveTraffic, int searchModel) {
+        this.settingId = settingId;
+        this.themeModel = themeModel;
+        this.bookListSortModel = bookListSortModel;
+        this.updateNotice = updateNotice;
+        this.saveTraffic = saveTraffic;
+        this.searchModel = searchModel;
+    }
+
+    @Override
+    public int getSettingId() {
+        return settingId;
+    }
+
+    public void setSettingId(int settingId) {
+        this.settingId = settingId;
+    }
+
+    @Override
+    public int getThemeModel() {
+        return themeModel;
+    }
+
+    public void setThemeModel(int themeModel) {
+        this.themeModel = themeModel;
+    }
+
+    @Override
+    public int getBookListSortModel() {
+        return bookListSortModel;
+    }
+
+    public void setBookListSortModel(int bookListSortModel) {
+        this.bookListSortModel = bookListSortModel;
+    }
+
+    @Override
+    public boolean isUpdateNotice() {
+        return updateNotice;
+    }
+
+    public void setUpdateNotice(boolean updateNotice) {
+        this.updateNotice = updateNotice;
+    }
+
+    @Override
+    public boolean isSaveTraffic() {
+        return saveTraffic;
+    }
+
+    public void setSaveTraffic(boolean saveTraffic) {
+        this.saveTraffic = saveTraffic;
+    }
+
+    @Override
+    public int getSearchModel() {
+        return searchModel;
+    }
+
+    public void setSearchModel(int searchModel) {
+        this.searchModel = searchModel;
+    }
+}

+ 194 - 0
app/src/main/java/top/yinxueqin/readbook/data/local/file/DSDiskStorage.java

@@ -0,0 +1,194 @@
+package top.yinxueqin.readbook.data.local.file;
+
+import android.content.Context;
+import android.os.Environment;
+
+import com.jakewharton.disklrucache.DiskLruCache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import top.yinxueqin.readbook.AppExecutors;
+
+/**
+ * Author: yin
+ * Time: 2018/3/8.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class DSDiskStorage {
+
+    private volatile static DSDiskStorage operationInstance;
+
+    private DiskLruCache mDiskLruCache;
+    private Context mContext;
+    private AppExecutors mAppExecutors;
+
+    private DSDiskStorage(Context context, AppExecutors executors) {
+        mContext = context;
+        mAppExecutors = executors;
+    }
+
+    public static DSDiskStorage getInstance(final Context context, AppExecutors executors) {
+        if (operationInstance == null) {
+            synchronized (DSDiskStorage.class) {
+                if (operationInstance == null) {
+                    operationInstance = new DSDiskStorage(context, executors);
+                }
+            }
+        }
+        return operationInstance;
+    }
+
+    public String saveChapter(DiskLruCache diskLruCache, String chapterUrl,
+                              String chapterContent) {
+        // 生成网页URL对应的key
+        final String key = hashKeyForDisk(chapterUrl);
+        DiskLruCache.Editor editor = null;
+        try {
+            editor = mDiskLruCache.edit(key);
+            OutputStream outputStream = editor.newOutputStream(0);
+            if ( streamSavaData(outputStream, chapterContent) ) {
+                editor.commit();
+            } else {
+                editor.abort();
+            }
+            return key;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public String getChapter(DiskLruCache diskLruCache, String key) {
+        StringBuilder chapterContent = new StringBuilder();
+        DiskLruCache.Snapshot snapShot = null;
+        FileInputStream fileInputStream = null;
+        // 查找key对应的缓存
+        try {
+            snapShot = mDiskLruCache.get(key);
+            if (snapShot != null) {
+                fileInputStream = (FileInputStream) snapShot.getInputStream(0);
+                int len = 0;
+                byte[] buf = new byte[1024];
+                while((len = fileInputStream.read(buf)) != -1){
+                    chapterContent.append(new String(buf, 0, len));
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (snapShot != null) {
+                snapShot.close();
+            }
+            if (fileInputStream != null) {
+                try {
+                    fileInputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+        }
+        return chapterContent.toString();
+    }
+
+    private boolean streamSavaData(OutputStream outputStream, String content) {
+        try {
+            outputStream.write(content.getBytes());
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return false;
+    }
+
+
+
+
+    /**
+     *
+     * @param context
+     * @param type 文件夹父名称
+     * @param bookname
+     * @return
+     */
+    public DiskLruCache getDiskLruCache(Context context, String type, String bookname) {
+        try {
+            // 获取图片缓存路径
+            File storageDir = getDiskStorageDir(mContext, type + File.separator + bookname);
+            if (storageDir != null && !storageDir.exists()) {
+                storageDir.mkdirs();
+            }
+            // 创建DiskLruCache实例,初始化缓存数据
+            mDiskLruCache = DiskLruCache.open(storageDir, 1, 1, 20 * 1024 * 1024);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return mDiskLruCache;
+    }
+
+    /**
+     * 根据传入的uniqueName获取硬盘存储的路径地址。
+     */
+    public File getDiskStorageDir(Context context, String uniqueName) {
+        String storagePath = "";
+        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+                || !Environment.isExternalStorageRemovable()) {
+            try {
+                storagePath = Environment.getExternalStorageDirectory().getCanonicalPath() + File.separator + "soushushenqi";
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } else {
+            storagePath = context.getFilesDir().getPath();
+        }
+        return new File(storagePath + File.separator + uniqueName);
+    }
+
+    /**
+     * 使用MD5算法对传入的key进行加密并返回。
+     */
+    public String hashKeyForDisk(String key) {
+        String cacheKey;
+        try {
+            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
+            mDigest.update(key.getBytes());
+            cacheKey = bytesToHexString(mDigest.digest());
+        } catch (NoSuchAlgorithmException e) {
+            cacheKey = String.valueOf(key.hashCode());
+        }
+        return cacheKey;
+    }
+
+    private String bytesToHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < bytes.length; i++) {
+            String hex = Integer.toHexString(0xFF & bytes[i]);
+            if (hex.length() == 1) {
+                sb.append('0');
+            }
+            sb.append(hex);
+        }
+        return sb.toString();
+    }
+
+
+
+
+
+}

+ 117 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/GetBookInfo.java

@@ -0,0 +1,117 @@
+package top.yinxueqin.readbook.data.remote;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+import top.yinxueqin.readbook.data.remote.book.Biquge;
+import top.yinxueqin.readbook.data.remote.book.BookNext9;
+import top.yinxueqin.readbook.data.remote.book.BookWeb;
+import top.yinxueqin.readbook.utils.ErrorCodeConstants;
+
+/**
+ * Author: yin
+ * Time: 2017/11/29.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 获取书籍信息
+ */
+
+public class GetBookInfo implements BookWeb{
+
+
+
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        BookInfo bookInfo = new BookInfo();
+        if (url != null && url.length() > 0) {
+            if ( url.contains("m.qu.la") ) {
+                Biquge biquge = Biquge.getInstance();
+                return biquge.getBookInfo(url);
+            } else if ( url.contains("m.book9.net") ) {
+                BookNext9 bookNext9 = BookNext9.getInstance();
+                return bookNext9.getBookInfo(url);
+            } else {
+                bookInfo.setErrorCode(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED);
+                bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED));
+            }
+        } else {
+            bookInfo.setErrorCode(ErrorCodeConstants.URL_ADDRESS_IS_NULL);
+            bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.URL_ADDRESS_IS_NULL));
+        }
+        return bookInfo;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        if (url != null && url.length() > 0) {
+            if ( url.contains("m.qu.la") ) {
+                Biquge biquge = Biquge.getInstance();
+                return biquge.getBookUpdateTimeAndNewestChapter(bookInfo, url);
+            } else if ( url.contains("m.book9.net") ) {
+                BookNext9 bookNext9 = BookNext9.getInstance();
+                return bookNext9.getBookUpdateTimeAndNewestChapter(bookInfo, url);
+            } else {
+                bookInfo.setErrorCode(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED);
+                bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED));
+            }
+        } else {
+            bookInfo.setErrorCode(ErrorCodeConstants.URL_ADDRESS_IS_NULL);
+            bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.URL_ADDRESS_IS_NULL));
+        }
+        return bookInfo;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        if (url != null && url.length() > 0) {
+            if ( url.contains("m.qu.la") ) {
+                Biquge biquge = Biquge.getInstance();
+                return biquge.getBookChapterList(bookInfo, url);
+            } else if ( url.contains("m.book9.net") ) {
+                BookNext9 bookNext9 = BookNext9.getInstance();
+                return bookNext9.getBookChapterList(bookInfo, url);
+            } else {
+                bookInfo.setErrorCode(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED);
+                bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED));
+            }
+        } else {
+            bookInfo.setErrorCode(ErrorCodeConstants.URL_ADDRESS_IS_NULL);
+            bookInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.URL_ADDRESS_IS_NULL));
+        }
+        return bookInfo;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        ChapterInfo chapterInfo = new ChapterInfo();
+        if (url != null && url.length() > 0) {
+            if ( url.contains("m.qu.la") ) {
+                Biquge biquge = Biquge.getInstance();
+                return biquge.getChapterContent(url);
+            } else if ( url.contains("m.book9.net") ) {
+                BookNext9 bookNext9 = BookNext9.getInstance();
+                return bookNext9.getChapterContent(url);
+            } else {
+                chapterInfo.setErrorCode(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED);
+                chapterInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.THIS_SITE_IS_NOT_SUPPORTED));
+            }
+        } else {
+            chapterInfo.setErrorCode(ErrorCodeConstants.URL_ADDRESS_IS_NULL);
+            chapterInfo.setErrorMessage(ErrorCodeConstants.getErrorMessage(ErrorCodeConstants.URL_ADDRESS_IS_NULL));
+        }
+        return chapterInfo;
+    }
+
+    /**
+     * 检测书架的书是否更新章节
+     * @param bookInfo
+     * @param url
+     * @return
+     * @throws IOException
+     */
+    public BookInfo checkUpdate(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 12 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/GetSearchInfo.java

@@ -0,0 +1,12 @@
+package top.yinxueqin.readbook.data.remote;
+
+/**
+ * Author: yin
+ * Time: 2017/11/29.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 获取搜索结果
+ */
+
+public class GetSearchInfo {
+
+}

+ 12 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/GetUiBookSearchResult.java

@@ -0,0 +1,12 @@
+package top.yinxueqin.readbook.data.remote;
+
+/**
+ * Author: yin
+ * Time: 2017/11/29.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class GetUiBookSearchResult {
+
+}

+ 192 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Biquge.java

@@ -0,0 +1,192 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+import top.yinxueqin.readbook.utils.Constants;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 笔趣阁(m.qu.la)
+ */
+
+public class Biquge implements BookWeb {
+
+    private static class BiqugeHolder {
+        private static final Biquge INSTANCE = new Biquge();
+    }
+
+    private Biquge (){}
+
+    public static final Biquge getInstance() {
+        return BiqugeHolder.INSTANCE;
+    }
+
+    /**
+     * 获取书的基本信息(书名,作者,封面地址,最新章节名,最新章节更新时间)
+     * @param url 书的网址
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+
+        url = Preconditions.checkNotNull(url);
+
+        BookInfo bookInfo = new BookInfo();
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        String bookName = doc.select("header").select("span").text();
+        Elements node1 = doc.select("div.synopsisArea")
+                .select("div.synopsisArea_detail");
+        String bookImageUrl = node1.select("img")
+                .attr("src");
+
+        String author = node1.select("a").select("p").text().substring(3);
+
+        Elements node2 = node1.select("p");
+        String updateTime = node2.get(3).text().substring(3);
+        String newChapterName = node2.get(4).text().substring(3);
+
+        bookInfo.setBookName(bookName);
+        bookInfo.setBookAuthor(author);
+        bookInfo.setBookImage(bookImageUrl);
+        bookInfo.setBookUpdateNewestChapterTime(updateTime);
+        bookInfo.setBookNewestChapterName(newChapterName);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取书的最新章节更新时间和章节名
+     * @param url 地址
+     * @param bookInfo 书的信息
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+
+        bookInfo = Preconditions.checkNotNull(bookInfo);
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements node1 = doc.select("div.synopsisArea")
+                .select("div.synopsisArea_detail");
+
+        Elements node2 = node1.select("p");
+        String updateTime = node2.get(3).text().substring(3);
+        String newChapterName = node2.get(4).text().substring(3);
+
+        bookInfo.setBookUpdateNewestChapterTime(updateTime);
+        bookInfo.setBookNewestChapterName(newChapterName);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取书的目录
+     * @param bookInfo 书的信息
+     * @param url 书的地址
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+
+        bookInfo = Preconditions.checkNotNull(bookInfo);
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        String catalogLink = doc.select("div.recommend")
+                .select("h2")
+                .select("a#AllChapterList2")
+                .attr("abs:href");
+
+        Document catalogDoc = Jsoup.connect(catalogLink)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements catalogList = catalogDoc.select("div#chapterlist")
+                .select("p");
+
+        List<ChapterInfo> chapterInfoList = new ArrayList<>();
+
+        for (Element element : catalogList) {
+            if ("#bottom".equals(element.select("a").attr("href"))) {
+                continue;
+            }
+            ChapterInfo chapterInfo = new ChapterInfo();
+            String chapterLink = element.select("a").attr("abs:href");
+            String chapterName = element.select("a").text();
+            chapterInfo.setChapterLink(chapterLink);
+            chapterInfo.setChapterName(chapterName);
+            chapterInfoList.add(chapterInfo);
+        }
+
+        bookInfo.setChapterInfos(chapterInfoList);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取章节内容
+     * @param url 章节地址
+     * @return 章节信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements chapterContent = doc.select("div#chaptercontent");
+
+        Elements filterChapterContent = chapterContent.select("p");
+        for (Element element : filterChapterContent) {
+            element.text("");
+            element.html("");
+        }
+
+        String chapterContentHtml = chapterContent.outerHtml();
+
+        chapterContentHtml = chapterContentHtml.replaceAll("&nbsp;", " ");
+        chapterContentHtml = chapterContentHtml.replaceAll("<br>", "\n");
+        chapterContentHtml = chapterContentHtml.replaceAll("<([^>]*)>", " ");
+
+        ChapterInfo chapterInfo = new ChapterInfo();
+        chapterInfo.setChapterContent(chapterContentHtml);
+        return chapterInfo;
+    }
+}

+ 209 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/BookNext9.java

@@ -0,0 +1,209 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+import com.google.common.base.Preconditions;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+import top.yinxueqin.readbook.utils.Constants;
+
+/**
+ *
+ * @ClassName: BookNext9
+ * @Description: m.book9.net
+ * @author yinxueqin@rd.hgits.cn
+ * @date 2017/11/28 10:57
+ * m.book9.net
+ */
+
+public class BookNext9 implements BookWeb {
+
+    private static class BookNext9Holder {
+        private static final BookNext9 INSTANCE = new BookNext9();
+    }
+
+    private BookNext9 (){}
+
+    public static final BookNext9 getInstance() {
+        return BookNext9Holder.INSTANCE;
+    }
+
+    /**
+     *
+     * @param url 网站网址
+     * @return 小说基本信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+
+        url = Preconditions.checkNotNull(url);
+
+        BookInfo bookInfo = new BookInfo();
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements node1 = doc.select("body");
+
+        String bookName = node1.select("div.top")
+                .text().substring(0, 4);
+
+        Elements node2 = node1.select("div.synopsisArea")
+                .select("div.synopsisArea_detail");
+        String bookImageUrl = node2.select("img")
+                .attr("src");
+
+        Elements node3 = node2.select("p");
+        String author = node3.get(0).text().substring(3);
+        String updateTime = node3.get(3).select("a").text();
+        String newChapterName = node3.get(4).text().substring(3);
+
+        bookInfo.setBookName(bookName);
+        bookInfo.setBookAuthor(author);
+        bookInfo.setBookImage(bookImageUrl);
+        bookInfo.setBookUpdateNewestChapterTime(updateTime);
+        bookInfo.setBookNewestChapterName(newChapterName);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取书的最新章节更新时间和章节名
+     * @param url 地址
+     * @param bookInfo 书的信息
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+
+        bookInfo = Preconditions.checkNotNull(bookInfo);
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements node1 = doc.select("body");
+
+        Elements node2 = node1.select("div.synopsisArea")
+                .select("div.synopsisArea_detail");
+
+        Elements node3 = node2.select("p");
+        String updateTime = node3.get(3).select("a").text();
+        String newChapterName = node3.get(4).text().substring(3);
+
+        bookInfo.setBookUpdateNewestChapterTime(updateTime);
+        bookInfo.setBookNewestChapterName(newChapterName);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取书的目录
+     * @param bookInfo 书的信息
+     * @param url 书的地址
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+
+        bookInfo = Preconditions.checkNotNull(bookInfo);
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        String catalogLink = doc.select("div.recommend")
+                .select("div.top_l")
+                .get(1)
+                .select("a")
+                .attr("abs:href");
+
+        System.out.println("catalogLink:"+catalogLink);
+
+        Document catalogDoc = Jsoup.connect(catalogLink)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+        Elements catalogList = catalogDoc.select("div.chapterlist")
+                .select("p");
+
+        List<ChapterInfo> chapterInfoList = new ArrayList<>();
+
+        for (Element element : catalogList) {
+            if ("#bottom".equals(element.select("a").attr("href"))) {
+                continue;
+            }
+
+            ChapterInfo chapterInfo = new ChapterInfo();
+
+            String chapterLink = element.select("a").attr("abs:href");
+            String chapterName = element.select("a").text();
+
+            chapterInfo.setChapterLink(chapterLink);
+            chapterInfo.setChapterName(chapterName);
+            chapterInfoList.add(chapterInfo);
+        }
+
+        bookInfo.setChapterInfos(chapterInfoList);
+
+        return bookInfo;
+    }
+
+    /**
+     * 获取章节内容
+     * @param url 章节地址
+     * @return 章节信息
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+
+        url = Preconditions.checkNotNull(url);
+
+        Document doc = Jsoup.connect(url)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+
+
+        Elements chapterContent = doc.select("div#chaptercontent");
+
+        Elements filterChapterContent = chapterContent.select("p");
+        for (Element element : filterChapterContent) {
+            element.text("");
+            element.html("");
+        }
+
+        Elements filterChapterContent2 = chapterContent.select("center");
+        for (Element element : filterChapterContent2) {
+            element.text("");
+            element.html("");
+        }
+
+        String chapterContentHtml = chapterContent.outerHtml();
+
+        chapterContentHtml = chapterContentHtml.replaceAll("&nbsp;", " ");
+        chapterContentHtml = chapterContentHtml.replaceAll("<br>", "\n");
+        chapterContentHtml = chapterContentHtml.replaceAll("<([^>]*)>", "");
+
+        ChapterInfo chapterInfo = new ChapterInfo();
+        chapterInfo.setChapterContent(chapterContentHtml);
+        return chapterInfo;
+    }
+
+}

+ 52 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/BookWeb.java

@@ -0,0 +1,52 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: Admin
+ * Time: 2017/12/9.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface BookWeb {
+
+    /**
+     * 获取书的基本信息(书名,封面地址,最新章节名,最新章节更新时间)
+     * @param url 书的网址
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    BookInfo getBookInfo(@NonNull String url) throws IOException;
+
+    /**
+     * 获取书的最新章节更新时间和章节名
+     * @param url 地址
+     * @param bookInfo 书的信息
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException;
+
+    /**
+     * 获取书的目录
+     * @param bookInfo 书的信息
+     * @param url 书的地址
+     * @return 书的信息
+     * @throws IOException IO异常
+     */
+    BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException;
+
+    /**
+     * 获取章节内容
+     * @param url 章节地址
+     * @return 章节信息
+     * @throws IOException IO异常
+     */
+    ChapterInfo getChapterContent(@NonNull String url) throws IOException;
+}

+ 40 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Dingdian.java

@@ -0,0 +1,40 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 顶点(http://m.booktxt.net)
+ */
+
+public class Dingdian implements BookWeb {
+
+
+
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Ixscc.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 神马(http://m.ixs.cc)
+ */
+
+public class Ixscc implements BookWeb{
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Shuku56.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 56书库(m.56shuku.org)
+ * */
+
+public class Shuku56 implements BookWeb {
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Xiaoxiaoshuoshuo.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 乡村小说(http://m.xiangcunxiaoshuo.com/)
+ */
+
+public class Xiaoxiaoshuoshuo implements BookWeb{
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 38 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/book/Yetianzi.java

@@ -0,0 +1,38 @@
+package top.yinxueqin.readbook.data.remote.book;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.BookInfo;
+import top.yinxueqin.readbook.bean.ChapterInfo;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 笔下文学(yetianzi.com)
+ */
+
+public class Yetianzi implements BookWeb{
+
+    @Override
+    public BookInfo getBookInfo(@NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookUpdateTimeAndNewestChapter(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public BookInfo getBookChapterList(@NonNull BookInfo bookInfo, @NonNull String url) throws IOException {
+        return null;
+    }
+
+    @Override
+    public ChapterInfo getChapterContent(@NonNull String url) throws IOException {
+        return null;
+    }
+}

+ 11 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/search/Baidu.java

@@ -0,0 +1,11 @@
+package top.yinxueqin.readbook.data.remote.search;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class Baidu {
+}

+ 12 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/search/Google.java

@@ -0,0 +1,12 @@
+package top.yinxueqin.readbook.data.remote.search;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class Google {
+
+}

+ 22 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/search/SearchWeb.java

@@ -0,0 +1,22 @@
+package top.yinxueqin.readbook.data.remote.search;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.SearchResult;
+
+/**
+ * Author: yin
+ * Time: 2017/12/14.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface SearchWeb {
+
+    /**
+     * 搜索结果
+     * @param key 搜索关键字
+     * @throws IOException IO异常
+     */
+    SearchResult searchResult(String key) throws IOException;
+}

+ 198 - 0
app/src/main/java/top/yinxueqin/readbook/data/remote/search/Shenma.java

@@ -0,0 +1,198 @@
+package top.yinxueqin.readbook.data.remote.search;
+
+import android.support.annotation.NonNull;
+import android.util.SparseArray;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.IOException;
+
+import top.yinxueqin.readbook.bean.SearchResult;
+import top.yinxueqin.readbook.utils.Constants;
+
+/**
+ * Author: yin
+ * Time: 2017/11/20.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 神马搜索
+ */
+
+public class Shenma implements SearchWeb{
+
+    private SparseArray<String> bookLinks = new SparseArray<>();
+    private SparseArray<String> bookNewestChapter = new SparseArray<>();
+    private SearchResult SearchResult = new SearchResult();
+    private int count = 0;
+
+
+    /**
+     * 搜索结果
+     * @param key 搜索关键字
+     * @throws IOException IO异常
+     */
+    @Override
+    @NonNull
+    public SearchResult searchResult(String key) throws IOException {
+        searchFirstPageResult(key);
+        for (int i = 2; i <= 4; i++) {
+            searchNextPageResult(key, i);
+        }
+        if (SearchResult != null) {
+            SearchResult.setBookLink(bookLinks);
+            SearchResult.setBookNewestChapter(bookNewestChapter);
+        }
+        return SearchResult;
+    }
+
+
+
+    /**
+     * 首页搜索结果
+     * @param key 搜索关键字
+     * @throws IOException IO异常
+     */
+    private void searchFirstPageResult(String key) throws IOException {
+
+        bookLinks.clear();
+        bookNewestChapter.clear();
+        count = 0;
+        //获取搜索文档
+        Document doc = Jsoup.connect("https://yz.m.sm.cn/s?q=" + key)
+                .header("User-Agent", Constants.USER_AGENT)
+                .get();
+        //取出搜索结果
+        Elements node1 = doc.select("div#results");
+        //第一个结果分析方式不同,单独拿出
+        Elements node2 = node1.select("div#sc_novel_filter_1_1");
+        Elements node3 = node2.select("div.card");
+        //取出书的网站地址
+        Element link = node3.select("div.nf-block")
+                .select("div.nf-info")
+                .select("div.nf-item")
+                .last();
+        String firstBookLink = link.select("a.nf-txt").attr("href");
+        //取出书的最新章节
+        Element chapter = node3.select("div.nf-charpter-block")
+                .select("div.nf-charpter-list")
+                .get(0);
+        String firstNewestChapter = chapter.select("p.x-flex-1").get(0).text();
+
+        if (firstBookLink != null && firstBookLink.length() > 0 &&
+                firstNewestChapter != null && firstNewestChapter.length() > 0 ) {
+            bookLinks.put(count, firstBookLink);
+            bookNewestChapter.put(count, firstNewestChapter);
+            count++;
+        }
+
+        //接下来的解析符合条件的搜索结果
+        Elements node4 = node1.select("div.sc_structure_web_novel");
+        for (Element element : node4) {
+            //取出小说主页连接
+            String bookLink = element.select("a.sc-title").attr("data-recoorig");
+            //取出不同布局的最新章节名
+            Elements block2 = element.select("section.swn-block-2");
+            Elements block3 = element.select("section.swn-block-3");
+            String newestChapter;
+            if (block2.size() > 0) {
+                newestChapter = block2.select("div.swn-main-left").select("div.swn-chapters-wraper")
+                        .select("a.swn-chapter").get(0).text();
+            } else if (block3.size() > 0) {
+                newestChapter = block3.select("div.swn-left").select("div.swn-div-bottom")
+                        .select("a").get(0).text();
+            } else {
+                continue;
+            }
+            if (bookLink != null && bookLink.length() > 0 &&
+                    newestChapter != null && newestChapter.length() > 0 ) {
+                bookLinks.put(count, bookLink);
+                bookNewestChapter.put(count, newestChapter);
+                count++;
+            }
+        }
+    }
+
+    /**
+     * 翻页
+     * @param key 搜索关键字
+     * @param page 页码
+     * @throws IOException IO异常
+     */
+    private void searchNextPageResult(String key, int page) throws IOException {
+        Document doc = Jsoup.connect("https://yz.m.sm.cn/s?q=" + key + "&by=next&layout=html&page=" + page)
+                .header("User-Agent", Constants.USER_AGENT)
+                .header("Referer", "https://yz.m.sm.cn/s?q=" + key)
+                .get();
+        //接下来的解析符合条件的搜索结果
+        Elements node4 = doc.select("div.sc_structure_web_novel");
+        for (Element element : node4) {
+            //取出小说主页连接
+            String bookLink = element.select("a.sc-title").attr("data-recoorig");
+            //取出不同布局的最新章节名
+            Elements block2 = element.select("section.swn-block-2");
+            Elements block3 = element.select("section.swn-block-3");
+            String newestChapter;
+            if (block2.size() > 0) {
+                newestChapter = block2.select("div.swn-main-left").select("div.swn-chapters-wraper")
+                        .select("a.swn-chapter").get(0).text();
+            } else if (block3.size() > 0) {
+                newestChapter = block3.select("div.swn-left").select("div.swn-div-bottom")
+                        .select("a").get(0).text();
+            } else {
+                continue;
+            }
+            if (bookLink != null && bookLink.length() > 0 &&
+                    newestChapter != null && newestChapter.length() > 0 ) {
+                bookLinks.put(count, bookLink);
+                bookNewestChapter.put(count, newestChapter);
+                count++;
+            }
+        }
+    }
+
+
+    /**
+     * 后3页结果
+     * @param key 搜索关键字
+     * @param page 页码
+     * @throws IOException IO异常
+     */
+    private void searchlastThreePageResult(String key, int page) throws IOException {
+        Document doc = Jsoup.connect("https://yz.m.sm.cn/s?q=" + key + "&by=next&layout=html&page=" + page)
+                .header("User-Agent", Constants.USER_AGENT)
+                .header("Referer", "https://yz.m.sm.cn/s?q=" + key)
+                .get();
+        //接下来的解析符合条件的搜索结果
+        Elements node4 = doc.select("div.sc_structure_web_novel");
+        for (Element element : node4) {
+            //取出小说主页连接
+            String bookLink = element.select("a.sc-title").attr("data-recoorig");
+            //取出不同布局的最新章节名
+            Elements block2 = element.select("section.swn-block-2");
+            Elements block3 = element.select("section.swn-block-3");
+            String newestChapter;
+            if (block2.size() > 0) {
+                newestChapter = block2.select("div.swn-main-left").select("div.swn-chapters-wraper")
+                        .select("a.swn-chapter").get(0).text();
+            } else if (block3.size() > 0) {
+                newestChapter = block3.select("div.swn-left").select("div.swn-div-bottom")
+                        .select("a").get(0).text();
+            } else {
+                continue;
+            }
+            if (bookLink != null && bookLink.length() > 0 &&
+                    newestChapter != null && newestChapter.length() > 0 ) {
+                bookLinks.put(count, bookLink);
+                bookNewestChapter.put(count, newestChapter);
+                count++;
+            }
+        }
+        page++;
+        if (page <= 4) {
+            searchlastThreePageResult(key, page);
+        }
+    }
+
+}

+ 42 - 0
app/src/main/java/top/yinxueqin/readbook/model/DBBookInfo.java

@@ -0,0 +1,42 @@
+package top.yinxueqin.readbook.model;
+
+import java.util.Date;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface DBBookInfo {
+
+    int getBookId();
+
+    String getBookName();
+
+    String getBookAuthor();
+
+    String getBookLink();
+
+    String getBookNewestChapterName();
+
+    String getBookImage();
+
+    String getBookUpdateNewestChapterTime();
+
+    String getBookReadChapter();
+
+    int getBookCatalogPosition();
+
+    int getBookReadPosition();
+
+    Date getBookReadbookTime();
+
+    boolean isHasNewestChapter();
+
+    int getBookshowRegional();
+
+    String getBookMD5();
+
+}

+ 25 - 0
app/src/main/java/top/yinxueqin/readbook/model/DBChapterInfo.java

@@ -0,0 +1,25 @@
+package top.yinxueqin.readbook.model;
+
+/**
+ * Author: yin
+ * Time: 2018/2/26.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface DBChapterInfo {
+
+    int getChapterId();
+
+    String getChapterName();
+
+    String getChapterLink();
+
+    boolean isChapterCache();
+
+    String getChatperMD5();
+
+    String getChapterContent();
+
+    int getBookInfoId();
+}

+ 42 - 0
app/src/main/java/top/yinxueqin/readbook/model/ReadSettingConfig.java

@@ -0,0 +1,42 @@
+package top.yinxueqin.readbook.model;
+
+/**
+ * Author: yin
+ * Time: 2018/3/2.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface ReadSettingConfig {
+
+    int getReadSettingId();
+
+    int getDisplayBrightness();
+
+    boolean isBrightnessFollowingsystem();
+
+    int getTextSize();
+
+    boolean isSimpleTransformation();
+
+    int getLineSpacingModel();
+
+    boolean isAutoPage();
+
+    boolean isAutoSteering();
+
+    int getBackgroundColor();
+
+    boolean isVolumeKeyPage();
+
+    boolean isFullScreenClickNextPage();
+
+    boolean isFlipAnimation();
+
+    boolean isFullScreenModel();
+
+    boolean isOpenScreenOffTime();
+
+    int getScreenOffTime();
+
+}

+ 16 - 0
app/src/main/java/top/yinxueqin/readbook/model/SearchHistory.java

@@ -0,0 +1,16 @@
+package top.yinxueqin.readbook.model;
+
+import java.util.Date;
+
+/**
+ * Author: yin
+ * Time: 2018/3/2.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface SearchHistory {
+    int getSearchId();
+    String getSearchName();
+    Date getSearchDate();
+}

+ 17 - 0
app/src/main/java/top/yinxueqin/readbook/model/SettingConfig.java

@@ -0,0 +1,17 @@
+package top.yinxueqin.readbook.model;
+
+/**
+ * Author: yin
+ * Time: 2018/3/2.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public interface SettingConfig {
+    int getSettingId();
+    int getThemeModel();
+    int getBookListSortModel();
+    boolean isUpdateNotice();
+    boolean isSaveTraffic();
+    int getSearchModel();
+}

+ 31 - 0
app/src/main/java/top/yinxueqin/readbook/ui/MainActivity.java

@@ -0,0 +1,31 @@
+package top.yinxueqin.readbook.ui;
+
+import android.os.Bundle;
+import android.widget.TextView;
+
+import top.yinxueqin.readbook.R;
+import top.yinxueqin.readbook.ui.activity.BaseActivity;
+
+public class MainActivity extends BaseActivity {
+
+    // Used to load the 'native-lib' library on application startup.
+    static {
+        System.loadLibrary("native-lib");
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        // Example of a call to a native method
+        TextView tv = (TextView) findViewById(R.id.sample_text);
+        tv.setText(stringFromJNI());
+    }
+
+    /**
+     * A native method that is implemented by the 'native-lib' native library,
+     * which is packaged with this application.
+     */
+    public native String stringFromJNI();
+}

+ 41 - 0
app/src/main/java/top/yinxueqin/readbook/ui/activity/BaseActivity.java

@@ -0,0 +1,41 @@
+package top.yinxueqin.readbook.ui.activity;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import top.yinxueqin.readbook.bean.ChapterInfo;
+import top.yinxueqin.readbook.utils.Constants;
+
+/**
+ * Author: yin
+ * Time: 2018/3/13.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class BaseActivity extends AppCompatActivity {
+
+    private BlockingQueue<ChapterInfo> chapterBlockingQueue = new LinkedBlockingQueue<>(20);
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    public boolean addChapterBlockingQueue(@NonNull ChapterInfo chapterInfo) throws InterruptedException {
+        return chapterBlockingQueue.offer(chapterInfo, Constants.CHAPTER_BLOCK_TIME_OUT, TimeUnit.SECONDS);
+    }
+
+    public ChapterInfo getChapterBlockingQueue() throws InterruptedException {
+        return chapterBlockingQueue.poll(Constants.CHAPTER_BLOCK_TIME_OUT, TimeUnit.SECONDS);
+    }
+
+
+
+}

+ 37 - 0
app/src/main/java/top/yinxueqin/readbook/utils/Constants.java

@@ -0,0 +1,37 @@
+package top.yinxueqin.readbook.utils;
+
+/**
+ * Author: yin
+ * Time: 2017/11/22.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: TODO
+ */
+
+public class Constants {
+    //Http请求头部
+
+    //User-Agent
+    public final static String USER_AGENT = "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-CN; MI 5s Build/MXB48T) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.7.8.958 Mobile Safari/537.36";
+
+    //选择的搜索引擎
+
+    //百度(m.baidu.com)
+    public final static int M_BAIDU_COM = 1;
+    //谷歌(www.google.com.hk)
+    public final static int WWW_GOOGLE_COM_HK = 2;
+    //神马(yz.m.sm.cn)
+    public final static int YZ_M_SM_CN = 3;
+
+    //选择的小说网站
+
+    //笔趣阁主站(m.qu.la)
+    public final static int M_QU_LA = 1;
+    //第九中文网(book.next9.net)
+    public final static int BOOK_NEXT9_NET = 2;
+
+    //缓冲队列数据加入缓冲时间,单位:秒
+    public final static Long CHAPTER_BLOCK_TIME_OUT = 30L;
+
+
+
+}

+ 42 - 0
app/src/main/java/top/yinxueqin/readbook/utils/ErrorCodeConstants.java

@@ -0,0 +1,42 @@
+package top.yinxueqin.readbook.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Author: Admin
+ * Time: 2017/12/31.
+ * Email: yinxueqin@rd.hgits.cn
+ * Effect: 错误码常量
+ */
+
+public class ErrorCodeConstants {
+
+    private static Map<Integer, String> errorMap;
+    //正常
+    public static final int RESULT_IS_GOOD = 1000;
+    //判读URL是否为空字符串
+    public static final int URL_ADDRESS_IS_NULL = 1001;
+    //不支持当前网站解析
+    public static final int THIS_SITE_IS_NOT_SUPPORTED = 1002;
+
+
+    //没有预定义此错误
+    public static final int UNDEFINED_ERROR = 2000;
+
+    public static String getErrorMessage(int errorCode) {
+        if ( errorMap.containsKey(errorCode) ) {
+            return errorMap.get(errorCode);
+        } else {
+            return errorMap.get(UNDEFINED_ERROR);
+        }
+    }
+
+    static {
+        errorMap = new HashMap<>();
+        errorMap.put(RESULT_IS_GOOD, "结果正常");
+        errorMap.put(URL_ADDRESS_IS_NULL, "URL地址为空");
+        errorMap.put(THIS_SITE_IS_NOT_SUPPORTED, "不支持当前网站解析");
+        errorMap.put(UNDEFINED_ERROR, "未定义错误");
+    }
+}

Файловите разлики са ограничени, защото са твърде много
+ 34 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#26A69A"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>

+ 19 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="top.yinxueqin.readbook.ui.MainActivity">
+
+    <TextView
+        android:id="@+id/sample_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 6 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">ReadBook</string>
+</resources>

+ 11 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>

+ 20 - 0
app/src/test/java/top/yinxueqin/readbook/ExampleUnitTest.java

@@ -0,0 +1,20 @@
+package top.yinxueqin.readbook;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws IOException {
+
+
+    }
+
+
+}

+ 27 - 0
build.gradle

@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.1.0'
+        
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 16 - 0
gradle.properties

@@ -0,0 +1,16 @@
+## Project-wide Gradle settings.
+#
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+#Wed Dec 13 16:56:13 CST 2017
+org.gradle.jvmargs=-Xmx1536m

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sun Apr 01 17:16:17 CST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

+ 160 - 0
gradlew

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 12 - 0
local.properties

@@ -0,0 +1,12 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Mon Nov 20 13:50:38 CST 2017
+ndk.dir=D\:\\android-sdk\\ndk-bundle
+sdk.dir=D\:\\android-sdk