Compare commits

..

2 Commits
master ... old

Author SHA1 Message Date
Z 2f55751ef0
Update README.md 4 years ago
Z b7207b761f
Update README.md 4 years ago
  1. 1
      .gitignore
  2. 3
      .idea/.gitignore
  3. 20
      .idea/codeStyles/Project.xml
  4. 6
      .idea/compiler.xml
  5. 7
      .idea/dictionaries/Administrator.xml
  6. 4
      .idea/encodings.xml
  7. 4
      .idea/gradle.xml
  8. 33
      .idea/jarRepositories.xml
  9. 86
      .idea/markdown-navigator.xml
  10. 3
      .idea/markdown-navigator/profiles_settings.xml
  11. 2
      .idea/misc.xml
  12. 12
      .idea/runConfigurations.xml
  13. 3
      .idea/vcs.xml
  14. 36
      README.md
  15. 156
      app/build.gradle
  16. 208
      app/proguard-rules.pro
  17. BIN
      app/reader.jks
  18. 18
      app/release/output-metadata.json
  19. BIN
      app/release/reader_app_1.0.0.apk
  20. 2
      app/src/androidTest/java/com/novel/read/ExampleInstrumentedTest.kt
  21. 111
      app/src/main/AndroidManifest.xml
  22. BIN
      app/src/main/assets/bg/羊皮纸1.jpg
  23. 62
      app/src/main/assets/defaultData/httpTTS.json
  24. 52
      app/src/main/assets/defaultData/readConfig.json
  25. 26
      app/src/main/assets/defaultData/themeConfig.json
  26. 128
      app/src/main/assets/defaultData/txtTocRule.json
  27. BIN
      app/src/main/assets/font/number.ttf
  28. 27
      app/src/main/assets/litepal.xml
  29. 138
      app/src/main/java/com/novel/read/App.kt
  30. 22
      app/src/main/java/com/novel/read/Ext.kt
  31. 205
      app/src/main/java/com/novel/read/activity/NovelBookDetailActivity.kt
  32. 89
      app/src/main/java/com/novel/read/activity/NovelBookTypeListActivity.kt
  33. 148
      app/src/main/java/com/novel/read/activity/NovelMainActivity.kt
  34. 91
      app/src/main/java/com/novel/read/activity/NovelRankListActivity.kt
  35. 655
      app/src/main/java/com/novel/read/activity/NovelReadActivity.kt
  36. 62
      app/src/main/java/com/novel/read/activity/NovelRecommendBookListActivity.kt
  37. 255
      app/src/main/java/com/novel/read/activity/NovelSearchActivity.kt
  38. 152
      app/src/main/java/com/novel/read/activity/NovelSettingActivity.kt
  39. 69
      app/src/main/java/com/novel/read/activity/NovelSplashActivity.kt
  40. 163
      app/src/main/java/com/novel/read/adapter/BookAdapter.kt
  41. 126
      app/src/main/java/com/novel/read/adapter/BookListAdapter.kt
  42. 34
      app/src/main/java/com/novel/read/adapter/CategoryAdapter.java
  43. 87
      app/src/main/java/com/novel/read/adapter/EasyAdapter.java
  44. 62
      app/src/main/java/com/novel/read/adapter/EditRecommendAdapter.kt
  45. 54
      app/src/main/java/com/novel/read/adapter/HistoryAdapter.kt
  46. 54
      app/src/main/java/com/novel/read/adapter/HotAdapter.kt
  47. 58
      app/src/main/java/com/novel/read/adapter/HumanAdapter.kt
  48. 15
      app/src/main/java/com/novel/read/adapter/IViewHolder.kt
  49. 59
      app/src/main/java/com/novel/read/adapter/LoveLyAdapter.kt
  50. 71
      app/src/main/java/com/novel/read/adapter/MarkAdapter.kt
  51. 54
      app/src/main/java/com/novel/read/adapter/PageStyleAdapter.kt
  52. 59
      app/src/main/java/com/novel/read/adapter/RankAdapter.kt
  53. 125
      app/src/main/java/com/novel/read/adapter/RankListAdapter.kt
  54. 173
      app/src/main/java/com/novel/read/adapter/SearchAdapter.kt
  55. 61
      app/src/main/java/com/novel/read/adapter/StackAdapter.kt
  56. 40
      app/src/main/java/com/novel/read/adapter/ViewHolderImpl.java
  57. 22
      app/src/main/java/com/novel/read/adapter/ViewPageAdapter.kt
  58. 50
      app/src/main/java/com/novel/read/adapter/holder/CategoryHolder.java
  59. 6
      app/src/main/java/com/novel/read/adapter/holder/EmptyHolder.kt
  60. 34
      app/src/main/java/com/novel/read/adapter/holder/MoreHolder.kt
  61. 178
      app/src/main/java/com/novel/read/base/BaseActivity.kt
  62. 41
      app/src/main/java/com/novel/read/base/BaseBindingAdapter.kt
  63. 58
      app/src/main/java/com/novel/read/base/BaseDialogFragment.kt
  64. 96
      app/src/main/java/com/novel/read/base/BaseFragment.kt
  65. 60
      app/src/main/java/com/novel/read/base/BasePreferenceFragment.kt
  66. 29
      app/src/main/java/com/novel/read/base/BaseService.kt
  67. 154
      app/src/main/java/com/novel/read/base/BaseViewModel.kt
  68. 63
      app/src/main/java/com/novel/read/base/MyApp.kt
  69. 78
      app/src/main/java/com/novel/read/base/NovelBaseActivity.kt
  70. 59
      app/src/main/java/com/novel/read/base/NovelBaseFragment.kt
  71. 15
      app/src/main/java/com/novel/read/base/VMBaseActivity.kt
  72. 9
      app/src/main/java/com/novel/read/base/VMBaseFragment.kt
  73. 73
      app/src/main/java/com/novel/read/constant/AppConst.kt
  74. 16
      app/src/main/java/com/novel/read/constant/AppPattern.kt
  75. 13
      app/src/main/java/com/novel/read/constant/BookType.kt
  76. 28
      app/src/main/java/com/novel/read/constant/EventBus.kt
  77. 30
      app/src/main/java/com/novel/read/constant/IntentAction.kt
  78. 23
      app/src/main/java/com/novel/read/constant/LayoutType.kt
  79. 69
      app/src/main/java/com/novel/read/constant/PreferKey.kt
  80. 7
      app/src/main/java/com/novel/read/constant/Status.kt
  81. 20
      app/src/main/java/com/novel/read/constant/Theme.kt
  82. 115
      app/src/main/java/com/novel/read/constants/Constant.kt
  83. 17
      app/src/main/java/com/novel/read/data/README.md
  84. 50
      app/src/main/java/com/novel/read/data/db/BookDao.kt
  85. 60
      app/src/main/java/com/novel/read/data/db/BookDatabase.kt
  86. 39
      app/src/main/java/com/novel/read/data/db/BookMarkDao.kt
  87. 48
      app/src/main/java/com/novel/read/data/db/ChapterDao.kt
  88. 23
      app/src/main/java/com/novel/read/data/db/ReadRecordDao.kt
  89. 19
      app/src/main/java/com/novel/read/data/db/SearchHistoryDao.kt
  90. 52
      app/src/main/java/com/novel/read/data/db/entity/Book.kt
  91. 19
      app/src/main/java/com/novel/read/data/db/entity/BookChapter.kt
  92. 16
      app/src/main/java/com/novel/read/data/db/entity/Bookmark.kt
  93. 10
      app/src/main/java/com/novel/read/data/db/entity/ChapterDetailEntity.kt
  94. 9
      app/src/main/java/com/novel/read/data/db/entity/HttpTTS.kt
  95. 15
      app/src/main/java/com/novel/read/data/db/entity/ReadRecord.kt
  96. 20
      app/src/main/java/com/novel/read/data/model/AppUpdateResp.kt
  97. 33
      app/src/main/java/com/novel/read/data/model/BookInfoResp.kt
  98. 52
      app/src/main/java/com/novel/read/data/model/BookListResp.kt
  99. 51
      app/src/main/java/com/novel/read/data/model/BoyEndRank.kt
  100. 50
      app/src/main/java/com/novel/read/data/model/BoyHotRank.kt
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -12,4 +12,3 @@
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties

3
.idea/.gitignore vendored

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

@ -1,26 +1,12 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions> <indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions> </indentOptions>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

@ -1,7 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="Administrator">
<words>
<w>umeng</w>
</words>
</dictionary>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

@ -4,6 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
@ -11,10 +12,11 @@
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/common_lib" />
<option value="$PROJECT_DIR$/net_serivce" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

@ -16,45 +16,20 @@
<option name="name" value="BintrayJCenter" /> <option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" /> <option name="url" value="https://jcenter.bintray.com/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="maven" /> <option name="id" value="maven" />
<option name="name" value="maven" /> <option name="name" value="maven" />
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" /> <option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="maven3" /> <option name="id" value="Google" />
<option name="name" value="maven3" /> <option name="name" value="Google" />
<option name="url" value="https://maven.google.com/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://maven.google.com/" />
</remote-repository> </remote-repository>
<remote-repository> <remote-repository>
<option name="id" value="maven2" /> <option name="id" value="maven2" />
<option name="name" value="maven2" /> <option name="name" value="maven2" />
<option name="url" value="https://jitpack.io" /> <option name="url" value="https://jitpack.io" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://dl.bintray.com/umsdk/release" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://dl.bintray.com/umsdk/release" />
</remote-repository>
</component> </component>
</project> </project>

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownProjectSettings" wasCopied="false">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="false" />
<option name="DEFINITIONS" value="false" />
<option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" />
<option name="HARDWRAPS" value="false" />
<option name="HTML_DEEP_PARSER" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="false" />
</PegdownExtensions>
<ParserOptions>
<option name="ADMONITION_EXT" value="false" />
<option name="ATTRIBUTES_EXT" value="false" />
<option name="COMMONMARK_LISTS" value="true" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="ENUMERATED_REFERENCES_EXT" value="false" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_LISTS" value="false" />
<option name="GITHUB_WIKI_LINKS" value="false" />
<option name="GITLAB_EXT" value="false" />
<option name="GITLAB_MATH_EXT" value="false" />
<option name="GITLAB_MERMAID_EXT" value="false" />
<option name="HEADER_ID_NON_ASCII_TO_LOWERCASE" value="false" />
<option name="HEADER_ID_NO_DUPED_DASHES" value="false" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="MACROS_EXT" value="false" />
<option name="NO_TEXT_ATTRIBUTES" value="false" />
<option name="PARSE_HTML_ANCHOR_ID" value="false" />
<option name="PLANTUML_FENCED_CODE" value="false" />
<option name="PUML_FENCED_CODE" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0" mathConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>

@ -0,0 +1,3 @@
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" plain-text-search-scope="Project Files" />
</component>

@ -10,7 +10,7 @@
<item index="0" class="java.lang.String" itemvalue="com.squareup.otto.Subscribe" /> <item index="0" class="java.lang.String" itemvalue="com.squareup.otto.Subscribe" />
</list> </list>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

@ -1,46 +1,39 @@
# Reader # Reader
一款开源小说阅读软件📕,借鉴任阅与阅读3.0,采用 Jetpack + 协程实现的 MVVM 架构。Kotlin+AndroidX编写,支持TTS听书、章节缓存、字体切换、繁简转换、黑夜模式、各种翻页(仿真、滚动、滑动、覆盖、无动画)、插页图片加载等。demo下载:[轻阅读.apk](https://github.com/390057892/reader/blob/master/app/release/reader_app_1.0.0.apk?raw=true) ## 注:此分支为旧版本,页面耦合性较高,重构后分支为master
- 有人换皮上了应用商店,为了避免这种情况,所以接口不再对外开放。大家只能看代码啦 一款在任阅基础上改进的网络小说阅读器📕,采用Kt编写,支持追书、看书。拥有语音朗读,仿真翻页等功能。相比原作者:
master分支对比old分支: - 重写代码逻辑,优化代码结构,Md设计风格
重写代码,优化阅读器代码结构,Md设计风格 + 使用郭神的LitePal框架存储
sql分层处理,MVVM架构 * 语音朗读、插画、国际化、黑夜模式等功能都进行了实现
语音朗读、插画、国际化、黑夜模式等功能都进行了实现 - 适配了安卓9.0,使用AndroidX控件,对刘海屏等异形屏幕有了很好的支持
使用AndroidX控件,对刘海屏等异形屏幕有了很好的支持 注: 之前项目采用Java编写,之后才转的Kotlin代码,可能有些替换不完善。欢迎大家提问题。本人一定尽快修复
注:
1.(主分支ip被墙了!出不来数据请自行开VPN,old的IP没被封但代码写的不太好)
2.想看java代码就去看old分支,old分支虽然也是kotlin,但old分支之前是用Java编写,之后用studio一键转的Kotlin代码,没有kotlin特有的语法糖之类 更容易懂。
3.欢迎大家提问题。本人一定尽快修复
## 使用技术 ## 使用技术
Kt、AndroidX、Retrofit、Okhttp3、Glide、LitePal、MVVM、协程 Kt、AndroidX、Retrofit、Okhttp3、Glide、LitePal等
## TIP ## TIP
- 当前书源为(自己爬的)轻小说书源,书中大多带了插图。 - 目前书源为轻小说书源,书中大多带了插图,因此屏蔽了tts语音朗读功能,之后会切换成正常小说书源并添加语音朗读功能
+ 服务器在美国,所以国内监管比较严的时候可能需要翻墙或切换网络去多尝试几次。(2021.2,主分支ip被墙了,必须开VPN了/(ㄒoㄒ)/~~) + 因为书籍为爬取的数据,服务器在美国,所以国内监管比较严的时候可能需要翻墙或切换网络去多尝试几次。
* 供大家学习使用。 * 同时请各位大神手下留情不要攻击或者爬取数据,供大家学习使用。之后会切换成网络源
## 项目截图 ## 项目截图
| <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%A6%96%E9%A1%B5.jpg?raw=true" width="280" alt="首页"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8E%A8%E8%8D%90.jpg?raw=true" width="280" alt="推荐"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E4%B9%A6%E5%BA%93.jpg?raw=true" width="280" alt="书库"/> | | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%A6%96%E9%A1%B5.jpg?raw=true" width="280" alt="首页"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8E%A8%E8%8D%90.jpg?raw=true" width="280" alt="推荐"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E4%B9%A6%E5%BA%93.jpg?raw=true" width="280" alt="书库"/> |
| --- | --- | --- | | --- | --- | --- |
| <img src="https://github.com/390057892/reader/blob/master/screenshot/%E8%AE%BE%E7%BD%AE.jpg?raw=true" width="280" alt="设置"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%90%9C%E7%B4%A2.jpg?raw=true" width="280" alt="搜索"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%98%85%E8%AF%BB%E9%A1%B5.jpg" width="280" alt="阅读页"/> | | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E8%AE%BE%E7%BD%AE.png?raw=true" width="280" alt="设置"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%90%9C%E7%B4%A2.png?raw=true" width="280" alt="搜索"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%98%85%E8%AF%BB%E9%A1%B5.png" width="280" alt="阅读页"/> |
| <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%98%85%E8%AF%BB%E9%A1%B5%E8%8F%9C%E5%8D%95.jpg" width="280" alt="菜单"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E4%B9%A6%E7%B1%8D%E7%9B%AE%E5%BD%95.jpg" width="280" alt="目录"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8F%92%E9%A1%B51.jpg" width="280" alt="插页"/> | | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E9%98%85%E8%AF%BB%E9%A1%B5%E8%8F%9C%E5%8D%95.png" width="280" alt="菜单"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E4%B9%A6%E7%B1%8D%E7%9B%AE%E5%BD%95.png" width="280" alt="目录"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8F%92%E9%A1%B51.jpg" width="280" alt="插页"/> |
| <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8F%92%E9%A1%B52.jpg" width="280" alt="插页2"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/night.jpg" width="280" alt="夜间"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/edit.jpg" width="280" alt="编辑"/> | | <img src="https://github.com/390057892/reader/blob/master/screenshot/%E6%8F%92%E9%A1%B52.jpg" width="280" alt="插页2"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/night.jpg" width="280" alt="夜间"/> | <img src="https://github.com/390057892/reader/blob/master/screenshot/edit.jpg" width="280" alt="编辑"/> |
## LICENSE ## LICENSE
``` ```
@ -59,4 +52,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
``` ```

@ -1,160 +1,38 @@
plugins { apply plugin: 'com.android.application'
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
def name = "reader" apply plugin: 'kotlin-android'
def code = 1
def version = "1.0.0"
android { apply plugin: 'kotlin-android-extensions'
compileSdkVersion 30
buildToolsVersion "29.0.3"
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig { defaultConfig {
applicationId "com.novel.read" applicationId rootProject.ext.android.applicationId
minSdkVersion 21 minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion 30 targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode code versionCode rootProject.ext.android.versionCode
versionName version versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "armeabi"
}
multiDexEnabled true
resConfigs "zh", "zh-rCN"
resConfigs "zh", "zh-rTW"
resConfigs "zh", "zh-rHK"
}
signingConfigs {
release {
keyAlias 'key0'
keyPassword '1qaz@WSX'
storeFile file('reader.jks')
storePassword '1qaz@WSX'
}
}
buildFeatures {
viewBinding true
} }
buildTypes { buildTypes {
debug {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
release { release {
buildConfigField "boolean", "LOG_DEBUG", "false" //log minifyEnabled false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
android.applicationVariants.all { variant ->
variant.outputs.all {
def flavor = variant.productFlavors[0].name
outputFileName = "${name}_${flavor}_${defaultConfig.versionName}.apk"
}
}
}
flavorDimensions "mode"
productFlavors {
app {
dimension "mode"
manifestPlaceholders = [APP_CHANNEL_VALUE: "app"]
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = '1.8'
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1' testImplementation rootProject.ext.dependencies["junit"]
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation rootProject.ext.dependencies["runner"]
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation rootProject.ext.dependencies["espresso_core"]
implementation "com.android.support:multidex:1.0.3" implementation project(path: ':net_serivce')
implementation project(path: ':common_lib')
//kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//androidX
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.media:media:1.2.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android:flexbox:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
//
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
//anko
def anko_version = '0.10.8'
implementation "org.jetbrains.anko:anko-sdk27:$anko_version"
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//sql
implementation 'org.litepal.android:kotlin:3.0.0'
//
def coroutines_version = '1.3.7'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
//liveEventBus
implementation 'com.jeremyliao:live-event-bus-x:1.7.2'
//lifecycle
def lifecycle_version = '2.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
//MarkDown
implementation 'io.noties.markwon:core:4.6.0'
implementation 'io.noties.markwon:image-glide:4.6.0'
//
implementation files('libs/ZHConverter.jar')
//brvah
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'
//
implementation 'net.ricecode:string-similarity:1.0.0'
//epub
implementation('com.positiondev.epublib:epublib-core:3.1') {
exclude group: 'org.slf4j'
exclude group: 'xmlpull'
}
//flex
implementation 'com.google.android:flexbox:1.1.0'
implementation 'com.allenliu.versionchecklib:library:2.2.0' implementation 'com.allenliu.versionchecklib:library:2.2.0'
implementation 'com.permissionx.guolindev:permissionx:1.4.0'
} }

@ -19,211 +19,3 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################
# 屏蔽错误Unresolved class name
#noinspection ShrinkerUnresolvedReference
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
# 保留androidx下的所有类及其内部类
-keep class androidx.** {*;}
# 保留继承的
-keep public class * extends androidx.**
# 保留R下面的资源
-keep class **.R$* {*;}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留在Activity中的方法参数是view的方法,
# 这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
# 保留枚举类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用
# 记得proguard-android.txt中一定不要加-dontoptimize才起作用
# 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# 保持js引擎调用的java类
-keep class **.analyzeRule.**{*;}
# 保持web类
-keep class **.web.**{*;}
#数据类
-keep class **.data.**{*;}
-dontwarn rx.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn org.apache.log4j.lf5.viewer.**
-dontnote org.apache.log4j.lf5.viewer.**
-dontwarn freemarker.**
-dontnote org.python.core.**
-dontwarn com.hwangjr.rxbus.**
-dontwarn okhttp3.**
-dontwarn org.conscrypt.**
-dontwarn com.jeremyliao.liveeventbus.**
-keep class com.google.gson.** { *; }
-keep class com.ke.gson.** { *; }
-keep class com.jeremyliao.liveeventbus.** { *; }
-keep class okhttp3.**{*;}
-keep class okio.**{*;}
-keep class com.hwangjr.rxbus.**{*;}
-keep class org.conscrypt.**{*;}
-keep class android.support.**{*;}
-keep class me.grantland.widget.**{*;}
-keep class de.hdodenhof.circleimageview.**{*;}
-keep class tyrant.explosionfield.**{*;}
-keep class tyrantgit.explosionfield.**{*;}
-keep class freemarker.**{*;}
-keep class com.gyf.barlibrary.** {*;}
-keep class org.slf4j.**{*;}
-dontwarn org.slf4j.**
-keep class org.codehaus.**{*;}
-dontwarn org.codehaus.**
-keep class com.jayway.**{*;}
-dontwarn com.jayway.**
-keep class com.fasterxml.**{*;}
-keep class javax.swing.**{*;}
-dontwarn javax.swing.**
-keep class java.awt.**{*;}
-dontwarn java.awt.**
-keep class sun.misc.**{*;}
-dontwarn sun.misc.**
-keep class sun.reflect.**{*;}
-dontwarn sun.reflect.**
## Rhino
-keep class javax.script.** { *; }
-keep class com.sun.script.javascript.** { *; }
-keep class org.mozilla.javascript.** { *; }
###EPUB
-dontwarn nl.siegmann.epublib.**
-dontwarn org.xmlpull.**
-keep class nl.siegmann.epublib.**{*;}
-keep class javax.xml.**{*;}
-keep class org.xmlpull.**{*;}
-keep class org.simpleframework.**{*;}
-dontwarn org.simpleframework.xml.**
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class org.litepal.** { *;}
-keep class * extends org.litepal.crud.DataSupport { *;}
-keep class * extends org.litepal.crud.LitePalSupport { *;}
-keep class com.allenliu.versionchecklib.**{*;}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class com.spreada.utils.chinese.**

Binary file not shown.

@ -1,18 +0,0 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.novel.read",
"variantName": "processAppReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "reader_app_1.0.0.apk"
}
]
}

Binary file not shown.

@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yjd.tuzibook", appContext.packageName) assertEquals("com.zlj.base_libaray", appContext.packageName)
} }
} }

@ -3,96 +3,71 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.novel.read"> package="com.novel.read">
<uses-permission
android:name="android.permission.MANAGE_DOCUMENTS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<application <application
android:name="com.novel.read.App" android:name=".base.MyApp"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/icon_read_book" android:icon="@mipmap/logo"
android:logo="@mipmap/logo"
android:label="@string/app_name" android:label="@string/app_name"
android:logo="@mipmap/icon_read_book" android:roundIcon="@mipmap/logo"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.Light" android:theme="@style/AppDartTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"> android:networkSecurityConfig="@xml/network_security_config"
<activity android:name="com.novel.read.ui.about.AboutActivity"/> tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.novel.read.ui.theme.ThemeActivity" /> <activity
<activity android:name="com.novel.read.ui.record.ReadRecordActivity" /> android:name="com.novel.read.activity.NovelRankListActivity"
<activity android:name="com.novel.read.ui.welcome.WelcomeActivity"> android:screenOrientation="portrait" />
<activity
android:name="com.novel.read.activity.NovelReadActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.novel.read.activity.NovelSearchActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.novel.read.activity.NovelSplashActivity"
android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:launchMode="singleTask"
android:resource="@xml/shortcuts" />
</activity> </activity>
<activity android:name="com.novel.read.ui.setting.SettingActivity" />
<activity
android:name="com.novel.read.ui.feedback.FeedBackActivity"
android:launchMode="singleTask" />
<activity
android:name="com.novel.read.ui.rank.RankActivity"
android:launchMode="singleTask" />
<activity
android:name="com.novel.read.ui.end.EndActivity"
android:launchMode="singleTask" />
<activity <activity
android:name="com.novel.read.ui.daily.DailyActivity" android:name="com.novel.read.activity.NovelRecommendBookListActivity"
android:launchMode="singleTask" /> android:screenOrientation="portrait" />
<activity <activity
android:name="com.novel.read.ui.channel.ChannelInfoActivity" android:name="com.novel.read.activity.NovelBookTypeListActivity"
android:launchMode="singleTask" /> android:screenOrientation="portrait" />
<activity <activity
android:name="com.novel.read.ui.channel.ChannelActivity" android:name="com.novel.read.activity.NovelBookDetailActivity"
android:launchMode="singleTask" android:screenOrientation="portrait" />
android:screenOrientation="behind" />
<activity android:name="com.novel.read.ui.info.BookInfoActivity" />
<activity <activity
android:name="com.novel.read.ui.search.SearchActivity" android:name="com.novel.read.activity.NovelSettingActivity"
android:launchMode="singleTask" /> android:screenOrientation="portrait" />
<activity <activity
android:name="com.novel.read.ui.read.ReadBookActivity" android:name="com.novel.read.activity.NovelMainActivity"
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout" android:screenOrientation="portrait" />
android:launchMode="singleTask" />
<activity
android:name="com.novel.read.ui.chapter.ChapterListActivity"
android:launchMode="singleTop"
android:screenOrientation="behind" />
<activity
android:name="com.novel.read.ui.MainActivity"
android:alwaysRetainTaskState="true"
android:launchMode="singleTask" />
<activity
android:name="com.novel.read.ui.main.bookshelf.arrange.ArrangeBookActivity"
android:launchMode="singleTask" />
<service android:name="com.novel.read.service.CacheBookService" /> <provider
<service android:name="com.novel.read.service.TTSReadAloudService" /> android:name="androidx.core.content.FileProvider"
<service android:name="com.novel.read.service.HttpReadAloudService" /> android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<meta-data <service android:name="com.novel.read.service.DownloadService" />
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-5528897088703176~2055995158" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

@ -1,62 +0,0 @@
[
{
"id": 1598233029304,
"name": "度小美",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=0&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029305,
"name": "度小宇",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=1&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029306,
"name": "度逍遥",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=5003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029307,
"name": "度丫丫",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=4&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029308,
"name": "度小娇",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=5&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029309,
"name": "度米朵",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029310,
"name": "度博文",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029311,
"name": "度小童",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=110&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029312,
"name": "度小萌",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=111&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029313,
"name": "百度骚男",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=11&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029314,
"name": "百度评书",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=6&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029315,
"name": "百度主持",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=9&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
}
]

@ -1,52 +0,0 @@
[
{
"bgStr": "#EFEFF7",
"bgStrNight": "#212021",
"textColor": "#383429",
"textColorNight": "#94969C",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": true,
"darkStatusIconNight": false
},
{
"bgStr": "#DDC090",
"bgStrNight": "#3C3F43",
"textColor": "#3E3422",
"textColorNight": "#DCDFE1",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": true,
"darkStatusIconNight": false
},
{
"bgStr": "#C2D8AA",
"bgStrNight": "#3C3F43",
"textColor": "#596C44",
"textColorNight": "#88C16F",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"darkStatusIconNight": false
},
{
"bgStr": "#DBB8E2",
"bgStrNight": "#3C3F43",
"textColor": "#68516C",
"textColorNight": "#F6AEAE",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"darkStatusIconNight": false
},
{
"bgStr": "#ABCEE0",
"bgStrNight": "#3C3F43",
"textColor": "#3D4C54",
"textColorNight": "#90BFF5",
"bgType": 0,
"bgTypeNight": 0,
"darkStatusIcon": false,
"darkStatusIconNight": false
}
]

@ -1,26 +0,0 @@
[
{
"themeName": "典雅蓝",
"isNightTheme": false,
"primaryColor": "#03A9F4",
"accentColor": "#AD1457",
"backgroundColor": "#F5F5F5",
"bottomBackground": "#EEEEEE"
},
{
"themeName": "黑白",
"isNightTheme": true,
"primaryColor": "#303030",
"accentColor": "#E0E0E0",
"backgroundColor": "#424242",
"bottomBackground": "#424242"
},
{
"themeName": "A屏黑",
"isNightTheme": true,
"primaryColor": "#000000",
"accentColor": "#FFFFFF",
"backgroundColor": "#000000",
"bottomBackground": "#000000"
}
]

@ -1,128 +0,0 @@
[
{
"id": -1,
"enable": true,
"name": "目录(去空白)",
"rule": "(?<=[ \\s])(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|篇(?!张))).{0,30}$",
"serialNumber": 0
},
{
"id": -2,
"enable": true,
"name": "目录",
"rule": "^[  \\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|篇(?!张))).{0,30}$",
"serialNumber": 1
},
{
"id": -3,
"enable": false,
"name": "目录(匹配简介)",
"rule": "(?<=[ \\s])(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": 2
},
{
"id": -4,
"enable": false,
"name": "目录(古典、轻小说备用)",
"rule": "^[  \\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|回(?![合来事去])|场(?![和合比电是])|话|篇(?!张))).{0,30}$",
"serialNumber": 3
},
{
"id": -5,
"enable": false,
"name": "数字(纯数字标题)",
"rule": "(?<=[ \\s])\\d+\\.?[  \\t]{0,4}$",
"serialNumber": 4
},
{
"id": -6,
"enable": true,
"name": "数字 分隔符 标题名称",
"rule": "^[  \\t]{0,4}\\d{1,5}[::,., 、_—\\-].{1,30}$",
"serialNumber": 5
},
{
"id": -7,
"enable": true,
"name": "大写数字 分隔符 标题名称",
"rule": "^[  \\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8})[ 、_—\\-].{1,30}$",
"serialNumber": 6
},
{
"id": -8,
"enable": true,
"name": "正文 标题/序号",
"rule": "^[  \\t]{0,4}正文[  ]{1,4}.{0,20}$",
"serialNumber": 7
},
{
"id": -9,
"enable": true,
"name": "Chapter/Section/Part/Episode 序号 标题",
"rule": "^[  \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|PART|[Nn][oO]\\.|[Ee]pisode|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)\\s{0,4}\\d{1,4}.{0,30}$",
"serialNumber": 8
},
{
"id": -10,
"enable": false,
"name": "Chapter(去简介)",
"rule": "^[  \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|PART|[Nn][Oo]\\.|[Ee]pisode)\\s{0,4}\\d{1,4}.{0,30}$",
"serialNumber": 9
},
{
"id": -11,
"enable": true,
"name": "特殊符号 序号 标题",
"rule": "(?<=[\\s ])[【〔〖「『〈[\\[](?:第|[Cc]hapter)[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节].{0,20}$",
"serialNumber": 10
},
{
"id": -12,
"enable": false,
"name": "特殊符号 标题(成对)",
"rule": "(?<=[\\s ]{0,4})(?:[\\[〈「『〖〔《(【\\(].{1,30}[\\)】)》〕〗』」〉\\]]?|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[  ]{0,4}$",
"serialNumber": 11
},
{
"id": -13,
"enable":true,
"name": "特殊符号 标题(单个)",
"rule": "(?<=[\\s ]{0,4})(?:[☆★✦✧].{1,30}|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[  ]{0,4}$",
"serialNumber": 12
},
{
"id": -14,
"enable": true,
"name": "章/卷 序号 标题",
"rule": "^[ \\t ]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[卷章][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8})[  ]{0,4}.{0,30}$",
"serialNumber": 13
},
{
"id": -15,
"enable":false,
"name": "顶格标题",
"rule": "^\\S.{1,20}$",
"serialNumber": 14
},
{
"id": -16,
"enable":false,
"name": "双标题(前向)",
"rule": "(?m)(?<=[ \\t ]{0,4})第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$(?=[\\s ]{0,8}第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章)",
"serialNumber": 15
},
{
"id": -17,
"enable":false,
"name": "双标题(后向)",
"rule": "(?m)(?<=[ \\t ]{0,4}第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$[\\s ]{0,8})第[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$",
"serialNumber": 16
},
{
"id":-18,
"enable": true,
"name": "标题 特殊符号 序号",
"rule": "^.{1,20}[((][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}[))][  \t]{0,4}$",
"serialNumber": 17
}
]

@ -1,12 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<litepal> <litepal>
<dbname value="light_book" /> <dbname value="readDb" />
<version value="1" />
<version value="9" />
<list> <list>
<mapping class="com.novel.read.data.db.entity.Book" /> <!--搜索记录表-->
<mapping class="com.novel.read.data.db.entity.BookChapter" /> <mapping class="com.novel.read.model.db.SearchListTable"/>
<mapping class="com.novel.read.data.db.entity.Bookmark" /> <!--收藏书籍表-->
<mapping class="com.novel.read.data.db.entity.ReadRecord" /> <mapping class="com.novel.read.model.db.CollBookBean"/>
<mapping class="com.novel.read.data.db.entity.SearchHistory" /> <!--章节表-->
<mapping class="com.novel.read.model.db.BookChapterBean"/>
<!--阅读记录表-->
<mapping class="com.novel.read.model.db.BookRecordBean"/>
<!--章节详情表-->
<mapping class="com.novel.read.model.db.ChapterInfoBean" />
<!--章节详情表-->
<mapping class="com.novel.read.model.db.DownloadTaskBean" />
<!--书签表-->
<mapping class="com.novel.read.model.db.BookSignTable" />
</list> </list>
<storage value="external" />
</litepal> </litepal>

@ -1,138 +0,0 @@
package com.novel.read
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import com.bumptech.glide.Glide
import com.jeremyliao.liveeventbus.LiveEventBus
import com.novel.read.constant.AppConst.channelIdDownload
import com.novel.read.constant.AppConst.channelIdReadAloud
import com.novel.read.constant.EventBus
import com.novel.read.data.db.BookDatabase
import com.novel.read.help.ActivityHelp
import com.novel.read.help.AppConfig
import com.novel.read.help.ReadBookConfig
import com.novel.read.help.ThemeConfig
import com.novel.read.utils.LanguageUtils
import com.novel.read.utils.ext.postEvent
import org.litepal.LitePal
@Suppress("DEPRECATION")
class App : MultiDexApplication() {
companion object {
@JvmStatic
lateinit var INSTANCE: App
private set
@JvmStatic
lateinit var db: BookDatabase
private set
lateinit var androidId: String
var versionCode = 0
var versionName = ""
}
override fun onCreate() {
super.onCreate()
INSTANCE = this
androidId = Settings.System.getString(contentResolver, Settings.Secure.ANDROID_ID)
LanguageUtils.setConfiguration(this)
LitePal.initialize(this)
db = BookDatabase.get()
packageManager.getPackageInfo(packageName, 0)?.let {
versionCode = it.versionCode
versionName = it.versionName
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
applyDayNight()
LiveEventBus
.config()
.lifecycleObserverAlwaysActive(true)
.autoClear(false)
registerActivityLifecycleCallbacks(ActivityHelp)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES,
Configuration.UI_MODE_NIGHT_NO -> applyDayNight()
}
}
fun applyDayNight() {
AppConfig.upEInkMode()
ReadBookConfig.upBg()
ThemeConfig.applyTheme(this)
initNightMode()
postEvent(EventBus.RECREATE, "")
}
private fun initNightMode() {
val targetMode =
if (AppConfig.isNightTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(targetMode)
}
/**
* 创建通知ID
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannelId() {
(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.let {
//用唯一的ID创建渠道对象
val downloadChannel = NotificationChannel(
channelIdDownload,
getString(R.string.action_download),
NotificationManager.IMPORTANCE_LOW
)
//初始化channel
downloadChannel.enableLights(false)
downloadChannel.enableVibration(false)
downloadChannel.setSound(null, null)
//用唯一的ID创建渠道对象
val readAloudChannel = NotificationChannel(
channelIdReadAloud,
getString(R.string.read_aloud),
NotificationManager.IMPORTANCE_LOW
)
//初始化channel
readAloudChannel.enableLights(false)
readAloudChannel.enableVibration(false)
readAloudChannel.setSound(null, null)
//向notification manager 提交channel
it.createNotificationChannels(listOf(downloadChannel, readAloudChannel))
}
}
override fun onLowMemory() {
super.onLowMemory()
Glide.get(this).clearMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level == TRIM_MEMORY_UI_HIDDEN) {
Glide.get(this).clearMemory()
}
Glide.get(this).trimMemory(level)
}
}

@ -0,0 +1,22 @@
package com.novel.read
import android.content.Context
import android.widget.ImageView
import android.widget.Toast
fun Context.showToast(msg:String){
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show()
}
fun Context.getScreenContentWidth(): Int {
val displayMetrics = this.resources.displayMetrics
return displayMetrics.widthPixels
}
fun Context.dp2px(dps: Int): Int {
return Math.round(dps.toFloat() * getDensityDpiScale(this))
}
fun getDensityDpiScale(context: Context): Float {
return context.resources.displayMetrics.xdpi / 160.0f
}

@ -0,0 +1,205 @@
package com.novel.read.activity
import android.annotation.SuppressLint
import android.app.ProgressDialog
import android.content.Intent
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.adapter.LoveLyAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.RequestCode.Companion.REQUEST_READ
import com.novel.read.constants.Constant.ResultCode.Companion.RESULT_IS_COLLECTED
import com.novel.read.event.BookArticleEvent
import com.novel.read.event.GetBookDetailEvent
import com.novel.read.event.GetRecommendBookEvent
import com.novel.read.event.UpdateBookEvent
import com.novel.read.http.AccountManager
import com.novel.read.model.db.CollBookBean
import com.novel.read.model.db.dbManage.BookRepository
import com.novel.read.model.protocol.RecommendBookResp
import com.novel.read.showToast
import com.novel.read.utlis.DateUtli
import com.novel.read.utlis.GlideImageLoader
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_book_detail.*
import org.litepal.LitePal
import java.util.*
class NovelBookDetailActivity(override val layoutId: Int = R.layout.activity_book_detail) : NovelBaseActivity(), View.OnClickListener {
private lateinit var mAdapter: LoveLyAdapter
private val mList = ArrayList<RecommendBookResp.BookBean>()
private var mBookId: Int = 0
private var isCollected = false
private var mCollBookBean: CollBookBean? = null
private lateinit var mProgressDialog: ProgressDialog
override fun initView() {
mBookId = intent.getIntExtra(Constant.Bundle.BookId, 0)
rlv_lovely.layoutManager = LinearLayoutManager(this)
mAdapter = LoveLyAdapter(mList)
rlv_lovely.adapter = mAdapter
mProgressDialog = ProgressDialog(this)
}
override fun initData() {
refresh.showLoading()
refresh.setOnReloadingListener { getData() }
getData()
toolbar.setNavigationOnClickListener { finish() }
tv_add_book.setOnClickListener(this)
tv_start_read.setOnClickListener(this)
}
private fun getData() {
AccountManager.getInstance().getRecommendBook(mBookId.toString(), "10")
AccountManager.getInstance().getBookDetail(mBookId.toString())
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.tv_add_book ->
//点击存储
if (isCollected) {
//放弃点击
BookRepository.getInstance().deleteCollBookInRx(mCollBookBean)
tv_add_book.text = resources.getString(R.string.add_book)
isCollected = false
} else {
mProgressDialog.setTitle("正在添加到书架中")
mProgressDialog.show()
AccountManager.getInstance().getBookArticle(mBookId.toString())
}
R.id.tv_start_read ->
startActivityForResult(
Intent(this, NovelReadActivity::class.java)
.putExtra(NovelReadActivity.EXTRA_IS_COLLECTED, isCollected)
.putExtra(NovelReadActivity.EXTRA_COLL_BOOK, mCollBookBean), REQUEST_READ
)
}
}
@SuppressLint("SetTextI18n")
@Subscribe
fun getBookDetail(event: GetBookDetailEvent) {
refresh.showFinish()
if (event.isFail) {
refresh.showError()
} else {
val bookBean = event.result!!.book
GlideImageLoader.displayCornerImage(this, bookBean.cover, iv_book)
tv_book_name.text = bookBean.title
tv_book_author.text = bookBean.author + " | "
tv_book_length.text = getString(R.string.book_word, bookBean.words / 10000)
if (event.result!!.last_article != null) {
tv_new_title.text =
getString(R.string.new_chapter, event.result!!.last_article.title)
tv_update_time.text =
DateUtli.dateConvert(event.result!!.last_article.create_time, 0)
}
tv_human_num.text = bookBean.hot.toString() + ""
tv_love_look_num.text = bookBean.like
tv_Intro.text = bookBean.description
mCollBookBean = BookRepository.getInstance().getCollBook(bookBean.id.toString())
//判断是否收藏
if (mCollBookBean != null) {
isCollected = true
tv_add_book.text = resources.getString(R.string.already_add)
tv_start_read.text = resources.getString(R.string.go_read)
} else {
mCollBookBean = event.result!!.collBookBean
}
}
}
@Subscribe
fun getRecommendBook(event: GetRecommendBookEvent) {
if (event.isFail) {
Log.e("getRecommendBook", event.er?.msg)
} else {
mList.clear()
mList.addAll(event.result!!.book)
mAdapter.notifyDataSetChanged()
}
}
override fun onResume() {
super.onResume()
EventManager.instance.registerSubscriber(this)
}
override fun onPause() {
super.onPause()
EventManager.instance.unregisterSubscriber(this)
}
@Subscribe
fun getArticle(event: BookArticleEvent) {
if (event.isFail) {
dismiss()
showToast(getString(R.string.net_error))
} else {
//存储收藏
var success = false
if (mCollBookBean != null) {
success = mCollBookBean!!.saveOrUpdate("bookId=?", mCollBookBean!!.id)
}
if (success) {
val bookChapterBean = event.result!!.chapterBean
for (i in bookChapterBean.indices) {
bookChapterBean[i].collBookBean = mCollBookBean
}
LitePal.saveAllAsync(bookChapterBean).listen { success1 ->
if (success1) {
if (tv_add_book != null) {
tv_add_book.text = resources.getString(R.string.already_add)
}
isCollected = true
} else {
LitePal.deleteAll(CollBookBean::class.java, "bookId =?", mCollBookBean!!.id)
showToast(getString(R.string.net_error))
}
dismiss()
}
} else {
showToast(getString(R.string.net_error))
dismiss()
}
}
}
private fun dismiss() {
mProgressDialog.dismiss()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
//如果进入阅读页面收藏了,页面结束的时候,就需要返回改变收藏按钮
if (requestCode == REQUEST_READ) {
if (data == null) {
return
}
isCollected = data.getBooleanExtra(RESULT_IS_COLLECTED, false)
if (isCollected) {
tv_add_book.text = resources.getString(R.string.already_add)
tv_start_read.text = resources.getString(R.string.go_read)
}
}
}
override fun onDestroy() {
super.onDestroy()
EventManager.instance.postEvent(UpdateBookEvent())
}
}

@ -0,0 +1,89 @@
package com.novel.read.activity
import androidx.recyclerview.widget.LinearLayoutManager
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.adapter.BookListAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.COMMENT_SIZE
import com.novel.read.event.SearchListEvent
import com.novel.read.http.AccountManager
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.protocol.SearchResp
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_book_type_list.*
import java.util.*
class NovelBookTypeListActivity(override val layoutId: Int = R.layout.activity_book_type_list) : NovelBaseActivity() {
private var mList: MutableList<SearchResp.BookBean> = ArrayList()
private lateinit var mAdapter: BookListAdapter
private var mCategoryId: String? = null
private var mTitle: String? = null
private var page: Int = 1
private var loadSize: Int = 0
override fun initView() {
EventManager.instance.registerSubscriber(this)
mCategoryId = intent.getStringExtra(Constant.Bundle.CategoryId)
mTitle = intent.getStringExtra(Constant.Bundle.mTitle)
rlv_type_list.layoutManager = LinearLayoutManager(this)
mAdapter = BookListAdapter(mList, rlv_type_list)
rlv_type_list.adapter = mAdapter
mAdapter.setOnLoadMoreListener(object : OnLoadMoreListener {
override fun onLoadMore() {
if (mAdapter.isLoadingMore) {
rlv_type_list.stopScroll()
} else {
if (loadSize >= COMMENT_SIZE) {
mAdapter.isLoadingMore = true
mAdapter.notifyDataSetChanged()
page++
getData()
}
}
}
})
}
override fun initData() {
refresh.showLoading()
refresh.setOnReloadingListener { this.getData() }
getData()
toolbar.title = mTitle
toolbar.setNavigationOnClickListener { finish() }
}
private fun getData() {
mCategoryId?.let { AccountManager.getInstance().getSearchBookList(it, "", page) }
}
@Subscribe
fun getSearchList(event: SearchListEvent) {
refresh.showFinish()
if (event.isFail) {
refresh.showError()
} else {
loadSize = event.result!!.book.size
if (mAdapter.isLoadingMore) {
mAdapter.isLoadingMore = false
mList.addAll(event.result!!.book)
mAdapter.notifyDataSetChanged()
} else {
mList.clear()
mList.addAll(event.result!!.book)
mAdapter.notifyDataSetChanged()
}
}
}
override fun onDestroy() {
super.onDestroy()
EventManager.instance.unregisterSubscriber(this)
}
}

@ -0,0 +1,148 @@
package com.novel.read.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.View
import androidx.fragment.app.Fragment
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.event.*
import com.novel.read.fragment.BookFragment
import com.novel.read.fragment.MoreFragment
import com.novel.read.fragment.RecommendFragment
import com.novel.read.fragment.StackFragment
import com.novel.read.http.AccountManager
import com.novel.read.model.db.dbManage.BookRepository
import com.novel.read.showToast
import com.novel.read.utlis.SpUtil
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_main.*
class NovelMainActivity(override val layoutId: Int = R.layout.activity_main) : NovelBaseActivity() {
private lateinit var mCurrentFrag: Fragment
private lateinit var mMainFragment: BookFragment
private lateinit var mRecommendFragment: RecommendFragment
private lateinit var mStackFragment: StackFragment
private lateinit var mMoreFragment: MoreFragment
//记录用户首次点击返回键的时间
private var firstTime: Long = 0
override fun initView() {
mCurrentFrag = Fragment()
mMainFragment = BookFragment.newInstance()
mRecommendFragment = RecommendFragment.newInstance()
mStackFragment = StackFragment.newInstance()
mMoreFragment = MoreFragment.newInstance()
AccountManager.getInstance().login(this)
}
override fun initData() {
bottom_bar.setOnNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.tab_one -> {
switchFragment(mMainFragment)
return@setOnNavigationItemSelectedListener true
}
R.id.tab_two -> {
switchFragment(mRecommendFragment)
return@setOnNavigationItemSelectedListener true
}
R.id.tab_three -> {
switchFragment(mStackFragment)
return@setOnNavigationItemSelectedListener true
}
R.id.tab_four -> {
switchFragment(mMoreFragment)
return@setOnNavigationItemSelectedListener true
}
}
false
}
if (BookRepository.getInstance().collBooks.size > 0) {
switchFragment(mMainFragment)
} else {
bottom_bar.selectedItemId = R.id.tab_two
}
}
private fun switchFragment(targetFragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
if (!targetFragment.isAdded) {
transaction.hide(mCurrentFrag)
transaction.add(R.id.fl_content, targetFragment, targetFragment.javaClass.name)
} else {
transaction.hide(mCurrentFrag).show(targetFragment)
}
mCurrentFrag = targetFragment
transaction.commit()
}
override fun onResume() {
super.onResume()
EventManager.instance.registerSubscriber(this)
}
override fun onPause() {
super.onPause()
EventManager.instance.unregisterSubscriber(this)
}
@Subscribe
fun login(event: LoginEvent) {
if (event.isFail) {
Log.e("NovelMainActivity", "login: " + event.er!!.msg)
} else {
SpUtil.setStringValue(Constant.Uid, event.result!!.uid.toString())
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
if (!isVisible(bottom_bar)) {
bottom_bar.visibility = View.VISIBLE
mMainFragment.updateBook(UpdateBookEvent())
} else {
val secondTime = System.currentTimeMillis()
if (secondTime - firstTime > 1000) {
firstTime = secondTime
showToast("再次点击退出界面")
} else {
finish()
}
}
return true
}
return super.onKeyUp(keyCode, event)
}
@Subscribe
fun setBottomBar(event: HideBottomBarEvent) {
if (event.result!!) {
bottom_bar.visibility = View.GONE
} else {
bottom_bar.visibility = View.VISIBLE
}
}
@Subscribe
fun toRecommendFragment(event: SwitchFragmentEvent) {
bottom_bar.selectedItemId = R.id.tab_two
}
companion object {
fun reStart(context: Context) {
val intent = Intent(context, NovelMainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
}
}

@ -0,0 +1,91 @@
package com.novel.read.activity
import androidx.recyclerview.widget.LinearLayoutManager
import com.novel.read.R
import com.novel.read.adapter.RankListAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.COMMENT_SIZE
import com.novel.read.http.AccountManager
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.protocol.RankByUpdateResp
import kotlinx.android.synthetic.main.activity_rank_list.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
/**
* 推荐fragment中点击更多跳转来的
*/
class NovelRankListActivity(override val layoutId: Int= R.layout.activity_rank_list) : NovelBaseActivity() {
private lateinit var mAdapter: RankListAdapter
private var mList: MutableList<RankByUpdateResp.BookBean> = ArrayList()
private var page = 1
private var loadSize: Int = 0
private var type: String = ""
private var sex: String = ""
override fun initView() {
rlv_book_list.layoutManager = LinearLayoutManager(this)
mAdapter = RankListAdapter(mList, rlv_book_list)
rlv_book_list.adapter = mAdapter
sex = intent.getStringExtra(Constant.Sex)
type = intent.getStringExtra(Constant.Type)
when (type) {
Constant.ListType.Human -> toolbar.title = getString(R.string.popular_selection)
Constant.ListType.EditRecommend -> toolbar.title = getString(R.string.edit_recommend)
Constant.ListType.HotSearch -> toolbar.title = getString(R.string.hot_search)
}
getData()
}
override fun initData() {
toolbar.setNavigationOnClickListener { finish() }
mAdapter.setOnLoadMoreListener(object : OnLoadMoreListener {
override fun onLoadMore() {
if (mAdapter.isLoadingMore) {
} else {
if (loadSize >= COMMENT_SIZE) {
mAdapter.isLoadingMore = true
mAdapter.notifyDataSetChanged()
page++
getData()
}
}
}
})
}
private fun getData() {
AccountManager.getInstance().getRankList(type, sex, Constant.DateTyp.Week, page.toString(), RankCallBack())
}
private inner class RankCallBack : Callback<RankByUpdateResp> {
override fun onResponse(call: Call<RankByUpdateResp>, response: Response<RankByUpdateResp>) {
if (response.isSuccessful) {
if (response.body() != null) {
loadSize = response.body()!!.book.size
if (mAdapter.isLoadingMore) {
mAdapter.isLoadingMore = false
mList.addAll(response.body()!!.book)
mAdapter.notifyDataSetChanged()
} else {
mList.clear()
mList.addAll(response.body()!!.book)
mAdapter.notifyDataSetChanged()
}
}
}
}
override fun onFailure(call: Call<RankByUpdateResp>, t: Throwable) {
}
}
}

@ -0,0 +1,655 @@
package com.novel.read.activity
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Service
import android.content.*
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Message
import android.util.Log
import android.view.KeyEvent
import android.view.View.*
import android.view.WindowManager
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.SeekBar
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.LinearLayoutManager
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.adapter.CategoryAdapter
import com.novel.read.adapter.MarkAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.ResultCode.Companion.RESULT_IS_COLLECTED
import com.novel.read.event.BookArticleEvent
import com.novel.read.event.ErrorChapterEvent
import com.novel.read.event.FinishChapterEvent
import com.novel.read.event.RxBus
import com.novel.read.http.AccountManager
import com.novel.read.model.db.BookChapterBean
import com.novel.read.model.db.BookSignTable
import com.novel.read.model.db.CollBookBean
import com.novel.read.model.db.DownloadTaskBean
import com.novel.read.model.db.dbManage.BookRepository
import com.novel.read.service.DownloadMessage
import com.novel.read.service.DownloadService
import com.novel.read.showToast
import com.novel.read.utlis.BrightnessUtils
import com.novel.read.utlis.ScreenUtils
import com.novel.read.utlis.SpUtil
import com.novel.read.utlis.SystemBarUtils
import com.novel.read.widget.dialog.ReadSettingDialog
import com.novel.read.widget.page.PageLoader
import com.novel.read.widget.page.PageView
import com.novel.read.widget.page.ReadSettingManager
import com.novel.read.widget.page.TxtChapter
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_read.*
import kotlinx.android.synthetic.main.layout_download.*
import kotlinx.android.synthetic.main.layout_light.*
import kotlinx.android.synthetic.main.layout_read_mark.*
import java.util.*
/**
* 阅读页📕
*/
class NovelReadActivity : NovelBaseActivity(), DownloadService.OnDownloadListener {
private lateinit var mCategoryAdapter: CategoryAdapter
private val mChapters = ArrayList<TxtChapter>()
private lateinit var mCurrentChapter: TxtChapter //当前章节
private var currentChapter = 0
private lateinit var mMarkAdapter: MarkAdapter
private val mMarks = ArrayList<BookSignTable>()
private lateinit var mPageLoader: PageLoader
private var mTopInAnim: Animation? = null
private var mTopOutAnim: Animation? = null
private var mBottomInAnim: Animation? = null
private var mBottomOutAnim: Animation? = null
private lateinit var mSettingDialog: ReadSettingDialog
private var isCollected = false // isFromSDCard
private var isNightMode = false
private var isFullScreen = false
private val isRegistered = false
private lateinit var mCollBook: CollBookBean
private lateinit var mBookId: String
@SuppressLint("HandlerLeak")
private val mHandler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
WHAT_CATEGORY -> rlv_list.setSelection(mPageLoader.chapterPos)
WHAT_CHAPTER -> mPageLoader.openChapter()
}
}
}
override val layoutId: Int get() = R.layout.activity_read
// 接收电池信息和时间更新的广播
private val mReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Objects.requireNonNull(intent.action) == Intent.ACTION_BATTERY_CHANGED) {
val level = intent.getIntExtra("level", 0)
mPageLoader.updateBattery(level)
} else if (intent.action == Intent.ACTION_TIME_TICK) {
mPageLoader.updateTime()
}// 监听分钟的变化
}
}
private var mService: DownloadService.IDownloadManager? = null
private var mConn: ServiceConnection? = null
override fun initView() {
EventManager.instance.registerSubscriber(this)
mCollBook = intent.getSerializableExtra(EXTRA_COLL_BOOK) as CollBookBean
isCollected = intent.getBooleanExtra(EXTRA_IS_COLLECTED, false)
mBookId = mCollBook.id
initService()
// 如果 API < 18 取消硬件加速
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
read_pv_page.setLayerType(LAYER_TYPE_SOFTWARE, null)
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
//获取页面加载器
mPageLoader = read_pv_page.getPageLoader(mCollBook)
mSettingDialog = ReadSettingDialog(this, mPageLoader)
//禁止滑动展示DrawerLayout
read_dl_slide.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
//侧边打开后,返回键能够起作用
read_dl_slide.isFocusableInTouchMode = false
//半透明化StatusBar
SystemBarUtils.transparentStatusBar(this)
//隐藏StatusBar
read_pv_page.post { this.hideSystemBar() }
read_abl_top_menu.setPadding(0, ScreenUtils.statusBarHeight, 0, 0)
ll_download.setPadding(0, ScreenUtils.statusBarHeight, 0, ScreenUtils.dpToPx(15))
val lp = window.attributes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lp.layoutInDisplayCutoutMode = 1
}
window.attributes = lp
//设置当前Activity的Brightness
if (ReadSettingManager.getInstance().isBrightnessAuto) {
BrightnessUtils.setDefaultBrightness(this)
} else {
BrightnessUtils.setBrightness(this, ReadSettingManager.getInstance().brightness)
}
//注册广播
val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
intentFilter.addAction(Intent.ACTION_TIME_TICK)
registerReceiver(mReceiver, intentFilter)
if (!SpUtil.getBooleanValue(Constant.BookGuide, false)) {
iv_guide.visibility = VISIBLE
toggleMenu(false)
}
if (isCollected) {
mPageLoader.collBook.bookChapters = BookRepository.getInstance().getBookChaptersInRx(mBookId)
// 刷新章节列表
mPageLoader.refreshChapterList()
// 如果是网络小说并被标记更新的,则从网络下载目录
if (mCollBook.isUpdate && !mCollBook.isLocal) {
AccountManager.getInstance().getBookArticle(mBookId)
}
} else {
AccountManager.getInstance().getBookArticle(mBookId)
}
}
override fun initData() {
tv_book_name.text = mCollBook.title
mCategoryAdapter = CategoryAdapter()
rlv_list.adapter = mCategoryAdapter
rlv_list.isFastScrollEnabled = true
rlv_mark.layoutManager = LinearLayoutManager(this)
mMarkAdapter = MarkAdapter(mMarks)
rlv_mark.adapter = mMarkAdapter
isNightMode = ReadSettingManager.getInstance().isNightMode
//夜间模式按钮的状态
toggleNightMode()
isFullScreen = ReadSettingManager.getInstance().isFullScreen
toolbar.setNavigationOnClickListener { finish() }
read_setting_sb_brightness.progress = ReadSettingManager.getInstance().brightness
mPageLoader.setOnPageChangeListener(
object : PageLoader.OnPageChangeListener {
override fun onChapterChange(pos: Int) {
var index: Int = pos
if (pos >= mChapters.size) {
index = mChapters.size - 1
}
mCategoryAdapter.setChapter(index)
mCurrentChapter = mChapters[index]
currentChapter = index
}
override fun requestChapters(requestChapters: List<TxtChapter>) {
AccountManager.getInstance().getBookArticleDetail(mBookId, requestChapters)
mHandler.sendEmptyMessage(WHAT_CATEGORY)
}
override fun onCategoryFinish(chapters: List<TxtChapter>) {
mChapters.clear()
mChapters.addAll(chapters)
mCategoryAdapter.refreshItems(mChapters)
}
override fun onPageCountChange(count: Int) {}
override fun onPageChange(pos: Int) {
}
}
)
read_pv_page.setTouchListener(object : PageView.TouchListener {
override fun onTouch(): Boolean {
return !hideReadMenu()
}
override fun center() {
toggleMenu(true)
}
override fun prePage() {}
override fun nextPage() {}
override fun cancel() {}
})
read_tv_category.setOnClickListener {
//移动到指定位置
if (mCategoryAdapter.count > 0) {
rlv_list.setSelection(mPageLoader.chapterPos)
}
//切换菜单
toggleMenu(true)
//打开侧滑动栏
read_dl_slide.openDrawer(GravityCompat.START)
}
tv_light.setOnClickListener {
ll_light.visibility = GONE
rlReadMark.visibility = GONE
if (isVisible(ll_light)) {
ll_light.visibility = GONE
} else {
ll_light.visibility = VISIBLE
}
}
tv_setting.setOnClickListener {
ll_light.visibility = GONE
rlReadMark.visibility = GONE
toggleMenu(false)
mSettingDialog.show()
}
read_setting_sb_brightness.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
val progress = seekBar.progress
//设置当前 Activity 的亮度
BrightnessUtils.setBrightness(this@NovelReadActivity, progress)
//存储亮度的进度条
ReadSettingManager.getInstance().brightness = progress
}
})
tvBookReadMode.setOnClickListener {
isNightMode = !isNightMode
mPageLoader.setNightMode(isNightMode)
toggleNightMode()
}
read_tv_brief.setOnClickListener {
val intent = Intent(this, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, Integer.valueOf(mBookId))
startActivity(intent)
}
read_tv_community.setOnClickListener {
if (isVisible(read_ll_bottom_menu)) {
if (isVisible(rlReadMark)) {
gone(rlReadMark)
} else {
gone(ll_light)
updateMark()
visible(rlReadMark)
}
}
}
tvAddMark.setOnClickListener {
mMarkAdapter.edit = false
if (BookRepository.getInstance().getSignById(mCurrentChapter.chapterId)) {
showToast(getString(R.string.sign_exist))
return@setOnClickListener
}
BookRepository.getInstance().addSign(mBookId, mCurrentChapter.chapterId, mCurrentChapter.title)
updateMark()
}
tvClear.setOnClickListener {
if (mMarkAdapter.edit) {
val sign = mMarkAdapter.selectList
if (sign != "") {
BookRepository.getInstance().deleteSign(sign)
updateMark()
}
mMarkAdapter.edit = false
} else {
mMarkAdapter.edit = true
}
}
tv_cache.setOnClickListener {
if (!isCollected) { //没有收藏 先收藏 然后弹框
//设置为已收藏
isCollected = true
//设置阅读时间
mCollBook.lastRead = System.currentTimeMillis().toString()
BookRepository.getInstance().saveCollBookWithAsync(mCollBook)
}
showDownLoadDialog()
}
rlv_list.setOnItemClickListener { _, _, position, _ ->
read_dl_slide.closeDrawer(GravityCompat.START)
mPageLoader.skipToChapter(position)
}
iv_guide.setOnClickListener {
iv_guide.visibility = GONE
SpUtil.setBooleanValue(Constant.BookGuide, true)
}
}
private fun showDownLoadDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.d_cache_num))
.setItems(
arrayOf(
getString(R.string.d_cache_last_50),
getString(R.string.d_cache_last_all),
getString(R.string.d_cache_all)
)
) { _, which ->
when (which) {
0 -> {
//50章
val last = currentChapter + 50
if (last > mCollBook.bookChapters.size) {
downLoadCache(mCollBook.bookChapters, mCollBook.bookChapters.size)
} else {
downLoadCache(mCollBook.bookChapters, last)
}
}
1 -> {
//后面所有
val lastBeans = ArrayList<BookChapterBean>()
for (i in currentChapter until mCollBook.bookChapters.size) {
lastBeans.add(mCollBook.bookChapters[i])
}
downLoadCache(lastBeans, mCollBook.bookChapters.size - currentChapter)
}
2 -> downLoadCache(mCollBook.bookChapters, mCollBook.bookChapters.size) //所有
else -> {
}
}
toggleMenu(true)
}
builder.show()
}
private fun downLoadCache(beans: List<BookChapterBean>, size: Int) {
val task = DownloadTaskBean()
task.taskName = mCollBook.title
task.bookId = mCollBook.id
task.bookChapters = beans //计算要缓存的章节
task.currentChapter = currentChapter
task.lastChapter = size
RxBus.getInstance().post(task)
startService(Intent(this, DownloadService::class.java))
}
private fun toggleNightMode() {
if (isNightMode) {
tvBookReadMode.text = resources.getString(R.string.book_read_mode_day)
val drawable = ContextCompat.getDrawable(this, R.drawable.ic_read_menu_moring)
tvBookReadMode.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null)
cl_layout.setBackgroundColor(ContextCompat.getColor(this, R.color.read_bg_night))
} else {
tvBookReadMode.text = resources.getString(R.string.book_read_mode_day)
val drawable = ContextCompat.getDrawable(this, R.drawable.ic_read_menu_night)
tvBookReadMode.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null)
cl_layout.setBackgroundColor(
ContextCompat.getColor(
this,
ReadSettingManager.getInstance().pageStyle.bgColor
)
)
}
}
/**
* 隐藏阅读界面的菜单显示
*
* @return 是否隐藏成功
*/
private fun hideReadMenu(): Boolean {
hideSystemBar()
if (read_abl_top_menu.visibility == VISIBLE) {
toggleMenu(true)
return true
} else if (mSettingDialog.isShowing) {
mSettingDialog.dismiss()
return true
}
return false
}
private fun showSystemBar() {
//显示
SystemBarUtils.showUnStableStatusBar(this)
if (isFullScreen) {
SystemBarUtils.showUnStableNavBar(this)
}
}
private fun hideSystemBar() {
//隐藏
SystemBarUtils.hideStableStatusBar(this)
if (isFullScreen) {
SystemBarUtils.hideStableNavBar(this)
}
}
/**
* 切换菜单栏的可视状态
* 默认是隐藏的
*/
private fun toggleMenu(hideStatusBar: Boolean) {
initMenuAnim()
gone(ll_light, rlReadMark)
if (read_abl_top_menu.visibility == VISIBLE) {
//关闭
read_abl_top_menu.startAnimation(mTopOutAnim)
read_ll_bottom_menu.startAnimation(mBottomOutAnim)
read_abl_top_menu.visibility = GONE
read_ll_bottom_menu.visibility = GONE
if (hideStatusBar) {
hideSystemBar()
}
} else {
read_abl_top_menu.visibility = VISIBLE
read_ll_bottom_menu.visibility = VISIBLE
read_abl_top_menu.startAnimation(mTopInAnim)
read_ll_bottom_menu.startAnimation(mBottomInAnim)
showSystemBar()
}
}
//初始化菜单动画
private fun initMenuAnim() {
if (mTopInAnim != null) return
mTopInAnim = AnimationUtils.loadAnimation(this, R.anim.slide_top_in)
mTopOutAnim = AnimationUtils.loadAnimation(this, R.anim.slide_top_out)
mBottomInAnim = AnimationUtils.loadAnimation(this, R.anim.slide_bottom_in)
mBottomOutAnim = AnimationUtils.loadAnimation(this, R.anim.slide_bottom_out)
//退出的速度要快
mTopOutAnim!!.duration = 200
mBottomOutAnim!!.duration = 200
}
@Subscribe
fun getBookArticle(event: BookArticleEvent) {
if (event.isFail) {
//获取章节失败处理
Log.e(TAG, event.er!!.msg)
} else {
val chapterBeans = event.result!!.chapterBean
mPageLoader.collBook.bookChapters = chapterBeans
mPageLoader.refreshChapterList()
// 如果是目录更新的情况,那么就需要存储更新数据
if (mCollBook.isUpdate && isCollected) {
BookRepository.getInstance().saveBookChaptersWithAsync(chapterBeans, mCollBook)
}
}
}
@Subscribe
fun finishChapter(event: FinishChapterEvent) {
if (mPageLoader.pageStatus == PageLoader.STATUS_LOADING) {
mHandler.sendEmptyMessage(WHAT_CHAPTER)
}
// 当完成章节的时候,刷新列表
mCategoryAdapter.notifyDataSetChanged()
}
@Subscribe
fun errorChapter(event: ErrorChapterEvent) {
if (mPageLoader.pageStatus == PageLoader.STATUS_LOADING) {
mPageLoader.chapterError()
}
}
private fun updateMark() {
mMarks.clear()
mMarks.addAll(BookRepository.getInstance().getSign(mBookId))
mMarkAdapter.notifyDataSetChanged()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> return mPageLoader.skipToPrePage()
KeyEvent.KEYCODE_VOLUME_DOWN -> return mPageLoader.skipToNextPage()
}
return super.onKeyDown(keyCode, event)
}
override fun onBackPressed() {
if (read_abl_top_menu.visibility == VISIBLE) {
// 非全屏下才收缩,全屏下直接退出
if (!ReadSettingManager.getInstance().isFullScreen) {
toggleMenu(true)
return
}
} else if (mSettingDialog.isShowing) {
mSettingDialog.dismiss()
return
} else if (read_dl_slide.isDrawerOpen(GravityCompat.START)) {
read_dl_slide.closeDrawer(GravityCompat.START)
return
}
Log.e(TAG, "onBackPressed: " + mCollBook.bookChapters.isEmpty())
if (!mCollBook.isLocal && !isCollected && mCollBook.bookChapters.isNotEmpty()) {
val alertDialog = AlertDialog.Builder(this)
.setTitle(getString(R.string.add_book))
.setMessage(getString(R.string.like_book))
.setPositiveButton(getString(R.string.sure)) { dialog, which ->
//设置为已收藏
isCollected = true
//设置阅读时间
mCollBook.lastRead = System.currentTimeMillis().toString()
BookRepository.getInstance().saveCollBookWithAsync(mCollBook)
exit()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, which -> exit() }.create()
alertDialog.show()
} else {
exit()
}
}
// 退出
private fun exit() {
// 返回给BookDetail。
val result = Intent()
result.putExtra(RESULT_IS_COLLECTED, isCollected)
setResult(Activity.RESULT_OK, result)
super.onBackPressed()
}
override fun onPause() {
super.onPause()
if (isCollected) {
mPageLoader.saveRecord()
}
}
override fun onDestroy() {
super.onDestroy()
EventManager.instance.unregisterSubscriber(this)
mPageLoader.closeBook()
unbindService(mConn)
unregisterReceiver(mReceiver)
}
private fun initService() {
mConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
mService = service as DownloadService.IDownloadManager
mService!!.setOnDownloadListener(this@NovelReadActivity)
}
override fun onServiceDisconnected(name: ComponentName) {}
}
//绑定
bindService(Intent(this, DownloadService::class.java), mConn, Service.BIND_AUTO_CREATE)
}
override fun onDownloadChange(pos: Int, status: Int, msg: String) {
Log.e(TAG, "onDownloadChange: $pos $status $msg")
if (msg == getString(R.string.download_success) || msg == getString(R.string.download_error)) {
//下载成功或失败后隐藏下载视图
if (ll_download != null) {
ll_download.visibility = GONE
showToast(msg)
}
} else {
if (ll_download != null) {
ll_download.visibility = VISIBLE
tv_progress.text = getString(
R.string.download_loading,
mService!!.downloadTaskList[pos].currentChapter,
mService!!.downloadTaskList[pos].lastChapter
)
pb_loading.max = mService!!.downloadTaskList[pos].lastChapter
pb_loading.progress = mService!!.downloadTaskList[pos].currentChapter
}
}
}
override fun onDownloadResponse(pos: Int, status: Int) {
Log.e(TAG, "onDownloadResponse: $pos $status")
}
@Subscribe
fun onDownLoadEvent(message: DownloadMessage) {
showToast(message.message)
}
companion object {
private const val TAG = "NovelReadActivity"
const val EXTRA_COLL_BOOK = "extra_coll_book"
const val EXTRA_IS_COLLECTED = "extra_is_collected"
private const val WHAT_CATEGORY = 1
private const val WHAT_CHAPTER = 2
}
}

@ -0,0 +1,62 @@
package com.novel.read.activity
import android.view.View
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.novel.read.R
import com.novel.read.adapter.ViewPageAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.fragment.BookListFragment
import com.novel.read.widget.VpTabLayout
import kotlinx.android.synthetic.main.activity_recommend_book_list.*
import java.util.*
class NovelRecommendBookListActivity(override val layoutId: Int= R.layout.activity_recommend_book_list) : NovelBaseActivity() {
override fun initView() {
val fragmentList = ArrayList<Fragment>()
val sex = intent.getStringExtra(Constant.Sex)
val type = intent.getStringExtra(Constant.Type)
when (type) {
Constant.ListType.Human -> toolbar.title = getString(R.string.popular_selection)
Constant.ListType.EditRecommend -> toolbar.title = getString(R.string.edit_recommend)
Constant.ListType.HotSearch -> toolbar.title = getString(R.string.hot_search)
}
val generalFragment = BookListFragment.newInstance(type, Constant.DateTyp.General, sex)
val monthFragment = BookListFragment.newInstance(type, Constant.DateTyp.Month, sex)
val weekFragment = BookListFragment.newInstance(type, Constant.DateTyp.Week, sex)
fragmentList.add(generalFragment)
fragmentList.add(monthFragment)
fragmentList.add(weekFragment)
val pageAdapter = ViewPageAdapter(supportFragmentManager, fragmentList)
vp_recommend_type.adapter = pageAdapter
vp_recommend_type.offscreenPageLimit = 2
vp_recommend_type.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
vp_tab.setAnim(position, vp_recommend_type)
}
override fun onPageScrollStateChanged(state: Int) {}
})
}
override fun initData() {
toolbar!!.setNavigationOnClickListener { finish() }
vp_tab.setOnTabBtnClickListener(object : VpTabLayout.OnTabClickListener {
override fun onTabBtnClick(var1: VpTabLayout.CommonTabBtn, var2: View) {
when (var1) {
VpTabLayout.CommonTabBtn.ONE -> vp_tab.setAnim(0, vp_recommend_type)
VpTabLayout.CommonTabBtn.TWO -> vp_tab.setAnim(1, vp_recommend_type)
VpTabLayout.CommonTabBtn.THREE -> vp_tab.setAnim(2, vp_recommend_type)
}
}
})
}
}

@ -0,0 +1,255 @@
package com.novel.read.activity
import android.content.DialogInterface
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.flexbox.AlignItems
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayoutManager
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.adapter.HistoryAdapter
import com.novel.read.adapter.HotAdapter
import com.novel.read.adapter.SearchAdapter
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant.COMMENT_SIZE
import com.novel.read.event.HotSearchEvent
import com.novel.read.event.SearchListEvent
import com.novel.read.http.AccountManager
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.db.SearchListTable
import com.novel.read.model.protocol.SearchResp
import com.novel.read.utlis.DialogUtils
import com.spreada.utils.chinese.ZHConverter
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_search.*
import kotlinx.android.synthetic.main.title_search.*
import org.litepal.LitePal
import java.util.*
class NovelSearchActivity(override val layoutId: Int= R.layout.activity_search) : NovelBaseActivity() {
private val mHotList = ArrayList<String>()
private lateinit var mHotAdapter: HotAdapter
private var mHisList: MutableList<SearchListTable> = ArrayList()
private lateinit var mHisAdapter: HistoryAdapter
private val mSearchList = ArrayList<SearchResp.BookBean>()
private lateinit var mSearchAdapter: SearchAdapter
private var page = 1
private var loadSize: Int = 0
override fun initView() {
EventManager.instance.registerSubscriber(this)
val manager = FlexboxLayoutManager(this)
//设置主轴排列方式
manager.flexDirection = FlexDirection.ROW
//设置是否换行
manager.flexWrap = FlexWrap.WRAP
manager.alignItems = AlignItems.STRETCH
rlv_hot.layoutManager = manager
mHotAdapter = HotAdapter(mHotList)
rlv_hot.adapter = mHotAdapter
mHisList = LitePal.order("saveTime desc").limit(5).find(SearchListTable::class.java)
val manager2 = FlexboxLayoutManager(this)
//设置主轴排列方式
manager2.flexDirection = FlexDirection.ROW
//设置是否换行
manager2.flexWrap = FlexWrap.WRAP
manager2.alignItems = AlignItems.STRETCH
mHisAdapter = HistoryAdapter(mHisList)
rlv_history.layoutManager = manager2
rlv_history.adapter = mHisAdapter
rlv_search.layoutManager = LinearLayoutManager(this)
mSearchAdapter = SearchAdapter(mSearchList, rlv_search)
rlv_search.adapter = mSearchAdapter
mSearchAdapter.setOnLoadMoreListener(object : OnLoadMoreListener {
override fun onLoadMore() {
if (mSearchAdapter.isLoadingMore) {
} else {
if (loadSize >= COMMENT_SIZE) {
mSearchAdapter.isLoadingMore = true
mSearchAdapter.notifyDataSetChanged()
page++
getData()
}
}
}
})
AccountManager.getInstance().getHotSearch()
}
private fun getData() {
val str = convertCC(tv_search.text.toString().trim { it <= ' ' })
AccountManager.getInstance().getSearchBookList("", str, page)
}
//繁簡轉換
fun convertCC(input: String): String {
return if (TextUtils.isEmpty(input) || input.isEmpty()) "" else ZHConverter.getInstance(
ZHConverter.SIMPLIFIED
).convert(input)
}
override fun initData() {
//输入框
tv_search.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.toString().trim { it <= ' ' } == "") {
refresh.visibility = View.GONE
head_hot.visibility = View.VISIBLE
head_history.visibility = View.VISIBLE
rlv_hot.visibility = View.VISIBLE
rlv_history.visibility = View.VISIBLE
} else {
refresh.visibility = View.VISIBLE
head_hot.visibility = View.GONE
head_history.visibility = View.GONE
rlv_hot.visibility = View.GONE
rlv_history.visibility = View.GONE
refresh.showLoading()
page = 1
getData()
}
}
override fun afterTextChanged(s: Editable) {
}
})
//键盘的搜索
tv_search.setOnKeyListener { v, keyCode, event ->
//修改回车键功能
if (keyCode == KeyEvent.KEYCODE_ENTER) {
mSearchAdapter.setHolderType(true)
saveKey()
return@setOnKeyListener true
}
false
}
mHotAdapter.setOnItemClickListener(object :HotAdapter.OnItemClickListener{
override fun onItemClick(view: View, pos: Int) {
mSearchAdapter.setHolderType(true)
refresh.visibility = View.VISIBLE
tv_search.setText(mHotList[pos])
saveKey()
}
})
mHisAdapter.setOnItemClickListener(object :HistoryAdapter.OnItemClickListener{
override fun onItemClick(view: View, pos: Int) {
mSearchAdapter.setHolderType(true)
refresh.visibility = View.VISIBLE
tv_search.setText(mHisList[pos].key)
saveKey()
}
})
mSearchAdapter.setOnItemClickListener(object :SearchAdapter.OnItemClickListener{
override fun onItemClick(view: View, pos: Int) {
mSearchAdapter.setHolderType(true)
tv_search.setText(mSearchList[pos].title)
saveKey()
}
})
head_history.setOnClickListener { view ->
DialogUtils.getInstance().showAlertDialog(
this,
getString(R.string.clear_search),
dialogListener = DialogInterface.OnClickListener { dialog, which ->
LitePal.deleteAll(SearchListTable::class.java)
mHisList.clear()
mHisList.addAll(LitePal.order("saveTime desc").limit(5).find(SearchListTable::class.java))
mHisAdapter.notifyDataSetChanged()
})
}
tv_cancel.setOnClickListener {
onBackPressed()
}
refresh.setOnReloadingListener { getData() }
}
private fun saveKey() {
if (tv_search.text.toString().trim { it <= ' ' } == "") {
return
}
val searchListTable = SearchListTable()
searchListTable.key = tv_search.text.toString().trim { it <= ' ' }
searchListTable.saveTime = System.currentTimeMillis()
searchListTable.saveOrUpdate("key=?", tv_search.text.toString().trim { it <= ' ' })
mHisList.clear()
mHisList.addAll(LitePal.order("saveTime desc").limit(5).find(SearchListTable::class.java))
mHisAdapter.notifyDataSetChanged()
}
override fun onBackPressed() {
if (refresh.visibility == View.VISIBLE) {
tv_search.setText("")
mSearchAdapter.setHolderType(false)
page = 1
} else {
super.onBackPressed()
overridePendingTransition(R.anim.message_fade_in, R.anim.message_fade_out)
}
}
@Subscribe
fun getHotSearch(event: HotSearchEvent) {
if (event.isFail) {
} else {
mHotList.clear()
mHotList.addAll(event.result!!.key)
mHotAdapter.notifyDataSetChanged()
}
}
@Subscribe
fun getSearchList(event: SearchListEvent) {
refresh.showFinish()
if (event.isFail) {
refresh.showError()
} else {
loadSize = event.result!!.book.size
if (mSearchAdapter.isLoadingMore) {
mSearchAdapter.isLoadingMore = false
mSearchList.addAll(event.result!!.book)
mSearchAdapter.notifyDataSetChanged()
} else {
mSearchList.clear()
mSearchList.addAll(event.result!!.book)
mSearchAdapter.notifyDataSetChanged()
}
}
}
override fun onDestroy() {
super.onDestroy()
EventManager.instance.unregisterSubscriber(this)
}
}

@ -0,0 +1,152 @@
package com.novel.read.activity
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.text.TextUtils
import android.view.View
import com.allenliu.versionchecklib.v2.AllenVersionChecker
import com.allenliu.versionchecklib.v2.builder.UIData
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.base.NovelBaseActivity
import com.novel.read.constants.Constant
import com.novel.read.event.UpdateBookEvent
import com.novel.read.event.VersionEvent
import com.novel.read.http.AccountManager
import com.novel.read.model.protocol.VersionResp
import com.novel.read.utlis.CleanCacheUtils
import com.novel.read.utlis.LocalManageUtil
import com.novel.read.utlis.SpUtil
import com.novel.read.utlis.VersionUtil
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_setting.*
class NovelSettingActivity(override val layoutId: Int = R.layout.activity_setting) : NovelBaseActivity(), View.OnClickListener {
private var resp: VersionResp? = null
override fun initView() {
EventManager.instance.registerSubscriber(this)
}
@SuppressLint("SetTextI18n")
override fun initData() {
tv_language.text =
resources.getStringArray(R.array.setting_dialog_language_choice)[SpUtil.getIntValue(
Constant.Language,
1
)]
tv_version.text = "V" + VersionUtil.getPackageName(this)!!
try {
val cacheSize =
CleanCacheUtils.getInstance().getTotalCacheSize(this@NovelSettingActivity)
tv_cache_num.text = cacheSize
} catch (e: Exception) {
e.printStackTrace()
}
toolbar.setNavigationOnClickListener { finish() }
AccountManager.getInstance().checkVersion(VersionUtil.getPackageCode(this))
ll_choose_language.setOnClickListener(this)
ll_clear_cache.setOnClickListener(this)
tv_check.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.ll_choose_language -> showLanguageDialog()
R.id.ll_clear_cache -> {
//默认不勾选清空书架列表,防手抖!!
val selected = booleanArrayOf(true, false)
AlertDialog.Builder(this)
.setTitle(getString(R.string.clear_cache))
.setCancelable(true)
.setMultiChoiceItems(arrayOf(getString(R.string.clear_cache), getString(R.string.clear_book)), selected) { _, which, isChecked -> selected[which] = isChecked }
.setPositiveButton(getString(R.string.sure)) { dialog, _ ->
Thread {
CleanCacheUtils.getInstance()
.clearCache(selected[0], selected[1], this@NovelSettingActivity)
var cacheSize = ""
try {
cacheSize =
CleanCacheUtils.getInstance()
.getTotalCacheSize(this@NovelSettingActivity)
} catch (e: Exception) {
e.printStackTrace()
}
val finalCacheSize = cacheSize
runOnUiThread {
EventManager.instance.postEvent(UpdateBookEvent())
tv_cache_num.text = finalCacheSize
}
}.start()
dialog.dismiss()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() }
.create().show()
}
R.id.tv_check ->
//版本大小不为空 去更新。
updateApk(resp!!)
}
}
private fun showLanguageDialog() {
AlertDialog.Builder(this)
.setTitle(getString(R.string.choose_language))
.setSingleChoiceItems(
resources.getStringArray(R.array.setting_dialog_language_choice),
SpUtil.getIntValue(Constant.Language, 1)
) { dialog, which ->
val language =
resources.getStringArray(R.array.setting_dialog_language_choice)[which]
tv_language.text = language
SpUtil.setIntValue(Constant.Language, which)
dialog.dismiss()
if (which == 0) {
selectLanguage(0)
} else {
selectLanguage(1)
}
}
.create().show()
}
private fun selectLanguage(select: Int) {
LocalManageUtil.saveSelectLanguage(this, select)
NovelMainActivity.reStart(this)
}
@Subscribe
fun checkVersion(event: VersionEvent) {
if (event.isFail) {
} else {
if (TextUtils.isEmpty(event.result!!.version.size)) {
return
}
resp = event.result
tv_check.visibility = View.VISIBLE
}
}
private fun updateApk(resp: VersionResp) {
val versionBean = resp.version
val builder = AllenVersionChecker
.getInstance()
.downloadOnly(
UIData.create()
.setTitle(getString(R.string.new_version, versionBean.version))
.setContent(versionBean.content)
.setDownloadUrl(versionBean.download)
)
builder.executeMission(this)
}
override fun onDestroy() {
super.onDestroy()
EventManager.instance.unregisterSubscriber(this)
}
}

@ -0,0 +1,69 @@
package com.novel.read.activity
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.novel.read.R
import com.novel.read.utlis.PermissionUtil
import com.novel.read.utlis.StatusBarUtil
import kotlinx.android.synthetic.main.activity_splash.*
class NovelSplashActivity : AppCompatActivity(), PermissionUtil.PermissionCallBack {
private var flag = false
private var runnable: Runnable? = null
private var mPermissionUtil: PermissionUtil = PermissionUtil.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StatusBarUtil.setBarsStyle(this, R.color.colorPrimary, true)
setContentView(R.layout.activity_splash)
mPermissionUtil.requestPermissions(this, PERMISSION_CODE, this)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
mPermissionUtil.requestResult(this, permissions, grantResults, this)
}
private fun init() {
runnable = Runnable { goHome() }
tvSkip.postDelayed(runnable, 2000)
tvSkip.setOnClickListener { goHome() }
}
@Synchronized
private fun goHome() {
if (!flag) {
flag = true
startActivity(Intent(this, NovelMainActivity::class.java))
finish()
}
}
override fun onDestroy() {
super.onDestroy()
flag = true
tvSkip.removeCallbacks(runnable)
}
override fun onPermissionSuccess() {
init()
}
override fun onPermissionReject(strMessage: String) {
finish()
}
override fun onPermissionFail() {
mPermissionUtil.requestPermissions(this, PERMISSION_CODE, this)
}
companion object {
private val PERMISSION_CODE = 999
}
}

@ -0,0 +1,163 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelReadActivity
import com.novel.read.model.db.CollBookBean
import com.novel.read.utlis.GlideImageLoader
import java.util.ArrayList
class BookAdapter(private val mList: List<CollBookBean>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var mContext: Context
private var mClickListener: OnItemClickListener? = null
private var mEdit: Boolean = false
val selectList: List<CollBookBean>
get() {
val collBookBeans = ArrayList<CollBookBean>()
for (i in mList.indices) {
if (mList[i].isSelect) {
collBookBeans.add(mList[i])
}
}
return collBookBeans
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
mContext = viewGroup.context
val view: View
if (i == VALUE_ITEM) {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_book, viewGroup, false)
return ViewHolder(view)
} else if (i == EMPTY_ITEM) {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_empty_add_book, viewGroup, false)
return EmptyHolder(view)
}
throw IllegalArgumentException()
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
if (mEdit) { //编辑模式
viewHolder.mIvCheck.isSelected = mList[i].isSelect
viewHolder.mIvCheck.visibility = View.VISIBLE
viewHolder.mTvBookName.text = mList[i].title
viewHolder.mTvBookAuthor.text = mList[i].author
if (mList[i].isUpdate) {
viewHolder.mIvGeng.visibility = View.VISIBLE
} else {
viewHolder.mIvGeng.visibility = View.GONE
}
GlideImageLoader.displayCornerImage(mContext, mList[i].cover, viewHolder.mIvBook)
viewHolder.itemView.setOnClickListener {
viewHolder.mIvCheck.isSelected = !mList[i].isSelect
mList[i].isSelect = !mList[i].isSelect
}
} else { //正常模式
viewHolder.mIvCheck.visibility = View.GONE
if (mList.size == i) { //最后的条目
viewHolder.mTvBookName.text = ""
viewHolder.mTvBookAuthor.text = ""
viewHolder.mIvBook.setImageResource(R.drawable.ic_book_add)
viewHolder.mIvGeng.visibility = View.GONE
viewHolder.itemView.setOnClickListener { view ->
mClickListener!!.onItemClick(
view,
i
)
}
} else {
viewHolder.mTvBookName.text = mList[i].title
viewHolder.mTvBookAuthor.text = mList[i].author
if (mList[i].isUpdate) {
viewHolder.mIvGeng.visibility = View.VISIBLE
} else {
viewHolder.mIvGeng.visibility = View.GONE
}
GlideImageLoader.displayCornerImage(
mContext,
mList[i].cover,
viewHolder.mIvBook
)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelReadActivity::class.java)
intent.putExtra(NovelReadActivity.EXTRA_IS_COLLECTED, true)
intent.putExtra(NovelReadActivity.EXTRA_COLL_BOOK, mList[i])
mContext.startActivity(intent)
}
}
}
} else if (viewHolder is EmptyHolder) { //空条目
viewHolder.mBtnAdd.setOnClickListener { view -> mClickListener!!.onItemClick(view, i) }
}
}
override fun getItemCount(): Int {
if (mList.isEmpty()) {
return 1
}
return if (mEdit) {
mList.size
} else {
mList.size + 1
}
}
override fun getItemViewType(position: Int): Int {
return if (mList.isEmpty()) {
EMPTY_ITEM
} else {
VALUE_ITEM
}
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvBook: ImageView = itemView.findViewById(R.id.iv_book)
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var mTvBookAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var mIvCheck: ImageView = itemView.findViewById(R.id.iv_check)
var mIvGeng: ImageView = itemView.findViewById(R.id.iv_geng)
}
internal class EmptyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mBtnAdd: Button = itemView.findViewById(R.id.btn_add)
}
fun setOnItemClickListener(mListener: OnItemClickListener) {
this.mClickListener = mListener
}
interface OnItemClickListener {
fun onItemClick(view: View, pos: Int)
}
fun setEdit(edit: Boolean) { //开启编辑模式
mEdit = edit
notifyDataSetChanged()
}
companion object {
private const val VALUE_ITEM = 100 //正常item
private const val EMPTY_ITEM = 101 //空白item
}
}

@ -0,0 +1,126 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.adapter.holder.EmptyHolder
import com.novel.read.adapter.holder.MoreHolder
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.COMMENT_SIZE
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.protocol.SearchResp
import com.novel.read.utlis.GlideImageLoader
class BookListAdapter(private val mList: List<SearchResp.BookBean>, recyclerView: RecyclerView) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mContext: Context? = null
var isLoadingMore: Boolean = false
private var lastVisibleItem: Int = 0
private var totalItemCount: Int = 0
private val visibleThreshold = 1
private var mOnLoadMoreListener: OnLoadMoreListener? = null
init {
if (recyclerView.layoutManager is LinearLayoutManager) {
val manager = recyclerView.layoutManager as LinearLayoutManager?
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
totalItemCount = manager!!.itemCount
lastVisibleItem = manager.findLastVisibleItemPosition()
if (!isLoadingMore && totalItemCount == lastVisibleItem + visibleThreshold && totalItemCount >= COMMENT_SIZE) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener!!.onLoadMore()
}
}
}
})
}
}
fun setOnLoadMoreListener(listener: OnLoadMoreListener) {
this.mOnLoadMoreListener = listener
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view: View
return when (viewType) {
VALUE_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_book_list, viewGroup, false)
ViewHolder(view)
}
EMPTY_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_empty_view, viewGroup, false)
EmptyHolder(view)
}
PROCESS_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.load_more_layout, viewGroup, false)
MoreHolder(view)
}
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
val bookBean = mList[i]
viewHolder.tvBookName.text = bookBean.title
viewHolder.tvBookAuthor.text = bookBean.author
viewHolder.tvBookDescription.text = bookBean.description
GlideImageLoader.displayCornerImage(mContext!!, bookBean.cover!!, viewHolder.ivBook)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, bookBean.id)
mContext!!.startActivity(intent)
}
}else if (viewHolder is MoreHolder){
viewHolder.bindModule(isLoadingMore)
}
}
override fun getItemCount(): Int {
return if (mList.isEmpty()) {
1
} else mList.size + 1
}
override fun getItemViewType(position: Int): Int {
if (mList.isNotEmpty() &&position == itemCount - 1) {
return PROCESS_ITEM
}
return when {
mList.isEmpty() -> EMPTY_ITEM
else -> VALUE_ITEM
}
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var ivBook: ImageView = itemView.findViewById(R.id.iv_book)
var tvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var tvBookAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var tvBookDescription: TextView = itemView.findViewById(R.id.tv_book_description)
}
companion object {
private const val VALUE_ITEM = 100 //正常item
private const val EMPTY_ITEM = 101 //空白item
private const val PROCESS_ITEM = 102
}
}

@ -0,0 +1,34 @@
package com.novel.read.adapter;
import android.view.View;
import android.view.ViewGroup;
import com.novel.read.adapter.holder.CategoryHolder;
import com.novel.read.widget.page.TxtChapter;
public class CategoryAdapter extends EasyAdapter<TxtChapter> {
private int currentSelected = 0;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
CategoryHolder holder = (CategoryHolder) view.getTag();
if (position == currentSelected){
holder.setSelectedChapter();
}
return view;
}
@Override
protected IViewHolder<TxtChapter> onCreateViewHolder(int viewType) {
return new CategoryHolder();
}
public void setChapter(int pos){
currentSelected = pos;
notifyDataSetChanged();
}
}

@ -0,0 +1,87 @@
package com.novel.read.adapter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class EasyAdapter<T> extends BaseAdapter {
private List<T> mList = new ArrayList<T>();
@Override
public int getCount() {
return mList.size();
}
@Override
public T getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public void addItem(T value){
mList.add(value);
notifyDataSetChanged();
}
public void addItem(int index,T value){
mList.add(index, value);
notifyDataSetChanged();
}
public void addItems(List<T> values){
mList.addAll(values);
notifyDataSetChanged();
}
public void removeItem(T value){
mList.remove(value);
notifyDataSetChanged();
}
public List<T> getItems(){
return Collections.unmodifiableList(mList);
}
public int getItemSize(){
return mList.size();
}
public void refreshItems(List<T> list){
mList.clear();
mList.addAll(list);
notifyDataSetChanged();
}
public void clear(){
mList.clear();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
IViewHolder holder;
if (convertView == null){
holder = onCreateViewHolder(getItemViewType(position));
convertView = holder.createItemView(parent);
convertView.setTag(holder);
//初始化
holder.initView();
}
else {
holder = (IViewHolder)convertView.getTag();
}
//执行绑定
holder.onBind(getItem(position),position);
return convertView;
}
protected abstract IViewHolder<T> onCreateViewHolder(int viewType);
}

@ -0,0 +1,62 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.constants.Constant
import com.novel.read.model.protocol.RecommendListResp
import com.novel.read.utlis.GlideImageLoader
/**
* create by zlj on 2019/6/19
* describe:
*/
class EditRecommendAdapter(private val mList: List<RecommendListResp.ListBean>) : RecyclerView.Adapter<EditRecommendAdapter.ViewHolder>() {
private var mContext: Context? = null
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view = LayoutInflater.from(mContext)
.inflate(R.layout.rlv_edit_recommend_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val listBean = mList[i]
viewHolder.mTvBookName.text = listBean.book_title
viewHolder.mTvAuthor.text = listBean.author
viewHolder.mTvDescription.text = listBean.description
viewHolder.mTvHumanNum.text = listBean.getHot()
viewHolder.mTvLoveNum.text = listBean.getLike()
GlideImageLoader.displayCornerImage(mContext!!, listBean.book_cover!!, viewHolder.mIvBook)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, listBean.book_id)
mContext!!.startActivity(intent)
}
}
override fun getItemCount(): Int {
return mList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvBook: ImageView = itemView.findViewById(R.id.iv_book)
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var mTvAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var mTvDescription: TextView = itemView.findViewById(R.id.tv_book_description)
var mTvHumanNum: TextView = itemView.findViewById(R.id.tv_human_num)
var mTvLoveNum: TextView = itemView.findViewById(R.id.tv_love_look_num)
}
}

@ -0,0 +1,54 @@
package com.novel.read.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.model.db.SearchListTable
/**
* create by zlj on 2019/6/17
* describe:
*/
class HistoryAdapter(private val mList: List<SearchListTable>) :
RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
private var mContext: Context? = null
private lateinit var mClickListener: OnItemClickListener
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTvLabel: TextView = itemView.findViewById(R.id.tv_label)
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view = LayoutInflater.from(mContext).inflate(R.layout.item_label, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
viewHolder.mTvLabel.text = mList[i].key
viewHolder.itemView.setOnClickListener { view -> mClickListener.onItemClick(view, i) }
}
override fun getItemCount(): Int {
return mList.size
}
fun setOnItemClickListener(mListener: OnItemClickListener) {
this.mClickListener = mListener
}
interface OnItemClickListener {
fun onItemClick(view: View, pos: Int)
}
}

@ -0,0 +1,54 @@
package com.novel.read.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.constants.Constant
/**
* create by 赵利君 on 2019/6/17
* describe:
*/
class HotAdapter(private val mList: List<String>) : RecyclerView.Adapter<HotAdapter.ViewHolder>() {
private var mContext: Context? = null
private lateinit var mClickListener: OnItemClickListener
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view = LayoutInflater.from(mContext).inflate(R.layout.item_label, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
viewHolder.mTvLabel.text = mList[i]
viewHolder.mTvLabel.setBackgroundColor(Constant.tagColors[i])
viewHolder.itemView.setOnClickListener { view -> mClickListener.onItemClick(view, i) }
}
override fun getItemCount(): Int {
return if (mList.size > 8) {
8
} else mList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTvLabel: TextView = itemView.findViewById(R.id.tv_label)
}
fun setOnItemClickListener(mListener: OnItemClickListener) {
this.mClickListener = mListener
}
interface OnItemClickListener {
fun onItemClick(view: View, pos: Int)
}
}

@ -0,0 +1,58 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.constants.Constant
import com.novel.read.dp2px
import com.novel.read.getScreenContentWidth
import com.novel.read.model.protocol.RecommendListResp
import com.novel.read.utlis.GlideImageLoader
import kotlinx.android.synthetic.main.rlv_human_item.view.*
/**
* create by zlj on 2019/6/19
* describe:
*/
class HumanAdapter(private val mList: List<RecommendListResp.ListBean>) : RecyclerView.Adapter<HumanAdapter.ViewHolder>() {
private lateinit var mContext: Context
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
mContext = viewGroup.context
val view = LayoutInflater.from(mContext).inflate(R.layout.rlv_human_item, viewGroup, false)
val ivSize = (mContext.getScreenContentWidth() - mContext.dp2px(10) * 4) / 3
view.cl_layout.layoutParams.width = ivSize
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
viewHolder.mTvBookName.text = mList[i].book_title
viewHolder.mTvAuthor.text = mList[i].author
GlideImageLoader.displayCornerImage(mContext, mList[i].book_cover!!, viewHolder.mIvBook)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, mList[i].book_id)
mContext.startActivity(intent)
}
}
override fun getItemCount(): Int {
return mList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvBook: ImageView = itemView.findViewById(R.id.iv_book)
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var mTvAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
}
}

@ -0,0 +1,15 @@
package com.novel.read.adapter
import android.view.View
import android.view.ViewGroup
/**
* Created by zlj
*/
interface IViewHolder<T> {
fun createItemView(parent: ViewGroup): View
fun initView()
fun onBind(data: T, pos: Int)
fun onClick()
}

@ -0,0 +1,59 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.constants.Constant
import com.novel.read.model.protocol.RecommendBookResp
import com.novel.read.utlis.GlideImageLoader
/**
* 猜你喜欢adapter
*/
class LoveLyAdapter(private val mList: List<RecommendBookResp.BookBean>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mContext: Context? = null
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view: View = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_lovely, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
val bookBean = mList[i]
GlideImageLoader.displayCornerImage(mContext!!, bookBean.cover!!, viewHolder.mIvBook)
viewHolder.mTvBookName.text = bookBean.title
viewHolder.mTvBookAuthor.text =
mContext!!.getString(R.string.author_zhu, bookBean.author)
viewHolder.mTvDescription.text = bookBean.description
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, bookBean.id)
mContext!!.startActivity(intent)
}
}
}
override fun getItemCount(): Int {
return mList.size
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvBook: ImageView = itemView.findViewById(R.id.iv_book)
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var mTvBookAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var mTvDescription: TextView = itemView.findViewById(R.id.tv_book_description)
}
}

@ -0,0 +1,71 @@
package com.novel.read.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.model.db.BookSignTable
import com.novel.read.model.protocol.MarkResp
class MarkAdapter(private val mList: List<BookSignTable>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mContext: Context? = null
var edit: Boolean = false
set(edit) {
field = edit
notifyDataSetChanged()
}
val selectList: String
get() {
val signs = StringBuilder()
for (i in mList.indices) {
if (mList[i].edit) {
if (signs.toString() == "") {
signs.append(mList[i].articleId)
} else {
signs.append(",").append(mList[i].articleId)
}
}
}
return signs.toString()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view: View = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_mark, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
if (this.edit) {
viewHolder.mCheck.visibility = View.VISIBLE
viewHolder.mCheck.setOnCheckedChangeListener { compoundButton, b ->
mList[i].edit = b
}
} else {
viewHolder.mCheck.visibility = View.GONE
}
viewHolder.mTvMark.text = mList[i].content
viewHolder.mCheck.isChecked = mList[i].edit
}
}
override fun getItemCount(): Int {
return mList.size
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTvMark: TextView = itemView.findViewById(R.id.tvMarkItem)
var mCheck: CheckBox = itemView.findViewById(R.id.checkbox)
}
}

@ -0,0 +1,54 @@
package com.novel.read.adapter
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.mango.mangolib.event.EventManager
import com.novel.read.R
import com.novel.read.widget.page.PageLoader
import com.novel.read.widget.page.PageStyle
class PageStyleAdapter(val mList: List<Drawable>, private val mPageLoader: PageLoader) :
RecyclerView.Adapter<PageStyleAdapter.PageHolder>() {
private var mContext: Context? = null
private var currentChecked: Int = 0
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PageHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view = LayoutInflater.from(mContext).inflate(R.layout.item_read_bg, viewGroup, false)
return PageHolder(view)
}
override fun onBindViewHolder(pageHolder: PageHolder, i: Int) {
pageHolder.mReadBg.background = mList[i]
pageHolder.mIvChecked.visibility = View.GONE
if (currentChecked == i) {
pageHolder.mIvChecked.visibility = View.VISIBLE
}
pageHolder.itemView.setOnClickListener {
currentChecked = i
notifyDataSetChanged()
mPageLoader.setPageStyle(PageStyle.values()[i])
}
}
fun setPageStyleChecked(pageStyle: PageStyle) {
currentChecked = pageStyle.ordinal
}
override fun getItemCount(): Int {
return mList.size
}
class PageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mReadBg: View = itemView.findViewById(R.id.read_bg_view)
val mIvChecked: ImageView = itemView.findViewById(R.id.read_bg_iv_checked)
}
}

@ -0,0 +1,59 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.constants.Constant
import com.novel.read.dp2px
import com.novel.read.getScreenContentWidth
import com.novel.read.model.protocol.RecommendListResp
import com.novel.read.utlis.GlideImageLoader
import kotlinx.android.synthetic.main.rlv_human_item.view.*
/**
* create by zlj on 2019/6/20
* describe:
*/
class RankAdapter(private val mList: List<RecommendListResp.ListBean>) :
RecyclerView.Adapter<RankAdapter.ViewHolder>() {
private lateinit var mContext: Context
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
mContext = viewGroup.context
val view = LayoutInflater.from(mContext).inflate(R.layout.rlv_human_item, viewGroup, false)
val ivSize = (mContext.getScreenContentWidth() - mContext.dp2px(10) * 4) / 3
view.cl_layout.layoutParams.width = ivSize
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
viewHolder.mTvBookName.text = mList[i].book_title
viewHolder.mTvAuthor.text = mList[i].author
GlideImageLoader.displayCornerImage(mContext, mList[i].book_cover!!, viewHolder.mIvBook)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, mList[i].book_id)
mContext.startActivity(intent)
}
}
override fun getItemCount(): Int {
return mList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvBook: ImageView = itemView.findViewById(R.id.iv_book)
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var mTvAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
}
}

@ -0,0 +1,125 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.adapter.holder.EmptyHolder
import com.novel.read.adapter.holder.MoreHolder
import com.novel.read.constants.Constant
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.protocol.RankByUpdateResp
import com.novel.read.utlis.GlideImageLoader
import com.novel.read.constants.Constant.COMMENT_SIZE
class RankListAdapter( val mList: List<RankByUpdateResp.BookBean>, recyclerView: RecyclerView) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var mContext: Context
var isLoadingMore: Boolean = false
private var lastVisibleItem: Int = 0
private var totalItemCount: Int = 0
private val visibleThreshold = 1
private var mOnLoadMoreListener: OnLoadMoreListener? = null
init {
if (recyclerView.layoutManager is LinearLayoutManager) {
val manager = recyclerView.layoutManager as LinearLayoutManager?
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
totalItemCount = manager!!.itemCount
lastVisibleItem = manager.findLastVisibleItemPosition()
if (!isLoadingMore && totalItemCount == lastVisibleItem + visibleThreshold && totalItemCount >= COMMENT_SIZE) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener!!.onLoadMore()
}
}
}
})
}
}
fun setOnLoadMoreListener(listener: OnLoadMoreListener) {
this.mOnLoadMoreListener = listener
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
mContext = viewGroup.context
val view: View
when (viewType) {
VALUE_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_book_list, viewGroup, false)
return ViewHolder(view)
}
EMPTY_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.rlv_empty_view, viewGroup, false)
return EmptyHolder(view)
}
PROCESS_ITEM -> {
view = LayoutInflater.from(mContext).inflate(R.layout.load_more_layout, viewGroup, false)
return MoreHolder(view)
}
}
throw IllegalArgumentException()
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
val bookBean = mList[i]
viewHolder.tvBookName.text = bookBean.title
viewHolder.tvBookAuthor.text = bookBean.author
viewHolder.tvBookDescription.text = bookBean.description
GlideImageLoader.displayCornerImage(mContext, bookBean.cover, viewHolder.ivBook)
viewHolder.itemView.setOnClickListener {
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, bookBean.id)
mContext.startActivity(intent)
}
}else if (viewHolder is MoreHolder){
viewHolder.bindModule(isLoadingMore)
}
}
override fun getItemCount(): Int {
return if (mList.isEmpty()) {
1
} else mList.size + 1
}
override fun getItemViewType(position: Int): Int {
if (mList.isNotEmpty() && position == itemCount - 1) {
return PROCESS_ITEM
}
return when {
mList.isEmpty() -> EMPTY_ITEM
else -> VALUE_ITEM
}
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var ivBook: ImageView = itemView.findViewById(R.id.iv_book)
var tvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var tvBookAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var tvBookDescription: TextView = itemView.findViewById(R.id.tv_book_description)
}
companion object {
private const val VALUE_ITEM = 100 //正常item
private const val EMPTY_ITEM = 101 //空白item
private const val PROCESS_ITEM = 102
}
}

@ -0,0 +1,173 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookDetailActivity
import com.novel.read.adapter.holder.EmptyHolder
import com.novel.read.adapter.holder.MoreHolder
import com.novel.read.constants.Constant
import com.novel.read.constants.Constant.COMMENT_SIZE
import com.novel.read.inter.OnLoadMoreListener
import com.novel.read.model.protocol.SearchResp
import com.novel.read.utlis.GlideImageLoader
class SearchAdapter(private val mList: List<SearchResp.BookBean>, recyclerView: RecyclerView) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mContext: Context? = null
private var book = false
private var mClickListener: OnItemClickListener? = null
var isLoadingMore: Boolean = false
private var lastVisibleItem: Int = 0
private var totalItemCount: Int = 0
private val visibleThreshold = 1
private var mOnLoadMoreListener: OnLoadMoreListener? = null
init {
if (recyclerView.layoutManager is LinearLayoutManager) {
val manager = recyclerView.layoutManager as LinearLayoutManager?
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
totalItemCount = manager!!.itemCount
lastVisibleItem = manager.findLastVisibleItemPosition()
if (!isLoadingMore && totalItemCount == lastVisibleItem + visibleThreshold && totalItemCount >= COMMENT_SIZE) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener!!.onLoadMore()
}
}
}
})
}
}
fun setOnLoadMoreListener(listener: OnLoadMoreListener) {
this.mOnLoadMoreListener = listener
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view: View
when (i) {
VALUE_ITEM -> {
view = LayoutInflater.from(mContext)
.inflate(R.layout.rlv_item_search, viewGroup, false)
return ViewHolder(view)
}
BOOK_ITEM -> {
view = LayoutInflater.from(mContext)
.inflate(R.layout.rlv_item_book_list_search, viewGroup, false)
return BookHolder(view)
}
EMPTY_ITEM -> {
view =
LayoutInflater.from(mContext).inflate(R.layout.rlv_empty_view, viewGroup, false)
return EmptyHolder(view)
}
PROCESS_ITEM -> {
view = LayoutInflater.from(mContext)
.inflate(R.layout.load_more_layout, viewGroup, false)
return MoreHolder(view)
}
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
when (viewHolder) {
is ViewHolder -> {
val bookBean = mList[i]
viewHolder.mTvBookName.text = bookBean.title
viewHolder.itemView.setOnClickListener { view ->
mClickListener!!.onItemClick(view, i)
}
}
is BookHolder -> {
val bookBean = mList[i]
viewHolder.tvBookName.text = bookBean.title
viewHolder.tvBookAuthor.text = bookBean.author
viewHolder.tvBookDescription.text = bookBean.description
GlideImageLoader.displayCornerImage(mContext!!, bookBean.cover!!, viewHolder.ivBook)
viewHolder.itemView.setOnClickListener { view ->
val intent = Intent(mContext, NovelBookDetailActivity::class.java)
intent.putExtra(Constant.Bundle.BookId, bookBean.id)
mContext!!.startActivity(intent)
}
}
is MoreHolder -> viewHolder.bindModule(isLoadingMore)
}
}
override fun getItemCount(): Int {
return if (mList.isEmpty()) {
1
} else mList.size + 1
}
override fun getItemViewType(position: Int): Int {
if (mList.isNotEmpty() && position == itemCount - 1) {
return PROCESS_ITEM
}
return if (mList.isEmpty()) {
EMPTY_ITEM
} else {
if (book) {
BOOK_ITEM
} else {
VALUE_ITEM
}
}
}
fun setHolderType(book: Boolean) {
this.book = book
notifyDataSetChanged()
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mTvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
}
internal class BookHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var ivBook: ImageView = itemView.findViewById(R.id.iv_book)
var tvBookName: TextView = itemView.findViewById(R.id.tv_book_name)
var tvBookAuthor: TextView = itemView.findViewById(R.id.tv_book_author)
var tvBookDescription: TextView = itemView.findViewById(R.id.tv_book_description)
}
fun setOnItemClickListener(mListener: OnItemClickListener) {
this.mClickListener = mListener
}
interface OnItemClickListener {
fun onItemClick(view: View, pos: Int)
}
companion object {
private const val VALUE_ITEM = 100 //正常item
private const val EMPTY_ITEM = 101 //空白item
private const val BOOK_ITEM = 102 //书本item
private const val PROCESS_ITEM = 103
}
}

@ -0,0 +1,61 @@
package com.novel.read.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
import com.novel.read.activity.NovelBookTypeListActivity
import com.novel.read.constants.Constant
import com.novel.read.model.protocol.CategoryTypeResp
import com.novel.read.utlis.GlideImageLoader
class StackAdapter(private val mList: List<CategoryTypeResp.CategoryBean>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mContext: Context? = null
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
if (mContext == null) {
mContext = viewGroup.context
}
val view: View = LayoutInflater.from(mContext).inflate(R.layout.rlv_item_book_type, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
if (viewHolder is ViewHolder) {
var input = mList[i].title
val regex = "(.{2})"
input = input.replace(regex.toRegex(), "$1\n")
viewHolder.mTvType.text = input
viewHolder.itemView.setOnClickListener { view ->
val intent = Intent(mContext, NovelBookTypeListActivity::class.java)
intent.putExtra(Constant.Bundle.CategoryId, mList[i].id.toString())
intent.putExtra(Constant.Bundle.mTitle, mList[i].title)
mContext!!.startActivity(intent)
}
GlideImageLoader.displayCornerImage(
mContext!!,
mList[i].cover,
viewHolder.mIvType,
R.drawable.ic_type_default
)
}
}
override fun getItemCount(): Int {
return mList.size
}
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mIvType: ImageView = itemView.findViewById(R.id.iv_book)
var mTvType: TextView = itemView.findViewById(R.id.tv_book_name)
}
}

@ -0,0 +1,40 @@
package com.novel.read.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.jetbrains.annotations.NotNull;
public abstract class ViewHolderImpl<T> implements IViewHolder<T> {
private View view;
private Context context;
protected abstract int getItemLayoutId();
@NotNull
@Override
public View createItemView(ViewGroup parent) {
view = LayoutInflater.from(parent.getContext())
.inflate(getItemLayoutId(), parent, false);
context = parent.getContext();
return view;
}
protected <V extends View> V findById(int id){
return (V) view.findViewById(id);
}
protected Context getContext(){
return context;
}
protected View getItemView(){
return view;
}
@Override
public void onClick() {
}
}

@ -0,0 +1,22 @@
package com.novel.read.adapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
/**
* Created by Administrator on 2017/2/24.
*/
class ViewPageAdapter(fm: FragmentManager, private val fragmentList: List<Fragment>) :
FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return fragmentList[position]
}
override fun getCount(): Int {
return fragmentList.size
}
}

@ -0,0 +1,50 @@
package com.novel.read.adapter.holder;
import android.graphics.drawable.Drawable;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.novel.read.R;
import com.novel.read.adapter.ViewHolderImpl;
import com.novel.read.model.db.dbManage.BookManager;
import com.novel.read.utlis.StringUtils;
import com.novel.read.widget.page.TxtChapter;
public class CategoryHolder extends ViewHolderImpl<TxtChapter> {
private TextView mTvChapter;
@Override
public void initView() {
mTvChapter = findById(R.id.category_tv_chapter);
}
@Override
public void onBind(TxtChapter value, int pos){
//首先判断是否该章已下载
Drawable drawable = null;
if (value.getBookId() != null && BookManager.isChapterCached(value.getBookId(),value.getTitle())){
drawable = ContextCompat.getDrawable(getContext(),R.drawable.selector_category_load);
}
else {
drawable = ContextCompat.getDrawable(getContext(), R.drawable.selector_category_unload);
}
mTvChapter.setSelected(false);
mTvChapter.setTextColor(ContextCompat.getColor(getContext(),R.color.colorTitle));
mTvChapter.setCompoundDrawablesWithIntrinsicBounds(drawable,null,null,null);
mTvChapter.setText(StringUtils.INSTANCE.convertCC(value.getTitle()));
}
@Override
protected int getItemLayoutId() {
return R.layout.rlv_item_category;
}
public void setSelectedChapter(){
mTvChapter.setTextColor(ContextCompat.getColor(getContext(),R.color.light_red));
mTvChapter.setSelected(true);
}
}

@ -0,0 +1,6 @@
package com.novel.read.adapter.holder
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class EmptyHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

@ -0,0 +1,34 @@
package com.novel.read.adapter.holder
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.novel.read.R
/**
* @author: LiJun 390057892@qq.com
* @date: 2018/4/4 9:28
*/
class MoreHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val mProgressBar: ProgressBar = itemView.findViewById(R.id.progressBar)
private val mTvName: TextView = itemView.findViewById(R.id.tv_name)
private val mTvEnd: TextView = itemView.findViewById(R.id.tv_end)
fun bindModule(loadMore: Boolean) {
if (loadMore) {
mTvName.visibility = View.VISIBLE
mProgressBar.visibility = View.VISIBLE
mTvEnd.visibility = View.GONE
} else {
mTvName.visibility = View.GONE
mProgressBar.visibility = View.GONE
mTvEnd.visibility = View.VISIBLE
}
}
}

@ -1,178 +0,0 @@
package com.novel.read.base
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.novel.read.R
import com.novel.read.constant.AppConst
import com.novel.read.constant.Theme
import com.novel.read.lib.*
import com.novel.read.ui.widget.TitleBar
import com.novel.read.utils.ColorUtils
import com.novel.read.utils.LanguageUtils
import com.novel.read.utils.ext.disableAutoFill
import com.novel.read.utils.ext.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
abstract class BaseActivity<VB : ViewBinding>(
val fullScreen: Boolean = true,
private val theme: Theme = Theme.Auto,
private val toolBarTheme: Theme = Theme.Auto,
private val transparent: Boolean = false
) : AppCompatActivity(),
CoroutineScope by MainScope() {
protected val binding: VB by lazy { getViewBinding() }
val isInMultiWindow: Boolean
get() {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
isInMultiWindowMode
} else {
false
}
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LanguageUtils.setConfiguration(newBase))
}
protected abstract fun getViewBinding(): VB
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
if (AppConst.menuViewNames.contains(name) && parent?.parent is FrameLayout) {
(parent.parent as View).setBackgroundColor(backgroundColor)
}
return super.onCreateView(parent, name, context, attrs)
}
override fun onCreate(savedInstanceState: Bundle?) {
window.decorView.disableAutoFill()
initTheme()
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupSystemBar()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
findViewById<TitleBar>(R.id.title_bar)
?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)
}
onActivityCreated(savedInstanceState)
observeLiveBus()
}
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration?) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
findViewById<TitleBar>(R.id.title_bar)
?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)
setupSystemBar()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
findViewById<TitleBar>(R.id.title_bar)
?.onMultiWindowModeChanged(isInMultiWindow, fullScreen)
setupSystemBar()
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
abstract fun onActivityCreated(savedInstanceState: Bundle?)
final override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return menu?.let {
val bool = onCompatCreateOptionsMenu(it)
it.applyTint(this, toolBarTheme)
bool
} ?: super.onCreateOptionsMenu(menu)
}
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.applyOpenTint(this)
return super.onMenuOpened(featureId, menu)
}
open fun onCompatCreateOptionsMenu(menu: Menu) = super.onCreateOptionsMenu(menu)
final override fun onOptionsItemSelected(item: MenuItem): Boolean {
item.let {
if (it.itemId == android.R.id.home) {
supportFinishAfterTransition()
return true
}
}
return onCompatOptionsItemSelected(item)
}
open fun onCompatOptionsItemSelected(item: MenuItem) = super.onOptionsItemSelected(item)
private fun initTheme() {
when (theme) {
Theme.Transparent -> setTheme(R.style.AppTheme_Transparent)
Theme.Dark -> {
setTheme(R.style.AppTheme_Dark)
ATH.applyBackgroundTint(window.decorView)
}
Theme.Light -> {
setTheme(R.style.AppTheme_Light)
ATH.applyBackgroundTint(window.decorView)
}
else -> {
if (ColorUtils.isColorLight(primaryColor)) {
setTheme(R.style.AppTheme_Light)
} else {
setTheme(R.style.AppTheme_Dark)
}
ATH.applyBackgroundTint(window.decorView)
}
}
}
private fun setupSystemBar() {
if (fullScreen && !isInMultiWindow) {
window.clearFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
ATH.setStatusBarColorAuto(this, fullScreen)
if (toolBarTheme == Theme.Dark) {
ATH.setLightStatusBar(window, false)
} else if (toolBarTheme == Theme.Light) {
ATH.setLightStatusBar(window, true)
}
upNavigationBarColor()
}
open fun upNavigationBarColor() {
ATH.setNavigationBarColorAuto(this)
}
open fun observeLiveBus() {
}
override fun finish() {
currentFocus?.hideSoftInput()
super.finish()
}
}

@ -1,41 +0,0 @@
package com.novel.read.base
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseSectionQuickAdapter
import com.chad.library.adapter.base.entity.SectionEntity
import com.chad.library.adapter.base.viewholder.BaseViewHolder
abstract class BaseBindingAdapter<T, VB : ViewBinding>(data: MutableList<T>? = null) :
BaseQuickAdapter<T, VBViewHolder<VB>>(0, data) {
abstract fun createViewBinding(inflater: LayoutInflater, parent: ViewGroup): VB
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VBViewHolder<VB> {
val viewBinding = createViewBinding(LayoutInflater.from(parent.context), parent)
return VBViewHolder(viewBinding, viewBinding.root)
}
}
class VBViewHolder<VB : ViewBinding>(val vb: VB, view: View) : BaseViewHolder(view)
@JvmName("bind")
fun <VB : ViewBinding> BaseViewHolder.withBinding(bind: (View) -> VB): BaseViewHolder =
BaseViewHolderWithBinding(bind(itemView))
@JvmName("getBinding")
@Suppress("UNCHECKED_CAST")
fun <VB : ViewBinding> BaseViewHolder.getViewBinding(): VB {
if (this is BaseViewHolderWithBinding<*>) {
return binding as VB
} else {
throw IllegalStateException("The binding could not be found.")
}
}
class BaseViewHolderWithBinding<VB : ViewBinding>(val binding: VB) : BaseViewHolder(binding.root)

@ -1,58 +0,0 @@
package com.novel.read.base
import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.novel.read.lib.ThemeStore
import com.novel.read.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext
abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var job: Job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.setBackgroundColor(ThemeStore.backgroundColor())
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun show(manager: FragmentManager, tag: String?) {
try {
//在每个add事务前增加一个remove事务,防止连续的add
manager.beginTransaction().remove(this).commit()
super.show(manager, tag)
} catch (e: Exception) {
//同一实例使用不同的tag会异常,这里捕获一下
e.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun <T> execute(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context) { block() }
open fun observeLiveBus() {
}
}

@ -1,96 +0,0 @@
package com.novel.read.base
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.os.Bundle
import android.view.*
import androidx.appcompat.view.SupportMenuInflater
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import com.novel.read.R
import com.novel.read.ui.widget.TitleBar
import com.novel.read.utils.ext.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate")
abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
CoroutineScope {
lateinit var job: Job
var supportToolbar: Toolbar? = null
private set
val menuInflater: MenuInflater
@SuppressLint("RestrictedApi")
get() = SupportMenuInflater(requireContext())
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
job = Job()
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onMultiWindowModeChanged()
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {
super.onMultiWindowModeChanged(isInMultiWindowMode)
onMultiWindowModeChanged()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
onMultiWindowModeChanged()
}
private fun onMultiWindowModeChanged() {
(activity as? BaseActivity<*>)?.let {
view?.findViewById<TitleBar>(R.id.title_bar)
?.onMultiWindowModeChanged(it.isInMultiWindow, it.fullScreen)
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun setSupportToolbar(toolbar: Toolbar) {
supportToolbar = toolbar
supportToolbar?.let {
it.menu.apply {
onCompatCreateOptionsMenu(this)
applyTint(requireContext())
}
it.setOnMenuItemClickListener { item ->
onCompatOptionsItemSelected(item)
true
}
}
}
open fun observeLiveBus() {
}
open fun onCompatCreateOptionsMenu(menu: Menu) {
}
open fun onCompatOptionsItemSelected(item: MenuItem) {
}
}

@ -1,60 +0,0 @@
package com.novel.read.base
import android.annotation.SuppressLint
import androidx.fragment.app.DialogFragment
import androidx.preference.*
import com.novel.read.ui.widget.prefs.EditTextPreferenceDialog
import com.novel.read.ui.widget.prefs.ListPreferenceDialog
import com.novel.read.ui.widget.prefs.MultiSelectListPreferenceDialog
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
private val dialogFragmentTag = "androidx.preference.PreferenceFragment.DIALOG"
@SuppressLint("RestrictedApi")
override fun onDisplayPreferenceDialog(preference: Preference) {
var handled = false
if (callbackFragment is OnPreferenceDisplayDialogCallback) {
handled =
(callbackFragment as OnPreferenceDisplayDialogCallback)
.onPreferenceDisplayDialog(this, preference)
}
if (!handled && activity is OnPreferenceDisplayDialogCallback) {
handled = (activity as OnPreferenceDisplayDialogCallback)
.onPreferenceDisplayDialog(this, preference)
}
if (handled) {
return
}
// check if dialog is already showing
if (parentFragmentManager.findFragmentByTag(dialogFragmentTag) != null) {
return
}
val f: DialogFragment = when (preference) {
is EditTextPreference -> {
EditTextPreferenceDialog.newInstance(preference.getKey())
}
is ListPreference -> {
ListPreferenceDialog.newInstance(preference.getKey())
}
is MultiSelectListPreference -> {
MultiSelectListPreferenceDialog.newInstance(preference.getKey())
}
else -> {
throw IllegalArgumentException(
"Cannot display dialog for an unknown Preference type: "
+ preference.javaClass.simpleName
+ ". Make sure to implement onPreferenceDisplayDialog() to handle "
+ "displaying a custom dialog for this Preference."
)
}
}
f.setTargetFragment(this, 0)
f.show(parentFragmentManager, dialogFragmentTag)
}
}

@ -1,26 +1,25 @@
package com.novel.read.base package com.novel.read.base
import android.app.Service import android.app.Service
import android.content.Intent
import com.novel.read.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
abstract class BaseService : Service(), CoroutineScope by MainScope() { import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
fun <T> execute( abstract class BaseService : Service() {
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context) { block() }
override fun onBind(intent: Intent?) = null private var mDisposable: CompositeDisposable? = null
protected fun addDisposable(disposable: Disposable) {
if (mDisposable == null) {
mDisposable = CompositeDisposable()
}
mDisposable!!.add(disposable)
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
cancel() if (mDisposable != null) {
mDisposable!!.dispose()
}
} }
} }

@ -1,154 +0,0 @@
package com.novel.read.base
import android.app.Application
import android.content.Context
import androidx.annotation.CallSuper
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.novel.read.App
import com.novel.read.R
import com.novel.read.network.api.ApiException
import com.novel.read.help.coroutine.Coroutine
import kotlinx.coroutines.*
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.toast
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import kotlin.coroutines.CoroutineContext
typealias Block<T> = suspend (CoroutineScope) -> T
typealias Error = suspend (Exception) -> Unit
typealias Cancel = suspend (Exception) -> Unit
open class BaseViewModel(application: Application) : AndroidViewModel(application),
CoroutineScope by MainScope(),
AnkoLogger {
val context: Context by lazy { this.getApplication<App>() }
fun <T> execute(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
): Coroutine<T> {
return Coroutine.async(scope, context) { block() }
}
fun <R> submit(
scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> Deferred<R>
): Coroutine<R> {
return Coroutine.async(scope, context) { block().await() }
}
@CallSuper
override fun onCleared() {
super.onCleared()
cancel()
}
open fun toast(message: Int) {
launch {
context.toast(message)
}
}
open fun toast(message: CharSequence?) {
launch {
context.toast(message ?: toString())
}
}
open fun longToast(message: Int) {
launch {
context.toast(message)
}
}
open fun longToast(message: CharSequence?) {
launch {
context.toast(message ?: toString())
}
}
/**
* 创建并执行协程
* @param block 协程中执行
* @param error 错误时执行
* @param cancel 取消时只需
* @param showErrorToast 是否弹出错误吐司
* @return Job
*/
protected fun launch(
block: Block<Unit>,
error: Error? = null,
cancel: Cancel? = null,
showErrorToast: Boolean = true
): Job {
return viewModelScope.launch {
try {
block.invoke(this)
} catch (e: Exception) {
when (e) {
is CancellationException -> {
cancel?.invoke(e)
}
else -> {
onError(e, showErrorToast)
error?.invoke(e)
}
}
}
}
}
/**
* 创建并执行协程
* @param block 协程中执行
* @return Deferred<T>
*/
protected fun <T> async(block: Block<T>): Deferred<T> {
return viewModelScope.async { block.invoke(this) }
}
/**
* 取消协程
* @param job 协程job
*/
protected fun cancelJob(job: Job?) {
if (job != null && job.isActive && !job.isCompleted && !job.isCancelled) {
job.cancel()
}
}
/**
* 统一处理错误
* @param e 异常
* @param showErrorToast 是否显示错误吐司
*/
private fun onError(e: Exception, showErrorToast: Boolean) {
when (e) {
is ApiException -> {
when (e.code) {
-1001 -> {
// 登录失效,清除用户信息、清除cookie/token
}
// 其他api错误
-1 -> if (showErrorToast) toast(e.message)
// 其他错误
else -> if (showErrorToast) toast(e.message)
}
}
// 网络请求失败
is ConnectException, is SocketTimeoutException, is UnknownHostException, is HttpException ->
if (showErrorToast) toast(R.string.network_request_failed)
// 其他错误
else ->
if (showErrorToast) toast(e.message ?: return)
}
}
}

@ -0,0 +1,63 @@
package com.novel.read.base
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import com.novel.read.constants.Constant
import com.novel.read.service.DownloadService
import com.novel.read.utlis.LocalManageUtil
import com.novel.read.utlis.SpUtil
import org.litepal.LitePal
import kotlin.properties.Delegates
/**
* create by zlj on 2019/6/10
*/
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
context = applicationContext
LitePal.initialize(this)
setNight()
LocalManageUtil.setApplicationLanguage(this)
startService(Intent(context, DownloadService::class.java))
}
private fun setNight() {
if (SpUtil.getBooleanValue(Constant.NIGHT, false)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
}
override fun attachBaseContext(base: Context) {
SpUtil.init(base)
//保存系统选择语言
LocalManageUtil.saveSystemCurrentLanguage(base)
super.attachBaseContext(LocalManageUtil.setLocal(base))
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
//保存系统选择语言
LocalManageUtil.onConfigurationChanged(applicationContext)
}
companion object {
var context: Context by Delegates.notNull()
private set
}
}

@ -0,0 +1,78 @@
package com.novel.read.base
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.novel.read.R
import com.novel.read.constants.Constant
import com.novel.read.utlis.LocalManageUtil
import com.novel.read.utlis.SpUtil
import com.novel.read.utlis.StatusBarUtil
/**
* create by 赵利君 on 2019/6/10
* describe:
*/
abstract class NovelBaseActivity : AppCompatActivity() {
private var mNowMode: Boolean = false
protected abstract val layoutId: Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StatusBarUtil.setBarsStyle(this, R.color.colorPrimary, true)
mNowMode = SpUtil.getBooleanValue(Constant.NIGHT)
setContentView(layoutId)
initView()
initData()
}
private fun setTheme() {
if (SpUtil.getBooleanValue(Constant.NIGHT) != mNowMode) {
if (SpUtil.getBooleanValue(Constant.NIGHT)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
recreate()
}
}
protected abstract fun initView()
protected abstract fun initData()
override fun onResume() {
super.onResume()
setTheme()
}
protected fun gone(vararg views: View) {
if (views.isNotEmpty()) {
for (view in views) {
view.visibility = View.GONE
}
}
}
protected fun visible(vararg views: View) {
if (views.isNotEmpty()) {
for (view in views) {
view.visibility = View.VISIBLE
}
}
}
protected fun isVisible(view: View): Boolean {
return view.visibility == View.VISIBLE
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocalManageUtil.setLocal(newBase))
}
}

@ -0,0 +1,59 @@
package com.novel.read.base
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
abstract class NovelBaseFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(getLayoutId(),null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
initData()
}
/**
* 加载布局
*/
@LayoutRes
abstract fun getLayoutId():Int
abstract fun initView()
abstract fun initData()
fun toActivity(toClsActivity: Class<*>) {
this.toActivity(toClsActivity, null as Bundle?)
}
fun toActivity(toClsActivity: Class<*>, bundle: Bundle?) {
val intent = Intent(this.context, toClsActivity)
if (bundle != null) {
intent.putExtras(bundle)
}
this.startActivity(intent)
}
fun toActivityForResult(toClsActivity: Class<*>, bundle: Bundle?, requestCode: Int) {
val intent = Intent(this.context, toClsActivity)
if (bundle != null) {
intent.putExtras(bundle)
}
startActivityForResult(intent, requestCode)
}
}

@ -1,15 +0,0 @@
package com.novel.read.base
import androidx.lifecycle.ViewModel
import androidx.viewbinding.ViewBinding
import com.novel.read.constant.Theme
abstract class VMBaseActivity<VB : ViewBinding,VM : ViewModel>(
fullScreen: Boolean = true,
theme: Theme = Theme.Auto,
toolBarTheme: Theme = Theme.Auto
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme) {
protected abstract val viewModel: VM
}

@ -1,9 +0,0 @@
package com.novel.read.base
import androidx.lifecycle.ViewModel
abstract class VMBaseFragment<VM : ViewModel>(layoutID: Int) : BaseFragment(layoutID) {
protected abstract val viewModel: VM
}

@ -1,73 +0,0 @@
package com.novel.read.constant
import android.annotation.SuppressLint
import android.graphics.Color
import java.text.SimpleDateFormat
@SuppressLint("SimpleDateFormat")
object AppConst {
const val APP_TAG = "TuZi"
const val AppId = "20210306161"
const val AppSecret = "c14f6a2b3dcah893a31b9i4f40f5cc08"
const val channelIdDownload = "channel_download"
const val channelIdReadAloud = "channel_read_aloud"
val timeFormat: SimpleDateFormat by lazy {
SimpleDateFormat("HH:mm")
}
val dateFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yyyy/MM/dd HH:mm")
}
val fileNameFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yy-MM-dd-HH-mm-ss")
}
val keyboardToolChars: List<String> by lazy {
arrayListOf(
"", "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\",
"$", "#", "!", ".", "href", "src", "textNodes", "xpath", "json", "css",
"id", "class", "tag"
)
}
const val notificationIdRead = 1144771
const val notificationIdDownload = 1144774
const val refresh = 1
const val loading = 2
const val complete = 3
const val error = 4
const val loadMore = 5
const val loadComplete = 6
const val loadMoreFail = 7
const val noMore = 8
@kotlin.jvm.JvmField
val tagColors = intArrayOf(
Color.parseColor("#90C5F0"),
Color.parseColor("#91CED5"),
Color.parseColor("#F88F55"),
Color.parseColor("#C0AFD0"),
Color.parseColor("#E78F8F"),
Color.parseColor("#67CCB7"),
Color.parseColor("#F6BC7E"),
Color.parseColor("#90C5F0"),
Color.parseColor("#91CED5")
)
const val home = 0
const val man = 1
const val woman = 2
const val shellName = "001"
val menuViewNames = arrayOf(
"com.android.internal.view.menu.ListMenuItemView",
"androidx.appcompat.view.menu.ListMenuItemView"
)
}

@ -1,16 +0,0 @@
package com.novel.read.constant
import java.util.regex.Pattern
object AppPattern {
val JS_PATTERN: Pattern =
Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE)
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
val imgPattern: Pattern =
Pattern.compile("<img .*?src.*?=.*?\"(.*?(?:,\\{.*\\})?)\".*?>", Pattern.CASE_INSENSITIVE)
val nameRegex = Regex("\\s+作\\s*者.*")
val authorRegex = Regex(".*?作\\s*?者[::]")
val fileNameRegex = Regex("[\\\\/:*?\"<>|.]")
val splitGroupRegex = Regex("[,;,;]")
}

@ -1,13 +0,0 @@
package com.novel.read.constant
object BookType {
const val default = 0 // 0 文本
const val audio = 1 // 1 音频
const val local = "loc_book"
const val net = "net_book"
const val serial="01"
const val stop="02"
const val complete="03"
}

@ -1,28 +0,0 @@
package com.novel.read.constant
object EventBus {
const val MEDIA_BUTTON = "mediaButton"
const val RECREATE = "RECREATE"
const val UP_BOOK = "upBookToc"
const val UPDATE_BOOK = "UPDATE_BOOK"
const val ALOUD_STATE = "aloud_state"
const val TTS_PROGRESS = "ttsStart"
const val TTS_DS = "ttsDs"
const val BATTERY_CHANGED = "batteryChanged"
const val TIME_CHANGED = "timeChanged"
const val UP_CONFIG = "upConfig"
const val OPEN_CHAPTER = "openChapter"
const val AUDIO_SUB_TITLE = "audioSubTitle"
const val AUDIO_STATE = "audioState"
const val AUDIO_PROGRESS = "audioProgress"
const val AUDIO_SIZE = "audioSize"
const val AUDIO_SPEED = "audioSpeed"
const val SHOW_RSS = "showRss"
const val SHOW_AD = "showAd"
const val WEB_SERVICE = "webService"
const val UP_DOWNLOAD = "upDownload"
const val SAVE_CONTENT = "saveContent"
const val CHECK_SOURCE = "checkSource"
const val CHECK_SOURCE_DONE = "checkSourceDone"
const val REPLACE_RULE_SAVE = "replaceRuleSave"
}

@ -1,30 +0,0 @@
package com.novel.read.constant
object IntentAction {
const val start = "start"
const val play = "play"
const val stop = "stop"
const val resume = "resume"
const val pause = "pause"
const val addTimer = "addTimer"
const val setTimer = "setTimer"
const val prevParagraph = "prevParagraph"
const val nextParagraph = "nextParagraph"
const val upTtsSpeechRate = "upTtsSpeechRate"
const val adjustProgress = "adjustProgress"
const val adjustSpeed = "adjustSpeed"
const val prev = "prev"
const val next = "next"
const val moveTo = "moveTo"
const val init = "init"
const val remove = "remove"
//page skip use
const val bookTypeId = "bookTypeId"
const val channelName = "channelName"
const val bookId = "bookId"
const val rankType = "rankType"
const val homeType = "homeType"
}

@ -1,23 +0,0 @@
package com.novel.read.constant
object LayoutType {
const val HOT = 1
const val CLICK = 2
const val RECOMMEND= 3
const val END = 4
const val BOY_HOT = 6
const val BOY_END = 7
const val BOY_SEARCH = 8
const val GIRL_HOT = 9
const val GIRL_END = 10
const val GIRL_SEARCH = 11
const val HEAD = 100
const val OTHER = 101
const val INIT_BOOK = 102
const val SEARCH_BOOK = 103
}

@ -1,69 +0,0 @@
package com.novel.read.constant
object PreferKey {
const val versionCode = "versionCode"
const val language = "language"
const val themeMode = "themeMode"
const val hideStatusBar = "hideStatusBar"
const val clickTurnPage = "clickTurnPage"
const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch"
const val speakEngine = "speakEngine"
const val readAloudByPage = "readAloudByPage"
const val ttsSpeechRate = "ttsSpeechRate"
const val prevKeys = "prevKeyCodes"
const val nextKeys = "nextKeyCodes"
const val bookshelfLayout = "bookshelfLayout"
const val bookshelfSort = "bookshelfSort"
const val recordLog = "recordLog"
const val processText = "process_text"
const val cleanCache = "cleanCache"
const val saveTabPosition = "saveTabPosition"
const val fontFolder = "fontFolder"
const val backupPath = "backupUri"
const val restoreIgnore = "restoreIgnore"
const val threadCount = "threadCount"
const val webPort = "webPort"
const val keepLight = "keep_light"
const val webService = "webService"
const val webDavUrl = "web_dav_url"
const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password"
const val webDavCreateDir = "webDavCreateDir"
const val webDavCacheBackup = "webDavCacheBackup"
const val changeSourceLoadToc = "changeSourceLoadToc"
const val changeSourceLoadInfo = "changeSourceLoadInfo"
const val chineseConverterType = "chineseConverterType"
const val launcherIcon = "launcherIcon"
const val textSelectAble = "selectText"
const val lastBackup = "lastBackup"
const val shareLayout = "shareLayout"
const val readStyleSelect = "readStyleSelect"
const val systemTypefaces = "system_typefaces"
const val readBodyToLh = "readBodyToLh"
const val textFullJustify = "textFullJustify"
const val textBottomJustify = "textBottomJustify"
const val autoReadSpeed = "autoReadSpeed"
const val barElevation = "barElevation"
const val transparentStatusBar = "transparentStatusBar"
const val defaultCover = "defaultCover"
const val replaceEnableDefault = "replaceEnableDefault"
const val showBrightnessView = "showBrightnessView"
const val autoClearExpired = "autoClearExpired"
const val cPrimary = "colorPrimary"
const val cAccent = "colorAccent"
const val cBackground = "colorBackground"
const val cBBackground = "colorBottomBackground"
const val cNPrimary = "colorPrimaryNight"
const val cNAccent = "colorAccentNight"
const val cNBackground = "colorBackgroundNight"
const val cNBBackground = "colorBottomBackgroundNight"
const val installCount = "installCount"
const val installTime = "installTime"
const val appraiseShow = "appraiseShow"
}

@ -1,7 +0,0 @@
package com.novel.read.constant
object Status {
const val STOP = 0
const val PLAY = 1
const val PAUSE = 3
}

@ -1,20 +0,0 @@
package com.novel.read.constant
import com.novel.read.help.AppConfig
import com.novel.read.utils.ColorUtils
enum class Theme {
Dark, Light, Auto, Transparent;
companion object {
fun getTheme() =
if (AppConfig.isNightTheme) Dark
else Light
fun getTheme(backgroundColor: Int) =
if (ColorUtils.isColorLight(backgroundColor)) Light
else Dark
}
}

@ -0,0 +1,115 @@
package com.novel.read.constants
import android.graphics.Color
import com.novel.read.utlis.FileUtils
import java.io.File
// ┏┓   ┏┓
//┏┛┻━━━┛┻┓
//┃       
//┃      
//┃ ┳┛ ┗┳ 
//┃       
//┃      
//┃       
//┗━┓   ┏━┛
// ┃   ┃ 神兽保佑
// ┃   ┃ 代码无BUG!
// ┃   ┗━━━┓
// ┃       ┣┓
// ┃       ┏┛
// ┗┓┓┏━┳┓┏┛
// ┃┫┫ ┃┫┫
// ┗┻┛ ┗┻┛
/**
* Created by zlj on 2019/7/27.
* desc: 常量
*/
object Constant {
const val NIGHT = "NIGHT"
const val Language = "Language"
const val BookSort = "BookSort"
const val Uid = "Uid"
const val Sex = "Sex"
const val Type = "Type"
const val DateType = "DateType"
const val BookGuide = "BookGuide" //图书引导是否提示过
const val FORMAT_BOOK_DATE = "yyyy-MM-dd HH:mm:ss"
const val FORMAT_TIME = "HH:mm"
const val COMMENT_SIZE = 10
const val FeedBackEmail = "390057892@qq.com"
/**
* 百度语音合成
*/
const val appId = "16826023"
const val appKey = "vEuU5gIWGwq5hivdTAaKz0P9"
const val secretKey = "FcWRYUIrOPyE7dy51qfYZmg8Y1ZyP1c4 "
//BookCachePath (因为getCachePath引用了Context,所以必须是静态变量,不能够是静态常量)
@kotlin.jvm.JvmField
var BOOK_CACHE_PATH: String = (FileUtils.getCachePath() + File.separator
+ "book_cache" + File.separator)
@kotlin.jvm.JvmField
val tagColors = intArrayOf(
Color.parseColor("#90C5F0"),
Color.parseColor("#91CED5"),
Color.parseColor("#F88F55"),
Color.parseColor("#C0AFD0"),
Color.parseColor("#E78F8F"),
Color.parseColor("#67CCB7"),
Color.parseColor("#F6BC7E"),
Color.parseColor("#90C5F0"),
Color.parseColor("#91CED5")
)
//榜单类型
interface ListType {
companion object {
const val Human = "1"
const val EditRecommend = "2"
const val HotSearch = "3"
}
}
interface GenderType {
companion object {
const val Man = "1"
const val Woman = "2"
}
}
interface DateTyp {
companion object {
const val General = "3"
const val Month = "2"
const val Week = "1"
}
}
interface Bundle {
companion object {
const val CategoryId = "category_id"
const val mTitle = "mTitle"
const val BookId = "BookId"
}
}
interface RequestCode {
companion object {
const val REQUEST_READ = 1
}
}
interface ResultCode {
companion object {
const val RESULT_IS_COLLECTED = "result_is_collected"
}
}
}

@ -1,17 +0,0 @@
# 存储数据用
* dao 数据操作
* entities 数据模型
* \Book 书籍信息
* \BookChapter 目录信息
* \BookGroup 书籍分组
* \Bookmark 书签
* \BookSource 书源
* \Cookie http cookie
* \ReplaceRule 替换规则
* \RssArticle rss条目
* \RssReadRecord rss阅读记录
* \RssSource rss源
* \RssStar rss收藏
* \SearchBook 搜索结果
* \SearchKeyword 搜索关键字
* \TxtTocRule txt文件目录规则

@ -1,50 +0,0 @@
package com.novel.read.data.db
import android.content.ContentValues
import com.novel.read.data.db.entity.Book
import org.litepal.LitePal
class BookDao {
fun getAllBooks(): MutableList<Book> = LitePal.findAll(Book::class.java)
fun lastReadBook(): Book = LitePal.order("durChapterTime desc").findFirst(Book::class.java)
fun update(book: Book) {
val values = ContentValues()
values.put("authorPenname", book.authorPenname)
values.put("bookName", book.bookName)
values.put("bookStatus", book.bookStatus)
values.put("categoryName", book.categoryName)
values.put("channelName", book.channelName)
values.put("cName", book.cName)
values.put("coverImageUrl", book.coverImageUrl)
values.put("introduction", book.introduction)
values.put("keyWord", book.keyWord)
values.put("lastUpdateChapterDate", book.lastUpdateChapterDate)
values.put("status", book.status)
values.put("wordCount", book.wordCount)
values.put("durChapterTime", book.durChapterTime)
values.put("durChapterIndex", book.durChapterIndex)
values.put("durChapterPos", book.durChapterPos)
values.put("durChapterTitle", book.durChapterTitle)
values.put("totalChapterNum", book.totalChapterNum)
LitePal.updateAll(Book::class.java, values, "bookId=?", book.bookId.toString())
}
fun getBook(bookId: String): Book? =
LitePal.where("bookId=?", bookId).findFirst(Book::class.java)
fun insert(book: Book) = book.saveOrUpdate("bookId=?", book.bookId.toString())
fun delete(book: Book): Int =
LitePal.deleteAll(Book::class.java, "bookId=?", book.bookId.toString())
fun saveBook(book: List<Book>?) {
if (book != null && book.isNotEmpty()) {
LitePal.saveAll(book)
}
}
}

@ -1,60 +0,0 @@
package com.novel.read.data.db
class BookDatabase private constructor() {
companion object {
private var instance: BookDatabase? = null
get() {
if (field == null) {
field = BookDatabase()
}
return field
}
fun get(): BookDatabase {
return instance!!
}
}
private var chapterDao: ChapterDao? = null
private var bookDao: BookDao? = null
private var readRecordDao: ReadRecordDao? = null
private var bookMarkDao: BookMarkDao? = null
private var searchHistoryDao: SearchHistoryDao? = null
fun getChapterDao(): ChapterDao {
if (chapterDao == null) {
chapterDao = ChapterDao()
}
return chapterDao!!
}
fun getBookDao(): BookDao {
if (bookDao == null) {
bookDao = BookDao()
}
return bookDao!!
}
fun getReadRecordDao(): ReadRecordDao {
if (readRecordDao == null) {
readRecordDao = ReadRecordDao()
}
return readRecordDao!!
}
fun getBookMarkDao(): BookMarkDao {
if (bookMarkDao == null) {
bookMarkDao = BookMarkDao()
}
return bookMarkDao!!
}
fun getSearchDao(): SearchHistoryDao {
if (searchHistoryDao == null) {
searchHistoryDao = SearchHistoryDao()
}
return searchHistoryDao!!
}
}

@ -1,39 +0,0 @@
package com.novel.read.data.db
import com.novel.read.data.db.entity.Bookmark
import org.litepal.LitePal
class BookMarkDao {
fun observeByBook(bookId: Long): List<Bookmark>? =
LitePal.where("bookId=?", bookId.toString()).find(Bookmark::class.java)
fun liveDataSearch(bookId: String, key: String): List<Bookmark>? =
LitePal.where(
"bookId=? and (chapterName like ? or content like ?)",
bookId.toString(),
"%${key}%", "%${key}%"
)
.find(Bookmark::class.java)
fun insert(bookmark: Bookmark) {
bookmark.saveOrUpdate(
"bookId=? and chapterIndex=?",
bookmark.bookId.toString(),
bookmark.chapterIndex.toString()
)
}
fun update(bookmark: Bookmark) {
bookmark.saveOrUpdate(
"bookId=? and chapterIndex=?",
bookmark.bookId.toString(),
bookmark.chapterIndex.toString()
)
}
fun delete(bookmark: Bookmark) {
bookmark.delete()
}
}

@ -1,48 +0,0 @@
package com.novel.read.data.db
import com.novel.read.data.db.entity.BookChapter
import org.litepal.LitePal
class ChapterDao {
fun observeByBook(bookId: String): List<BookChapter>? =
LitePal.where("bookId=?", bookId.toString()).find(BookChapter::class.java)
fun liveDataSearch(bookId: String, key: String): List<BookChapter>? =
LitePal.where("bookId=? and chapterName like ?", bookId, "%${key}%")
.find(BookChapter::class.java)
fun getChapter(chapterId: Long): BookChapter? =
LitePal.where("chapterId =?", chapterId.toString()).findFirst(BookChapter::class.java)
fun getChapter(bookId: Long, chapterIndex: Int): BookChapter? =
LitePal.where("bookId=? and chapterIndex =?", bookId.toString(), chapterIndex.toString())
.findFirst(BookChapter::class.java)
fun getChapterList(bookId: Long): List<BookChapter>? =
LitePal.where("bookId=?", bookId.toString()).find(BookChapter::class.java)
fun getChapterList(bookId: Long, start: Int, end: Int): List<BookChapter> =
LitePal.where(
"bookId=? and chapterIndex >=? and chapterIndex <=?",
bookId.toString(),
start.toString(),
end.toString()
).order("chapterIndex desc").find(BookChapter::class.java)
fun getChapterCount(bookId: Long): Int {
return LitePal.where("bookId=?", bookId.toString()).count(BookChapter::class.java)
}
fun insert(bookChapters: Array<BookChapter>) {
if (bookChapters.isNotEmpty()) {
LitePal.deleteAll(
BookChapter::class.java,
"bookId=?",
bookChapters[0].bookId.toString()
)
}
LitePal.saveAll(bookChapters.toList())
}
}

@ -1,23 +0,0 @@
package com.novel.read.data.db
import com.novel.read.data.db.entity.ReadRecord
import org.litepal.LitePal
class ReadRecordDao {
fun getReadTime(bookName: String): Long? =
LitePal.where("bookName =?", bookName).findFirst(ReadRecord::class.java)?.readTime
fun insert(readRecord: ReadRecord) {
readRecord.saveOrUpdate("bookId=?", readRecord.bookId.toString())
}
fun delete(readRecord: ReadRecord) {
readRecord.delete()
}
fun getAll(): List<ReadRecord> {
return LitePal.findAll(ReadRecord::class.java)
}
}

@ -1,19 +0,0 @@
package com.novel.read.data.db
import com.novel.read.data.db.entity.SearchHistory
import org.litepal.LitePal
class SearchHistoryDao {
fun getListByTime(): MutableList<SearchHistory>? =
LitePal.order("saveTime desc").limit(5).find(SearchHistory::class.java)
fun insert(searchHistory: SearchHistory, key: String) {
searchHistory.saveOrUpdate("key=?", key)
}
fun deleteAll() {
LitePal.deleteAll(SearchHistory::class.java)
}
}

@ -1,52 +0,0 @@
package com.novel.read.data.db.entity
import com.google.gson.annotations.SerializedName
import com.novel.read.constant.AppPattern
import com.novel.read.constant.BookType
import com.novel.read.utils.MD5Utils
import org.litepal.crud.LitePalSupport
import kotlin.math.max
data class Book(
val authorPenname: String,
val bookId: Long,
val bookName: String,
val bookStatus: String,
val categoryName: String?,
val channelName: String?,
@SerializedName("className")
val cName: String?,
val coverImageUrl: String,
val introduction: String,
val keyWord: String,
var lastUpdateChapterDate: String,
val status: Int,
val wordCount: Long,
var totalChapterNum: Int = 0, // 书籍目录总数
var durChapterTitle: String? = null, // 当前章节名称
var durChapterIndex: Int = 0, // 当前章节索引
var durChapterPos: Int = 0, // 当前阅读的进度(首行字符的索引位置)
var durChapterTime: Long = System.currentTimeMillis(), // 最近一次阅读书籍的时间(打开正文的时间)
var origin: String = BookType.net,
var originName: String = "",
var bookTypeId: Int = 0
) : LitePalSupport() {
fun isLocalBook(): Boolean {
return origin == BookType.local
}
fun getFolderName(): String {
return bookName.replace(AppPattern.fileNameRegex, "") + MD5Utils.md5Encode16(coverImageUrl)
}
fun isEpub(): Boolean {
return originName.endsWith(".epub", true)
}
fun canUpdate(): Boolean {
return bookStatus == BookType.serial
}
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
}

@ -1,19 +0,0 @@
package com.novel.read.data.db.entity
import org.litepal.crud.LitePalSupport
data class BookChapter(
// val bookId: Int,
// val chapterList: List<Chapter>,
// val volumeId: Int,
// val volumeName: String
val chapterId: Long,
val bookId: Long,
val chapterIndex: Int,
var chapterName: String,
val createTimeValue: Long,
val updateDate: String,
val updateTimeValue: Long,
val chapterUrl: String?,
) : LitePalSupport()

@ -1,16 +0,0 @@
package com.novel.read.data.db.entity
import org.litepal.crud.LitePalSupport
import java.io.Serializable
data class Bookmark(
var time: Long = System.currentTimeMillis(),
var bookId: Long = 0,
var bookName: String = "",
val bookAuthor: String = "",
var chapterIndex: Int = 0,
var pageIndex: Int = 0,
var chapterName: String = "",
var content: String = ""
) : LitePalSupport(), Serializable

@ -1,10 +0,0 @@
package com.novel.read.data.db.entity
data class ChapterDetailEntity(
val chapterContent: String,
val chapterIndex: Int,
val chapterName: String,
val isRecommend: String,
val isVIP: String,
val wordCount: Int
)

@ -1,9 +0,0 @@
package com.novel.read.data.db.entity
import org.litepal.crud.LitePalSupport
data class HttpTTS(
val id: Long = System.currentTimeMillis(),
var name: String = "",
var url: String = ""
) : LitePalSupport()

@ -1,15 +0,0 @@
package com.novel.read.data.db.entity
import org.litepal.crud.LitePalSupport
import java.io.Serializable
/**
* Created by zlj
*/
class ReadRecord(
var androidId: String = "",
var bookName: String = "",
var bookId: Long = 0L,
var readTime: Long = 0L
) : LitePalSupport(), Serializable

@ -1,20 +0,0 @@
package com.novel.read.data.model
data class AppUpdateResp(
val appEdition: AppEdition
)
data class AppEdition(
val createTime: String,
val editionCode: String,
val editionId: Int,
val fileUrl: String,
val forceUpdate: Int,
val insideEditionCode: Int,
val isDel: Int,
val packageSize: String,
val pushTime: String,
val shellName: String,
val status: Int,
val upgradeContent: String
)

@ -1,33 +0,0 @@
package com.novel.read.data.model
data class BookInfoResp(
val book: BookResp
)
data class BookResp(
val authorInformation: Any,
val authorName: String,
val authorUserId: Long,
val bookClass: Any,
val bookId: Long,
val bookName: String,
val bookSId: String,
val bookStatus: Any,
val bookTypeId: Int,
val categoryName: String?,
val channelName: Any?,
val coverImageUrl: String,
val createTime: Long,
val endStatus: Int,
val extBookId: Any,
val hits: Any,
val hotStatus: Any,
val introduction: String,
val keyWord: String,
val lastUpdateChapterDate: String,
val rankNumber: Any,
val rankType: Any,
val recommendStatus: Any,
val validFlag: Int,
val wordCount: Long
)

@ -1,52 +0,0 @@
package com.novel.read.data.model
import com.novel.read.utils.StringUtils
data class BookListResp(
val authorInformation: String,
var authorName: String?,
val authorUserId: Long,
val bookClass: Int,
val bookId: Long,
val bookName: String,
val bookSId: String,
val bookStatus: Int,
val bookTypeId: Int,
val categoryName: String?,
val channelName: String?,
val coverImageUrl: String,
val createTime: Long,
val endStatus: Int,
val hotStatus: Int,
val introduction: String?,
val keyWord: String,
val lastUpdateChapterDate: String?,
val recommendStatus: Int,
val validFlag: Int,
val wordCount: Long
) {
fun getBAuthorName(): String {
return StringUtils.convertCC(authorName!!)
}
fun getBBookName(): String {
return StringUtils.convertCC(bookName)
}
fun getBCategoryName(): String {
return StringUtils.convertCC(categoryName ?: "")
}
fun getBChannel(): String {
return StringUtils.convertCC(channelName!!)
}
fun getBIntroduction(): String {
return StringUtils.convertCC(introduction!!)
}
fun getBKeyWord(): String {
return StringUtils.convertCC(keyWord)
}
}

@ -1,51 +0,0 @@
package com.novel.read.data.model
import com.chad.library.adapter.base.entity.MultiItemEntity
import com.novel.read.constant.LayoutType
import com.novel.read.utils.StringUtils
data class BoyEndRank(
val authorInformation: String,
val authorName: String,
val authorUserId: Long,
val bookChannelId: Long,
val bookClass: Int,
val bookId: Long,
val bookName: String,
val bookSId: String,
val bookStatus: Int,
val bookTypeId: Long,
val categoryName: String,
val channelName: String,
val coverImageUrl: String,
val createTime: Long,
val endStatus: Int,
val extBookId: Int,
val hits: String,
val hotStatus: Int,
val introduction: String,
val keyWord: String,
val lastUpdateChapterDate: String,
val rankNumber: String,
val rankType: String,
val recommendStatus: Int,
val validFlag: Int,
val wordCount: Long
){
fun getBAuthorName(): String {
return StringUtils.convertCC(authorName)
}
fun getBBookName(): String {
return StringUtils.convertCC(bookName)
}
fun getBCategoryName(): String {
return StringUtils.convertCC(categoryName)
}
}
data class BoyEndRankEntity(val boyEndRanks:List<BoyEndRank>):MultiItemEntity{
override val itemType: Int
get() = LayoutType.BOY_END
}

@ -1,50 +0,0 @@
package com.novel.read.data.model
import com.chad.library.adapter.base.entity.MultiItemEntity
import com.novel.read.constant.LayoutType
import com.novel.read.utils.StringUtils
data class BoyHotRank(
val authorInformation: String,
val authorName: String,
val authorUserId: Long,
val bookChannelId: Long,
val bookClass: Int,
val bookId: Long,
val bookName: String,
val bookSId: String,
val bookStatus: Int,
val bookTypeId: Long,
val categoryName: String,
val channelName: String,
val coverImageUrl: String,
val createTime: Long,
val endStatus: Int,
val extBookId: Int,
val hits: String,
val hotStatus: Int,
val introduction: String,
val keyWord: String,
val lastUpdateChapterDate: String,
val rankNumber: String,
val rankType: String,
val recommendStatus: Int,
val validFlag: Int
) {
fun getBAuthorName(): String {
return StringUtils.convertCC(authorName)
}
fun getBBookName(): String {
return StringUtils.convertCC(bookName)
}
fun getBCategoryName(): String {
return StringUtils.convertCC(categoryName)
}
}
data class BoyHotRankEntity(val boyHotRanks: List<BoyHotRank>) : MultiItemEntity {
override val itemType: Int
get() = LayoutType.BOY_HOT
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save