├── .gitignore
├── README.md
├── app
├── build.gradle
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── woodnoisu
│ │ │ └── reader
│ │ │ ├── App.kt
│ │ │ ├── base
│ │ │ ├── BaseActivity.kt
│ │ │ ├── BaseFragment.kt
│ │ │ ├── BaseViewModel.kt
│ │ │ └── ContextProvider.kt
│ │ │ ├── constant
│ │ │ └── Constant.kt
│ │ │ ├── di
│ │ │ ├── NetworkModule.kt
│ │ │ ├── PersistenceModule.kt
│ │ │ └── RepositoryModule.kt
│ │ │ ├── model
│ │ │ ├── BookBean.kt
│ │ │ ├── BookShopInfo.kt
│ │ │ ├── BookSignBean.kt
│ │ │ ├── ChapterBean.kt
│ │ │ ├── ReadRecordBean.kt
│ │ │ ├── Request.kt
│ │ │ └── Response.kt
│ │ │ ├── network
│ │ │ ├── HtmlClient.kt
│ │ │ ├── HtmlService.kt
│ │ │ ├── HttpRequestInterceptor.kt
│ │ │ └── parse
│ │ │ │ ├── BQGParse.kt
│ │ │ │ ├── HtmlParse.kt
│ │ │ │ └── QWYDParse.kt
│ │ │ ├── persistence
│ │ │ ├── AppDataBase.kt
│ │ │ ├── BaseBeanDao.kt
│ │ │ ├── BookDao.kt
│ │ │ ├── BookSignDao.kt
│ │ │ ├── ChapterDao.kt
│ │ │ ├── ReadRecordDao.kt
│ │ │ └── TypeResponseConverter.kt
│ │ │ ├── repository
│ │ │ ├── NovelReadRepository.kt
│ │ │ ├── Repository.kt
│ │ │ ├── ShelfRepository.kt
│ │ │ └── SquareRepository.kt
│ │ │ ├── ui
│ │ │ ├── AboutActivity.kt
│ │ │ ├── StartActivity.kt
│ │ │ ├── main
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainAdapter.kt
│ │ │ │ └── MainViewModel.kt
│ │ │ ├── me
│ │ │ │ └── MeFragment.kt
│ │ │ ├── novelRead
│ │ │ │ ├── CatalogueAdapter.kt
│ │ │ │ ├── MarkAdapter.kt
│ │ │ │ ├── NovelReadActivity.kt
│ │ │ │ └── NovelReadViewModel.kt
│ │ │ ├── shelf
│ │ │ │ ├── ShelfAdapter.kt
│ │ │ │ ├── ShelfFragment.kt
│ │ │ │ └── ShelfViewModel.kt
│ │ │ ├── square
│ │ │ │ ├── SquareAdapter.kt
│ │ │ │ ├── SquareFragment.kt
│ │ │ │ └── SquareViewModel.kt
│ │ │ └── widget
│ │ │ │ └── page
│ │ │ │ ├── LocalPageLoader.kt
│ │ │ │ ├── NetPageLoader.kt
│ │ │ │ ├── PageAnimation.kt
│ │ │ │ ├── PageLoader.kt
│ │ │ │ ├── PageView.kt
│ │ │ │ ├── ReadSettingDialog.kt
│ │ │ │ ├── ReadSettingManager.kt
│ │ │ │ ├── adapter
│ │ │ │ ├── PageStyleAdapter.kt
│ │ │ │ └── PageStyleHolder.kt
│ │ │ │ ├── anim
│ │ │ │ ├── CoverPageAnim.kt
│ │ │ │ ├── HorizonPageAnim.kt
│ │ │ │ ├── NonePageAnim.kt
│ │ │ │ ├── ScrollPageAnim.kt
│ │ │ │ ├── SimulationPageAnim.kt
│ │ │ │ └── SlidePageAnim.kt
│ │ │ │ ├── event
│ │ │ │ ├── OnCurPageChangeListener.kt
│ │ │ │ ├── OnPageChangeListener.kt
│ │ │ │ └── OnTouchListener.kt
│ │ │ │ ├── model
│ │ │ │ ├── BitmapView.kt
│ │ │ │ ├── Direction.kt
│ │ │ │ ├── PageMode.kt
│ │ │ │ ├── PageStyle.kt
│ │ │ │ └── TxtPage.kt
│ │ │ │ └── utils
│ │ │ │ └── DateUtil.kt
│ │ │ └── utils
│ │ │ ├── BookUtil.kt
│ │ │ ├── BrightnessUtil.kt
│ │ │ ├── FileUtil.kt
│ │ │ ├── LocalManageUtil.kt
│ │ │ ├── LogUtil.kt
│ │ │ ├── MD5Util.kt
│ │ │ ├── ScreenUtil.kt
│ │ │ ├── SpUtil.kt
│ │ │ ├── StatusBarUtil.kt
│ │ │ ├── StringUtil.kt
│ │ │ ├── SystemBarUtil.kt
│ │ │ ├── ToastUtil.kt
│ │ │ └── UtilDialog.kt
│ └── res
│ │ ├── anim
│ │ ├── message_fade_in.xml
│ │ ├── message_fade_out.xml
│ │ ├── slide_bottom_in.xml
│ │ ├── slide_bottom_out.xml
│ │ ├── slide_left_in.xml
│ │ ├── slide_left_out.xml
│ │ ├── slide_right_in.xml
│ │ ├── slide_right_out.xml
│ │ ├── slide_top_in.xml
│ │ └── slide_top_out.xml
│ │ ├── color
│ │ └── selector_bottom_navigation.xml
│ │ ├── drawable-night-xxhdpi
│ │ ├── ic_arrow_right.png
│ │ ├── ic_back.png
│ │ ├── ic_edit.png
│ │ ├── ic_menu_mode_night_normal.png
│ │ ├── ic_search.png
│ │ └── ic_theme.png
│ │ ├── drawable-xhdpi
│ │ ├── back.png
│ │ ├── find.png
│ │ ├── find1.png
│ │ ├── home.png
│ │ ├── home1.png
│ │ ├── ic_back.png
│ │ ├── ic_book_add.png
│ │ ├── ic_book_detail.png
│ │ ├── ic_checked.png
│ │ ├── ic_delete.png
│ │ ├── ic_font_add.png
│ │ ├── ic_font_min.png
│ │ ├── ic_guide.jpg
│ │ ├── ic_item_category_activated.png
│ │ ├── ic_item_category_download.png
│ │ ├── ic_item_category_normal.png
│ │ ├── ic_label.png
│ │ ├── ic_menu_add_mark.png
│ │ ├── ic_more_n.png
│ │ ├── ic_more_p.png
│ │ ├── ic_no_thumb.jpg
│ │ ├── ic_read_menu_moring.png
│ │ ├── ic_read_menu_night.png
│ │ ├── ic_theme.png
│ │ ├── icon_inner.png
│ │ ├── icon_search.png
│ │ ├── mine.png
│ │ ├── mine1.png
│ │ ├── pic_placeholder.png
│ │ ├── search.png
│ │ ├── search1.png
│ │ ├── search_icon.png
│ │ ├── seekbar_thumb_normal.png
│ │ ├── seekbar_thumb_selected.png
│ │ ├── shelf.png
│ │ ├── shelf1.png
│ │ ├── theme_leather_bg.jpg
│ │ └── title_more.png
│ │ ├── drawable-xxhdpi
│ │ ├── ic_back.png
│ │ ├── ic_book_add.png
│ │ ├── ic_book_detail.png
│ │ ├── ic_book_n.png
│ │ ├── ic_book_p.png
│ │ ├── ic_cache.png
│ │ ├── ic_checked.png
│ │ ├── ic_contents.png
│ │ ├── ic_guide.jpg
│ │ ├── ic_item_category_activated.png
│ │ ├── ic_item_category_download.png
│ │ ├── ic_item_category_normal.png
│ │ ├── ic_label.png
│ │ ├── ic_light.png
│ │ ├── ic_menu_add_mark.png
│ │ ├── ic_menu_clear.png
│ │ ├── ic_menu_mode_night_normal.png
│ │ ├── ic_more_n.png
│ │ ├── ic_more_p.png
│ │ ├── ic_no_select.png
│ │ ├── ic_read_menu_moring.png
│ │ ├── ic_read_menu_night.png
│ │ ├── ic_read_setting.png
│ │ ├── ic_select.png
│ │ ├── seekbar_thumb_normal.png
│ │ └── seekbar_thumb_selected.png
│ │ ├── drawable
│ │ ├── bg_coner_line.xml
│ │ ├── bg_listen.xml
│ │ ├── listen_select.xml
│ │ ├── seekbar_bg.xml
│ │ ├── seekbar_thumb.xml
│ │ ├── select_more.xml
│ │ ├── selector_bottom_navigation_home.xml
│ │ ├── selector_bottom_navigation_me.xml
│ │ ├── selector_bottom_navigation_shelf.xml
│ │ ├── selector_bottom_navigation_square.xml
│ │ ├── selector_category_load.xml
│ │ ├── selector_category_unload.xml
│ │ ├── shape_bg_oval.xml
│ │ ├── text_color.xml
│ │ └── text_font_color.xml
│ │ ├── layout
│ │ ├── activity_about.xml
│ │ ├── activity_main.xml
│ │ ├── activity_read.xml
│ │ ├── activity_start.xml
│ │ ├── fragment_me.xml
│ │ ├── fragment_shelf.xml
│ │ ├── fragment_square.xml
│ │ ├── item_book.xml
│ │ ├── item_read_bg.xml
│ │ ├── item_shelf.xml
│ │ ├── layout_download.xml
│ │ ├── layout_light.xml
│ │ ├── layout_read_mark.xml
│ │ ├── layout_setting.xml
│ │ ├── rlv_item_catalogue.xml
│ │ ├── rlv_item_mark.xml
│ │ ├── search_title.xml
│ │ └── title_view.xml
│ │ ├── menu
│ │ ├── bottom_navigation_menu.xml
│ │ ├── left_menu.xml
│ │ └── shelf_pop_menu.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── network_security_config.xml
│ └── test
│ └── java
│ └── com
│ └── woodnoisu
│ └── reader
│ ├── di
│ ├── NetworkModule.kt
│ ├── NetworkModule_ProvideApiClientFactory.java
│ ├── NetworkModule_ProvideApiServiceFactory.java
│ ├── NetworkModule_ProvideHtmlClientFactory.java
│ ├── NetworkModule_ProvideHtmlServiceFactory.java
│ ├── NetworkModule_ProvideOkHttpClientFactory.java
│ ├── NetworkModule_ProvideRetrofitFactory.java
│ ├── PersistenceModule.kt
│ ├── PersistenceModule_ProvideAppDatabaseFactory.java
│ ├── PersistenceModule_ProvideBookDaoFactory.java
│ ├── PersistenceModule_ProvideBookSignDaoFactory.java
│ ├── PersistenceModule_ProvideChapterDaoFactory.java
│ ├── PersistenceModule_ProvideMoshiFactory.java
│ ├── PersistenceModule_ProvideReadRecordDaoFactory.java
│ ├── RepositoryModule.kt
│ ├── RepositoryModule_ProvideNovelReadRepositoryFactory.java
│ ├── RepositoryModule_ProvideShelfRepositoryFactory.java
│ └── RepositoryModule_ProvideSquareRepositoryFactory.java
│ └── utils
│ └── StringUtilTest.kt
├── build.gradle
├── dependencies.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew.bat
├── local.properties
├── screenshot
└── mvvm.png
├── settings.gradle
├── spotless.gradle
└── versionsPlugin.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /.gradle/
3 | /buildSrc/.gradle/
4 | /buildSrc/build/
5 | /app/.gradle/
6 | /app/build/
7 | /feature_album/build/
8 | /feature_album/.gradle/
9 | /feature_favourite/build/
10 | /feature_favourite/.gradle/
11 | /feature_profile/build/
12 | /feature_profile/.gradle/
13 | /feature_reader/build/
14 | /feature_reader/.gradle/
15 | /library_base/build/
16 | /library_base/.gradle/
17 | /library_test_utils/build/
18 | /library_test_utils/.gradle/
19 | /library_base/schemas/
20 | /app/schemas/
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # reader
2 | 免费小说阅读App
3 |
4 | 说明
5 |
6 | 使用多模块版本的编译成apk安装后发现不能运行(调试不影响),然后小弟也去尝试了官方demo的编译安装使用,也是同样的问题,所以先把主分支切为单模块版本的
7 |
8 |
9 | 参考项目
10 |
11 | 1.Pokedex 单项目 mvvm,flow [Pokedex](https://github.com/skydoves/Pokedex)
12 |
13 | 2.NovelReader 基于"任阅"的改进追书App [NovelReader](https://github.com/newbiechen1024/NovelReader)
14 |
15 | 3.FreeNovel 基于kotlin的免费Android小说应用[FreeNovel](https://github.com/lxygithub/FreeNovel)
16 |
17 | 4.OKBook kotlin + 协程 + MVVM 模式来编写的看小说APP [OKBook](https://gitee.com/xcode_xiao/OKBook)
18 |
19 | # 最新应用下载地址
20 | [reader_v1.0.2](https://raw.githubusercontent.com/woodwen/reader/main/apk/reader_v1.0.2.apk)
21 |
22 | # 应用展示
23 |
24 | 
25 | 
26 | 
27 | 
28 | 
29 | 
30 |
31 |
32 | # 应用简介
33 |
34 | 小说阅读器(模块化开发/单项目开发,基于Kotlin+MVVM+Kodein/Hilt+Retrofit+Jsoup+Moshi+Coroutines+Flow+Jetpack+Coil+Room+Mockk等架构实现),用kotlin重写了“任阅”的阅读模块代码,优化,代码逻辑,降低内存使用率。
35 |
36 | 目前已有功能:
37 |
38 | 1.书城
39 |
40 | * 支持书城切换(目前支持,全文阅读网,笔趣阁)。
41 | * 支持小说分类切换。
42 | * 支持按书名,作者搜索小说。
43 | * 支持查看小说简介。
44 | * 支持小说订阅
45 | * 支持直接在线阅读
46 |
47 | 2.书架
48 |
49 | * 支持取消订阅
50 | * 支持搜索书架
51 | * 支持订阅本地书籍(目前只支持.txt)
52 | * 支持本地阅读
53 |
54 | 3.个人配置
55 |
56 | * 清理缓存
57 | * 跳转github
58 |
59 | 4.阅读
60 |
61 | * 目录(小说目录)
62 | * 亮度(设置阅读器亮度,日/夜模式)
63 | * 缓存(下载小说到本地)
64 | * 设置(字体,字号,翻页模式,背景图片)
65 |
66 | 准备加入但是目前还没的功能:
67 |
68 | 1.隐藏书城
69 | 2.尝试支持厚墨源
70 | 3.书城不再是写死的方式,而是类似于厚墨的安装方式
71 | 4.支持语音朗读
72 |
73 | **注: 该项目不定时维护更新,如有侵权的地方,请告知小弟,立马删除**
74 |
75 | # 以下为框架相关(开辟了两个分支,一个是单项目的,一个是模块化的)
76 |
77 | [单模块版本](https://github.com/woodwen/reader/tree/dev-single)
78 |
79 | [多模块版本](https://github.com/woodwen/reader/tree/dev-multiple)
80 |
81 |
82 | # 可能遇到的编译问题
83 |
84 | 如果是从多项目版本切换过来的,会在单项目版本中多很多多余的文件夹(buildSrc文件夹,feature系列文件夹,library系列文件夹,各种build文件夹),删除后再编译
85 |
86 | # 以下为框架相关
87 |
88 | # 项目特点
89 |
90 | * 基于现代Android应用程序技术堆栈和MVVM架构的小型应用程序。
91 | * 该项目的重点是实现依赖注入的新库Hilt。
92 | * 还可以从网络中获取数据,并通过存储库模式将持久性数据集成到数据库中
93 |
94 | # 技术栈
95 |
96 | • 最低SDK级别21
97 |
98 | • 基于Kotlin,Coroutines + Flow用于异步。
99 |
100 | • Hilt(alpha)用于依赖项注入。
101 |
102 | • JetPack
103 | ○ LiveData-将域层数据通知视图。
104 | ○ Lifecycle-当生命周期状态改变时,丢弃观察数据。
105 | ○ ViewModel-与UI相关的数据持有者,具有生命周期意识。
106 | ○ Room Persistence-使用抽象层构建数据库。
107 |
108 | • 结构
109 | ○ MVVM体系结构(视图-数据绑定-ViewModel-模型)
110 | ○ 储存库模式
111 |
112 | • Retrofit2和OkHttp3-构造REST API和分页网络数据。
113 |
114 | • Moshi -Kotlin和Java的现代JSON库。
115 |
116 | • Coil -使用Kotlin惯用API的图像加载库
117 |
118 | • Bundler -Android Intent和Bundle扩展,可优雅地插入和检索值。
119 |
120 | • Material-Components-材质设计组件,例如波纹动画,cardView。
121 |
122 | • 自定义视图
123 | ○ Rainbow-适用于Android的渐变和着色的简单方法。
124 | ○ AndroidRibbon-一种在Android上通过闪烁实现漂亮的功能区的简单方法。
125 | ○ ProgressView-优美灵活的ProgressView,可完全通过动画进行自定义。
126 |
127 | # 架构
128 |
129 | 基于MVVM体系结构和存储库模式
130 |
131 | 
132 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/App.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader
2 |
3 | import android.app.Application
4 | import com.woodnoisu.reader.constant.Constant
5 | import dagger.hilt.android.HiltAndroidApp
6 | import java.io.File
7 |
8 | /**
9 | * app对象
10 | */
11 |
12 | @HiltAndroidApp
13 | class App() : Application(){
14 | /**
15 | * 创建事件
16 | */
17 | override fun onCreate() {
18 | super.onCreate()
19 |
20 | // 初始化文件夹
21 | if (!File(Constant.BOOK_CACHE_PATH).exists()) {
22 | File(Constant.BOOK_CACHE_PATH).mkdir()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.base
2 |
3 | import androidx.annotation.MainThread
4 | import androidx.lifecycle.*
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | abstract class BaseViewModel : ViewModel() {
8 |
9 | inline fun launchOnViewModelScope(crossinline block: suspend () -> LiveData): LiveData {
10 | return liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
11 | emitSource(block())
12 | }
13 | }
14 |
15 | protected val _toast: MutableLiveData = MutableLiveData()
16 | val toast: LiveData get() = _toast
17 |
18 | protected val _isLoading: MutableLiveData = MutableLiveData()
19 | val isLoading: LiveData get() = _isLoading
20 |
21 | @MainThread
22 | fun toastMsg(msg: String) {
23 | _toast.value = msg
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/base/ContextProvider.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.base
2 |
3 | import android.content.ContentProvider
4 | import android.content.ContentValues
5 | import android.content.Context
6 | import android.database.Cursor
7 | import android.net.Uri
8 | import com.woodnoisu.reader.utils.LocalManageUtil
9 | import com.woodnoisu.reader.utils.SpUtil
10 |
11 | /**
12 | * 上下文
13 | */
14 | class ContextProvider : ContentProvider() {
15 |
16 | /**
17 | * 静态对象
18 | */
19 | companion object {
20 | lateinit var mContext: Context
21 | }
22 |
23 | /**
24 | * 创建事件
25 | */
26 | override fun onCreate(): Boolean {
27 | mContext = context!!
28 | SpUtil.init(context!!)
29 | LocalManageUtil.setApplicationLanguage(context!!)
30 | setNight()
31 | return false
32 | }
33 |
34 | /**
35 | * 设置暗夜模式
36 | */
37 | private fun setNight() {
38 |
39 |
40 | }
41 |
42 | /**
43 | * 查询
44 | */
45 | override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? {
46 | return null
47 | }
48 |
49 | /**
50 | * 获取类型
51 | */
52 | override fun getType(uri: Uri): String? {
53 | return null
54 | }
55 |
56 | /**
57 | * 插入
58 | */
59 | override fun insert(uri: Uri, values: ContentValues?): Uri? {
60 | return null
61 | }
62 |
63 | /**
64 | * 删除
65 | */
66 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
67 | return 0
68 | }
69 |
70 | /**
71 | * 更新
72 | */
73 | override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int {
74 | return 0
75 | }
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/constant/Constant.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.constant
2 |
3 | import com.woodnoisu.reader.utils.FileUtil
4 | import java.io.File
5 | import java.util.regex.Pattern
6 |
7 | object Constant {
8 | const val UserAgent =
9 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36"
10 |
11 | /*URL_BASE*/
12 | const val API_BASE_URL = "http://api.zhuishushenqi.com"
13 |
14 | //Book Date Convert Format
15 | const val FORMAT_BOOK_DATE = "yyyy-MM-dd'T'HH:mm:ss"
16 | const val FORMAT_TIME = "HH:mm"
17 | const val FORMAT_FILE_DATE = "yyyy-MM-dd"
18 |
19 | //RxBus
20 | const val MSG_SELECTOR = 1
21 | const val NIGHT = "NIGHT"
22 | const val Language = "Language"
23 | const val BookSort = "BookSort"
24 | const val Uid = "Uid"
25 | const val Sex = "Sex"
26 | const val Type = "Type"
27 | const val DateType = "DateType"
28 | const val BookGuide = "BookGuide" //图书引导是否提示过
29 |
30 | const val COMMENT_SIZE = 10
31 |
32 | const val FeedBackEmail = ""
33 |
34 | const val RESULT_IS_COLLECTED = "result_is_collected"
35 |
36 | //采用自己的格式去设置文件,防止文件被系统文件查询到
37 | const val SUFFIX_NB = ".zlj"
38 | const val SUFFIX_TXT = ".txt"
39 | const val SUFFIX_EPUB = ".epub"
40 | const val SUFFIX_PDF = ".pdf"
41 |
42 | //默认从文件中获取数据的长度
43 | const val BUFFER_SIZE = 512 * 1024
44 |
45 | //没有标题的时候,每个章节的最大长度
46 | const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024
47 |
48 | //BookCachePath (因为getCachePath引用了Context,所以必须是静态变量,不能够是静态常量)
49 | @kotlin.jvm.JvmField
50 | var BOOK_CACHE_PATH: String = (FileUtil.getDownloadPath() + File.separator
51 | + "free_novel" + File.separator)
52 |
53 | // "序(章)|前言"
54 | @kotlin.jvm.JvmField
55 | val mPreChapterPattern: Pattern = Pattern.compile(
56 | "^(\\s{0,10})((\u5e8f[\u7ae0\u8a00]?)|(\u524d\u8a00)|(\u6954\u5b50))(\\s{0,10})$",
57 | Pattern.MULTILINE
58 | )
59 |
60 | //正则表达式章节匹配模式
61 | // "(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节回集卷])(.*)"
62 | @kotlin.jvm.JvmField
63 | val CHAPTER_PATTERNS = arrayOf(
64 | "^(.{0,8})(\u7b2c)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\u7ae0\u8282\u56de\u96c6\u5377])(.{0,30})$",
65 | "^(\\s{0,4})([\\(\u3010\u300a]?(\u5377)?)([0-9\u96f6\u4e00\u4e8c\u4e24\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u58f9\u8d30\u53c1\u8086\u4f0d\u9646\u67d2\u634c\u7396\u62fe\u4f70\u4edf]{1,10})([\\.:\uff1a\u0020\\f\t])(.{0,30})$",
66 | "^(\\s{0,4})([\\(\uff08\u3010\u300a])(.{0,30})([\\)\uff09\u3011\u300b])(\\s{0,2})$",
67 | "^(\\s{0,4})(\u6b63\u6587)(.{0,20})$",
68 | "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$"
69 | )
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/di/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import com.woodnoisu.reader.network.*
4 | import com.woodnoisu.reader.constant.Constant
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import okhttp3.OkHttpClient
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.moshi.MoshiConverterFactory
12 | import java.util.concurrent.TimeUnit
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | object NetworkModule {
18 |
19 | @Provides
20 | @Singleton
21 | fun provideOkHttpClient(): OkHttpClient {
22 | return OkHttpClient.Builder()
23 | .apply {
24 | connectTimeout(30, TimeUnit.SECONDS)// 连接时间:30s超时
25 | readTimeout(10, TimeUnit.SECONDS)// 读取时间:10s超时
26 | writeTimeout(10, TimeUnit.SECONDS)// 写入时间:10s超时
27 | addInterceptor(HttpRequestInterceptor())
28 | }.build()
29 | }
30 |
31 | @Provides
32 | @Singleton
33 | fun provideHtmlService(): HtmlService {
34 | return HtmlService()
35 | }
36 |
37 | @Provides
38 | @Singleton
39 | fun provideHtmlClient(htmlService: HtmlService): HtmlClient {
40 | return HtmlClient(htmlService)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/di/PersistenceModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import com.squareup.moshi.Moshi
6 | import com.woodnoisu.reader.persistence.*
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object PersistenceModule {
16 |
17 | @Provides
18 | @Singleton
19 | fun provideMoshi(): Moshi {
20 | return Moshi.Builder().build()
21 | }
22 |
23 | @Provides
24 | @Singleton
25 | fun provideAppDatabase(
26 | application: Application,
27 | //typeResponseConverter: TypeResponseConverter
28 | ): AppDataBase {
29 | return Room
30 | .databaseBuilder(application, AppDataBase::class.java, "db_novel.db")
31 | .fallbackToDestructiveMigration()
32 | //.addTypeConverter(typeResponseConverter)
33 | .build()
34 | }
35 |
36 | @Provides
37 | @Singleton
38 | fun provideBookDao(appDataBase: AppDataBase): BookDao {
39 | return appDataBase.bookDao()
40 | }
41 |
42 | @Provides
43 | @Singleton
44 | fun provideChapterDao(appDataBase: AppDataBase): ChapterDao {
45 | return appDataBase.chapterDao()
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | fun provideBookSignDao(appDataBase: AppDataBase): BookSignDao {
51 | return appDataBase.bookSignDao()
52 | }
53 |
54 | @Provides
55 | @Singleton
56 | fun provideReadRecordDao(appDataBase: AppDataBase): ReadRecordDao {
57 | return appDataBase.readRecordDao()
58 | }
59 |
60 | // @Provides
61 | // @Singleton
62 | // fun provideTypeResponseConverter(moshi: Moshi): TypeResponseConverter {
63 | // return TypeResponseConverter(moshi)
64 | // }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import com.woodnoisu.reader.network.HtmlClient
4 | import com.woodnoisu.reader.persistence.BookDao
5 | import com.woodnoisu.reader.persistence.BookSignDao
6 | import com.woodnoisu.reader.persistence.ChapterDao
7 | import com.woodnoisu.reader.persistence.ReadRecordDao
8 | import com.woodnoisu.reader.repository.*
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.android.components.ActivityRetainedComponent
13 | import dagger.hilt.android.scopes.ActivityRetainedScoped
14 |
15 | @Module
16 | @InstallIn(ActivityRetainedComponent::class)
17 | object RepositoryModule {
18 | @Provides
19 | @ActivityRetainedScoped
20 | fun provideNovelReadRepository(
21 | htmlClient: HtmlClient,
22 | bookDao: BookDao,
23 | bookSignDao: BookSignDao,
24 | chapterDao: ChapterDao,
25 | readRecordDao: ReadRecordDao
26 | ): NovelReadRepository {
27 | return NovelReadRepository(htmlClient, bookDao, bookSignDao, chapterDao, readRecordDao)
28 | }
29 |
30 | @Provides
31 | @ActivityRetainedScoped
32 | fun provideShelfRepository(
33 | bookDao: BookDao,
34 | bookSignDao: BookSignDao,
35 | chapterDao: ChapterDao,
36 | readRecordDao: ReadRecordDao
37 | ): ShelfRepository {
38 | return ShelfRepository(bookDao, bookSignDao, chapterDao, readRecordDao)
39 | }
40 |
41 | @Provides
42 | @ActivityRetainedScoped
43 | fun provideSquareRepository(
44 | htmlClient: HtmlClient,
45 | bookDao: BookDao,
46 | ): SquareRepository {
47 | return SquareRepository(htmlClient,bookDao)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/BookBean.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | import android.os.Parcelable
4 | import androidx.room.Entity
5 | import androidx.room.Ignore
6 | import androidx.room.PrimaryKey
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * 书籍
11 | */
12 | @Entity(tableName = "my_shelf")
13 | @Parcelize
14 | data class BookBean constructor(@PrimaryKey(autoGenerate = true)
15 | var id: Int = 0,// 书籍id
16 | var name: String = "",// 书名
17 | var url: String = "",// 书籍网络地址
18 | var category: String = "",// 书籍类型
19 | var status: String = "",// 更新状态
20 | //var typeUrl: String = "",// 类型地址
21 | var cover: String = "",// 封面地址
22 | var author: String = "",// 作者
23 | var desc: String = "",// 描述
24 | //var source: String = "",// 来源网站
25 | var shopName:String="",//书城名字
26 | var chaptersUrl: String = "",// 目录地址
27 | var charCount: Int = 0,// 字数
28 | var chapterCount: Int = 0,// 章节数
29 | var favorite: Int = 0,// 是否收藏
30 | var updateDate: String = "",// 更新时间
31 | var bookFilePath: String = "",// 书籍文件路径
32 | //@Ignore var isLocal: Int = 0,// 是否本地书籍
33 | @Ignore var chapters: MutableList = ArrayList()// 章节列表(临时使用)
34 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/BookShopInfo.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | data class BookShopInfo(val shopName:String="",
4 | val protocol:String="",
5 | val host:String="",
6 | val bookInfoPath:String="",
7 | val bookChapterListPath:String="",
8 | val bookChapterContentPath:String="",
9 | val bookSearchByKeyPath:String="",
10 | val bookSearchByTypePath:String="")
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/BookSignBean.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | import android.os.Parcelable
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * 书签
11 | */
12 | @Entity(tableName = "my_signs",indices = [Index(value = ["chapterUrl"], unique = true)])
13 | @Parcelize
14 | data class BookSignBean constructor(@PrimaryKey(autoGenerate = true)
15 | var id: Int = 0, // 书签id
16 | var bookUrl: String = "", // 书籍地址
17 | var chapterUrl: String = "",// 章节地址
18 | var chapterName: String = "", // 章节名称
19 | var saveTime: Long = System.currentTimeMillis(), // 保存时间
20 | var edit: Boolean = false // 是否编辑
21 | ) :Parcelable
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/ChapterBean.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | import android.os.Parcelable
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * 章节信息
11 | */
12 | @Entity(tableName = "cached_chapters", indices = [Index(value = ["url"], unique = true)])
13 | @Parcelize
14 | data class ChapterBean constructor(@PrimaryKey(autoGenerate = true)
15 | var id: Int = 0, // 章节id
16 | var shopName:String="",//书城名字
17 | //var md5: String = "", // 书籍md5
18 | //var bookName: String = "",// 书名
19 | var bookUrl: String = "",// 书籍地址
20 | var url: String = "", // 章节地址
21 | var name: String = "", // 章节名称
22 | var index: Int = 0,// 章节号
23 | var content: String = "", // 内容
24 | //var source:String = "",//网站host
25 | var start:Long = 0L, // 开始
26 | var end:Long = 0L,// 结束
27 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/ReadRecordBean.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | import android.os.Parcelable
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * 阅读记录
11 | */
12 | @Entity(tableName = "my_read_records", indices = [Index(value = ["bookMd5"], unique = true)])
13 | @Parcelize
14 | data class ReadRecordBean constructor(@PrimaryKey(autoGenerate = true)
15 | var id: Int = 0,// 阅读记录id
16 | var bookUrl: String = "",// 书籍地址
17 | var bookMd5: String = "",//所属的书的id
18 | var chapterPos: Int = 0,//阅读到了第几章
19 | var pagePos: Int = 0 ,//当前的页码
20 | var lastRead:String= ""// 上次阅读的时间
21 | ) :Parcelable
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/Request.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 |
4 | data class RequestSearchPageByKeyword(val shopName:String,val keyword:String="", val page:Int=1)
5 |
6 | data class RequestSearchPageByType(val shopName:String,val typeName:String="", val page:Int=1)
7 |
8 | data class RequestBookInfo(val shopName:String, val bookUrl: String)
9 |
10 | data class RequestAddSign(val mBookUrl: String, val chapterUrl: String, val chapterName: String)
11 |
12 | data class RequestChapter(val mCollBook: BookBean, val start: Int, val limit:Int=100, val cacheContents:Boolean=false)
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/model/Response.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.model
2 |
3 | data class ResponseBookInfo(val bookBean: BookBean)
4 |
5 | data class ResponseChapter(val chapterBeans:List, val cacheContents:Boolean=false)
6 |
7 | data class ResponseSearchPageByKeyword(val keyword:String="", val currentPage:Int=1, val totalPage:Int=1, val bookBeans: ArrayList = ArrayList())
8 |
9 | data class ResponseSearchPageByType(val typeName:String="",val currentPage:Int=1, val totalPage:Int=1, val bookBeans: ArrayList = ArrayList())
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/network/HtmlClient.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.network
2 |
3 | import com.woodnoisu.reader.model.*
4 | import com.woodnoisu.reader.network.parse.BQGParse
5 | import com.woodnoisu.reader.network.parse.HtmlParse
6 | import com.woodnoisu.reader.network.parse.QWYDParse
7 | import javax.inject.Inject
8 | import kotlin.collections.ArrayList
9 |
10 | class HtmlClient @Inject constructor(htmlService: HtmlService) {
11 |
12 | private val parseMap: Map =
13 | mapOf("全文阅读" to QWYDParse(htmlService),
14 | "笔趣阁" to BQGParse(htmlService))
15 |
16 | /**
17 | * 获取网站
18 | */
19 | fun getParseArray():List{
20 | return parseMap.keys.toList()
21 | }
22 |
23 | /**
24 | * 获取类型
25 | */
26 | fun getTypeArray(shopName:String):List{
27 | return parseMap[shopName]?.typeMap?.keys!!.toList()
28 | }
29 |
30 | /**
31 | * 获取书籍信息
32 | */
33 | suspend fun getBookInfo(shopName:String,bookUrl: String): BookBean? {
34 | val parse = parseMap[shopName]
35 | return parse?.getBookInfo(bookUrl)
36 | }
37 |
38 | /**
39 | * 根据关键字搜索
40 | */
41 | suspend fun getSearchByKeyword(shopName:String,keyword: String, page: Int): ResponseSearchPageByKeyword {
42 | val parse = parseMap[shopName]
43 | if(parse!=null){
44 | return parse.getSearchByKeyword(keyword,page)
45 | }
46 | return ResponseSearchPageByKeyword()
47 | }
48 |
49 | /**
50 | * 根据类型搜索
51 | */
52 | suspend fun getSearchByType(
53 | shopName:String,
54 | typeName: String,
55 | page: Int
56 | ): ResponseSearchPageByType {
57 | val parse = parseMap[shopName]
58 | if (parse != null) {
59 | return parse.getSearchByType(typeName, page)
60 | }
61 | return ResponseSearchPageByType()
62 | }
63 |
64 | /**
65 | * 获取章节列表
66 | */
67 | suspend fun getChapterList(
68 | shopName:String,
69 | bookUrl: String,
70 | chaptersUrl: String,
71 | startCharter:Int,
72 | limitCharter:Int
73 | ): ArrayList {
74 | val parse = parseMap[shopName]
75 | if (parse != null) {
76 | return parse.getChapterList(bookUrl, chaptersUrl,startCharter,limitCharter)
77 | }
78 | return ArrayList()
79 | }
80 |
81 | /**
82 | * 获取章节内容
83 | */
84 | suspend fun getChapterContent(shopName:String, chapterUrl: String): String? {
85 | val parse = parseMap[shopName]
86 | return parse?.getChapterContent(chapterUrl)
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/network/HtmlService.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.network
2 |
3 | import com.woodnoisu.reader.constant.Constant.UserAgent
4 | import com.woodnoisu.reader.utils.LogUtil
5 | import com.woodnoisu.reader.utils.showToast
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.withContext
8 | import okhttp3.FormBody
9 | import okhttp3.OkHttpClient
10 | import okhttp3.Request
11 |
12 |
13 | class HtmlService {
14 |
15 | /**
16 | * 获取网页
17 | */
18 | suspend fun getHtml(url: String, head: Map): String? {
19 | try {
20 | return withContext(Dispatchers.IO) { get(url,head) }
21 | } catch (e: java.io.IOException) {
22 | LogUtil.e(e.toString())
23 | showToast(e.toString())
24 | }
25 | return null
26 | }
27 |
28 | /**
29 | * 获取网页
30 | */
31 | suspend fun postHtml(url: String,head: Map,body: Map): String? {
32 | try {
33 | return withContext(Dispatchers.IO) { post(url,head,body) }
34 | } catch (e: java.io.IOException) {
35 | LogUtil.e(e.toString())
36 | showToast(e.toString())
37 | }
38 | return null
39 | }
40 |
41 |
42 | /**
43 | * 请求
44 | */
45 | private fun get(url: String,head: Map): String? {
46 | val client = OkHttpClient()
47 | val requestBuilder = Request.Builder()
48 | .removeHeader("User-Agent")
49 | .addHeader("User-Agent", UserAgent)
50 | for (hd in head){
51 | requestBuilder.addHeader(hd.key, hd.value)
52 | }
53 | val request = requestBuilder.url(url)
54 | .build()
55 | val response = client.newCall(request).execute()
56 | return response.body?.string()
57 | }
58 |
59 | /**
60 | * 请求
61 | */
62 | private fun post(url: String,head: Map,body: Map): String? {
63 | val client = OkHttpClient()
64 | val requestBuilder = Request.Builder()
65 | .removeHeader("User-Agent")
66 | .addHeader("User-Agent", UserAgent)
67 | for (hd in head){
68 | requestBuilder.addHeader(hd.key, hd.value)
69 | }
70 | val formBodyBuilder = FormBody.Builder()
71 | for (bd in body){
72 | formBodyBuilder.add(bd.key, bd.value)
73 | }
74 | val request = requestBuilder.url(url)
75 | .post(formBodyBuilder.build())
76 | .build()
77 | val response = client.newCall(request).execute()
78 | return response.body?.string()
79 | }
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/network/HttpRequestInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.network
2 |
3 | import com.woodnoisu.reader.constant.Constant.UserAgent
4 | import com.woodnoisu.reader.utils.LogUtil
5 | import okhttp3.Interceptor
6 | import okhttp3.Response
7 |
8 | /**
9 | * 拦截器
10 | */
11 | class HttpRequestInterceptor : Interceptor {
12 | override fun intercept(chain: Interceptor.Chain): Response {
13 | val request = chain.request()
14 | .newBuilder()
15 | .removeHeader("User-Agent")
16 | .addHeader("User-Agent", UserAgent)
17 | .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
18 | .addHeader("Accept-Encoding", "gzip, deflate")
19 | .addHeader("Connection", "keep-alive")
20 | .addHeader("Accept", "*/*")
21 | .build()
22 | LogUtil.i(request.toString())
23 | return chain.proceed(request)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/network/parse/HtmlParse.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.network.parse
2 |
3 | import com.woodnoisu.reader.model.BookBean
4 | import com.woodnoisu.reader.model.ChapterBean
5 | import com.woodnoisu.reader.model.ResponseSearchPageByKeyword
6 | import com.woodnoisu.reader.model.ResponseSearchPageByType
7 | import com.woodnoisu.reader.network.HtmlService
8 |
9 | open class HtmlParse(protected val htmlService: HtmlService) {
10 | /**
11 | * 类型
12 | */
13 | open val typeMap: Map = mapOf()
14 |
15 | /**
16 | * 获取书籍信息
17 | */
18 | open suspend fun getBookInfo(bookUrl: String): BookBean? {
19 | return null
20 | }
21 |
22 | /**
23 | * 根据关键字搜索
24 | */
25 | open suspend fun getSearchByKeyword(keyword: String, page: Int): ResponseSearchPageByKeyword {
26 | return ResponseSearchPageByKeyword()
27 | }
28 |
29 | /**
30 | * 根据类型搜索
31 | */
32 | open suspend fun getSearchByType(
33 | typeName: String,
34 | page: Int
35 | ): ResponseSearchPageByType {
36 | return ResponseSearchPageByType()
37 | }
38 |
39 |
40 | /**
41 | * 获取章节列表
42 | */
43 | open suspend fun getChapterList(
44 | bookUrl: String,
45 | chaptersUrl: String,
46 | startCharter:Int,
47 | limitCharter:Int
48 | ): ArrayList {
49 | return ArrayList()
50 | }
51 |
52 |
53 | /**
54 | * 获取章节内容
55 | */
56 | open suspend fun getChapterContent(chapterUrl: String): String? {
57 | return ""
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/AppDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.woodnoisu.reader.model.BookBean
6 | import com.woodnoisu.reader.model.BookSignBean
7 | import com.woodnoisu.reader.model.ChapterBean
8 | import com.woodnoisu.reader.model.ReadRecordBean
9 |
10 | /**
11 | * 数据库操作类
12 | */
13 | @Database(entities = [
14 | BookBean::class,
15 | ChapterBean::class,
16 | BookSignBean::class,
17 | ReadRecordBean::class], version = 1, exportSchema = true)
18 | //@TypeConverters(value = [TypeResponseConverter::class])
19 | abstract class AppDataBase : RoomDatabase() {
20 | abstract fun bookDao(): BookDao
21 | abstract fun chapterDao(): ChapterDao
22 | abstract fun bookSignDao(): BookSignDao
23 | abstract fun readRecordDao(): ReadRecordDao
24 |
25 | // companion object {
26 | //
27 | // @Volatile
28 | // private var instance: AppDataBase? = null
29 | //
30 | // fun getDBInstace2(): AppDataBase {
31 | //
32 | // if (instance == null) {
33 | // synchronized(AppDataBase::class) {
34 | // if (instance == null) {
35 | // val MIGRATION_v_v: Migration? = getMigration()
36 | // if(MIGRATION_v_v==null){
37 | // instance = Room.databaseBuilder(
38 | // App.context!!,
39 | // AppDataBase::class.java,
40 | // "db_novel.db"
41 | // ).allowMainThreadQueries()
42 | // .build()
43 | // }
44 | // else{
45 | // instance = Room.databaseBuilder(
46 | // App.context!!,
47 | // AppDataBase::class.java,
48 | // "db_novel.db"
49 | // ).allowMainThreadQueries()
50 | // .addMigrations(MIGRATION_v_v)
51 | // .build()
52 | // }
53 | // }
54 | // }
55 | // }
56 | // return instance!!
57 | // }
58 | //
59 | // fun getMigration(): Migration?{
60 | // return null
61 | // //第一步,修改版本号,如要添加库的话要在entities里面添加新的表类
62 | // //第二步,新建需要添加的表的entities和dao,如果不需要新的表,这一步可以省略
63 | // //第三步,如下:
64 | //// MIGRATION_v_v = object : Migration(1, 2) {
65 | //// override fun migrate(database: SupportSQLiteDatabase) {
66 | //// //为旧表添加新的字段
67 | //// //database.execSQL("ALTER TABLE User "
68 | //// //+ " ADD COLUMN book_id TEXT");
69 | //// //创建新的数据表
70 | //// database.execSQL("CREATE TABLE IF NOT EXISTS `book` (`book_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT)")
71 | //// }
72 | //// }
73 | // //升级数据库addMigrations(MIGRATION_1_2)
74 | // }
75 | // }
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/BaseBeanDao.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.*
4 |
5 | @Dao
6 | interface BaseBeanDao
7 | {
8 | @Insert(onConflict = OnConflictStrategy.REPLACE)
9 | suspend fun insert(element: T)
10 |
11 | @Insert(onConflict = OnConflictStrategy.REPLACE)
12 | suspend fun insertSome(vararg elements:T)
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | suspend fun insertList(list: List)
16 |
17 | @Update
18 | suspend fun update(element: T)
19 |
20 | @Update
21 | suspend fun updateSome(vararg elements:T)
22 |
23 | @Update
24 | suspend fun updateList(elements:List)
25 |
26 | @Delete
27 | suspend fun delete(element: T)
28 |
29 | @Delete
30 | suspend fun deleteSome(vararg elements:T)
31 |
32 | @Delete
33 | suspend fun deleteList(elements:List)
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/BookDao.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.*
4 | import com.woodnoisu.reader.model.BookBean
5 |
6 | @Dao
7 | interface BookDao: BaseBeanDao {
8 | @Query("SELECT * FROM my_shelf WHERE url = :bookUrl")
9 | suspend fun getByUrl(bookUrl: String): BookBean?
10 |
11 | @Query("SELECT * FROM my_shelf WHERE url = :bookUrl AND `favorite`==1")
12 | suspend fun getByFavoriteAndUrl(bookUrl: String): BookBean?
13 |
14 | @Query("SELECT * FROM my_shelf")
15 | suspend fun getList(): List
16 |
17 | @Query("SELECT * FROM my_shelf WHERE `favorite`== 1")
18 | suspend fun getListByFavorite(): List
19 |
20 | @Query("SELECT * FROM my_shelf WHERE name IN (:names)")
21 | suspend fun getListByNames(names: List): List
22 |
23 | @Query("SELECT * FROM my_shelf WHERE `favorite`== 1 and name LIKE '%' || :bookName || '%'")
24 | suspend fun getListByName(bookName: String): List
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/BookSignDao.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.*
4 | import com.woodnoisu.reader.model.BookSignBean
5 |
6 | @Dao
7 | interface BookSignDao:
8 | BaseBeanDao {
9 | @Query("SELECT * FROM my_signs WHERE `chapterUrl`== :chapterUrl")
10 | suspend fun getByChapterUrl(chapterUrl: String): BookSignBean?
11 |
12 | @Query("SELECT * FROM my_signs")
13 | suspend fun getList(): List
14 |
15 | @Query("SELECT * FROM my_signs WHERE `bookUrl`== :bookUrl")
16 | suspend fun getListByBookUrl(bookUrl: String): MutableList
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/ChapterDao.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.*
4 | import com.woodnoisu.reader.model.ChapterBean
5 |
6 | @Dao
7 | interface ChapterDao:
8 | BaseBeanDao {
9 | @Query("SELECT * FROM cached_chapters WHERE url=:url")
10 | suspend fun get(url: String): ChapterBean?
11 |
12 | @Query("SELECT content FROM cached_chapters WHERE url=:url")
13 | suspend fun getContentByUrl(url: String): String?
14 |
15 | @Query("SELECT * FROM cached_chapters WHERE bookUrl=:bookUrl AND `index`>=:start ORDER BY `index` ASC LIMIT :limit")
16 | suspend fun getListByBookUrl(bookUrl: String, start: Int = 0, limit: Int = 100): MutableList
17 |
18 | @Query("SELECT count(*) FROM cached_chapters WHERE bookUrl=:bookUrl")
19 | suspend fun getListCountByBookUrl(bookUrl: String): Int
20 |
21 | @Query("DELETE FROM cached_chapters WHERE bookUrl=:bookUrl")
22 | suspend fun deleteByBookUrl(bookUrl: String)
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/ReadRecordDao.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.*
4 | import com.woodnoisu.reader.model.ReadRecordBean
5 |
6 | @Dao
7 | interface ReadRecordDao:
8 | BaseBeanDao {
9 | @Query("SELECT * FROM my_read_records WHERE `bookMd5`== :bookMd5")
10 | suspend fun getByMd5(bookMd5: String): ReadRecordBean?
11 |
12 | @Query("SELECT * FROM my_read_records")
13 | suspend fun getList(): List
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/persistence/TypeResponseConverter.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.persistence
2 |
3 | import androidx.room.ProvidedTypeConverter
4 | import androidx.room.TypeConverter
5 | import com.squareup.moshi.JsonAdapter
6 | import com.squareup.moshi.Moshi
7 | import com.squareup.moshi.Types
8 | import javax.inject.Inject
9 |
10 | @ProvidedTypeConverter
11 | class TypeResponseConverter @Inject constructor(
12 | private val moshi: Moshi
13 | ) {
14 |
15 | // @TypeConverter
16 | // fun fromString(value: String): List? {
17 | // val listType = Types.newParameterizedType(List::class.java, PokemonInfo.TypeResponse::class.java)
18 | // val adapter: JsonAdapter> = moshi.adapter(listType)
19 | // return adapter.fromJson(value)
20 | // }
21 | //
22 | // @TypeConverter
23 | // fun fromInfoType(type: List?): String {
24 | // val listType = Types.newParameterizedType(List::class.java, PokemonInfo.TypeResponse::class.java)
25 | // val adapter: JsonAdapter> = moshi.adapter(listType)
26 | // return adapter.toJson(type)
27 | // }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/repository/Repository.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.repository
2 |
3 | /**
4 | * 存储库是用于配置基本存储库类的接口。
5 | */
6 | interface Repository
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/repository/ShelfRepository.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.repository
2 |
3 | import androidx.annotation.WorkerThread
4 | import com.woodnoisu.reader.model.BookBean
5 | import com.woodnoisu.reader.persistence.BookDao
6 | import com.woodnoisu.reader.persistence.BookSignDao
7 | import com.woodnoisu.reader.persistence.ChapterDao
8 | import com.woodnoisu.reader.persistence.ReadRecordDao
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.flow.flow
11 | import kotlinx.coroutines.flow.flowOn
12 | import javax.inject.Inject
13 |
14 | /**
15 | * 搜索存储器
16 | */
17 | class ShelfRepository @Inject constructor(
18 | private val bookDao: BookDao,
19 | private val bookSignDao: BookSignDao,
20 | private val chapterDao: ChapterDao,
21 | private val readRecordDao: ReadRecordDao
22 | ):Repository {
23 | /**
24 | * 填充书架
25 | */
26 | @WorkerThread
27 | suspend fun fetchBookList(
28 | keyword: String,
29 | onSuccess: (String) -> Unit,
30 | onError: (String) -> Unit
31 | )= flow {
32 | try {
33 | val bookList = if(keyword.isNullOrBlank()){
34 | //没有关键字搜索,则显示全部
35 | bookDao.getListByFavorite()
36 | }else{
37 | //有关键字搜索,显示搜索内容
38 | bookDao.getListByName(keyword)
39 | }
40 | emit(bookList)
41 | onSuccess("获取成功")
42 | }catch (e:Exception){
43 | onError(e.toString())
44 | }
45 | }.flowOn(Dispatchers.IO)
46 |
47 | /**
48 | * 取消订阅书籍
49 | */
50 | @WorkerThread
51 | suspend fun deleteBook(
52 | book: BookBean,
53 | onSuccess: (String) -> Unit,
54 | onError: (String) -> Unit
55 | )= flow {
56 | try {
57 | if (book != null) {
58 | book.favorite = 0
59 | bookDao.update(book)
60 | }
61 | emit(book)
62 | onSuccess("删除成功")
63 | } catch (e: Exception) {
64 | onError(e.toString())
65 | }
66 | }.flowOn(Dispatchers.IO)
67 |
68 | /**
69 | * 订阅书籍
70 | */
71 | @WorkerThread
72 | suspend fun insertBook(
73 | book: BookBean,
74 | onSuccess: (String) -> Unit,
75 | onError: (String) -> Unit
76 | )= flow {
77 | try {
78 | if (book != null) {
79 | book.favorite = 1
80 | bookDao.insert(book)
81 | }
82 | emit(book)
83 | onSuccess("加入书架成功")
84 | } catch (e: Exception) {
85 | onError(e.toString())
86 | }
87 | }.flowOn(Dispatchers.IO)
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/AboutActivity.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui
2 |
3 | import com.woodnoisu.reader.R
4 | import com.woodnoisu.reader.base.BaseActivity
5 |
6 | /**
7 | * 关于窗口
8 | */
9 | class AboutActivity: BaseActivity() {
10 |
11 | /**
12 | * 获取界面id
13 | */
14 | override fun getRLayout():Int{
15 | return R.layout.activity_about
16 | }
17 |
18 | /**
19 | * 初始化界面
20 | */
21 | override fun initView(){}
22 |
23 | /**
24 | * 初始化监听
25 | */
26 | override fun initListener(){}
27 |
28 | /**
29 | * 初始化数据
30 | */
31 | override fun initData(){}
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/StartActivity.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui
2 |
3 | import com.woodnoisu.reader.R
4 | import com.woodnoisu.reader.base.BaseActivity
5 | import com.woodnoisu.reader.ui.main.MainActivity
6 | import kotlinx.coroutines.*
7 |
8 | class StartActivity: BaseActivity() {
9 | /**
10 | * 获取界面id
11 | */
12 | override fun getRLayout():Int{
13 | return R.layout.activity_start
14 | }
15 |
16 | /**
17 | * 初始化界面
18 | */
19 | override fun initView(){}
20 |
21 | /**
22 | * 初始化监听
23 | */
24 | override fun initListener(){}
25 |
26 | /**
27 | * 初始化数据
28 | */
29 | override fun initData(){
30 | // 主线程
31 | GlobalScope.launch(Dispatchers.Main) {
32 | delay(500)
33 |
34 | MainActivity.startFromActivity(this@StartActivity)
35 |
36 | delay(500)
37 | //完成
38 | finish()
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.main
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.app.ActivityOptions
6 | import android.content.Intent
7 | import android.widget.Toast
8 | import androidx.activity.result.contract.ActivityResultContracts
9 | import androidx.activity.viewModels
10 | import androidx.annotation.VisibleForTesting
11 | import androidx.fragment.app.FragmentActivity
12 | import androidx.lifecycle.Observer
13 | import androidx.viewpager2.widget.ViewPager2
14 | import com.woodnoisu.reader.R
15 | import com.woodnoisu.reader.base.BaseActivity
16 | import dagger.hilt.android.AndroidEntryPoint
17 | import kotlinx.android.synthetic.main.activity_main.*
18 |
19 | @AndroidEntryPoint
20 | class MainActivity : BaseActivity() {
21 | @VisibleForTesting
22 | val viewModel: MainViewModel by viewModels()
23 |
24 | /**
25 | * 获取界面id
26 | */
27 | override fun getRLayout():Int{
28 | return R.layout.activity_main
29 | }
30 |
31 | /**
32 | * 初始化界面
33 | */
34 | override fun initView(){
35 | viewPager.adapter =
36 | MainAdapter(this@MainActivity)
37 | //申请权限
38 | requestPermission()
39 | }
40 |
41 | /**
42 | * 初始化监听
43 | */
44 | override fun initListener() {
45 | // 设置切换标签事件
46 | bottom_navigation.setOnNavigationItemSelectedListener {
47 | when (it.itemId) {
48 | R.id.navigation_square -> {
49 | if (viewPager.currentItem != 0) {
50 | viewPager.currentItem = 0
51 | }
52 | return@setOnNavigationItemSelectedListener true
53 | }
54 | R.id.navigation_shelf -> {
55 | if (viewPager.currentItem != 1) {
56 | viewPager.currentItem = 1
57 | }
58 | return@setOnNavigationItemSelectedListener true
59 | }
60 | R.id.navigation_me -> {
61 | if (viewPager.currentItem != 2) {
62 | viewPager.currentItem = 2
63 | }
64 | return@setOnNavigationItemSelectedListener true
65 | }
66 | }
67 | false
68 | }
69 |
70 | //中间区域注册事件
71 | viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
72 | override fun onPageSelected(position: Int) {
73 | val menuItem = bottom_navigation.menu.getItem(position)
74 | if (!menuItem.isChecked) {
75 | menuItem.isChecked = true
76 | }
77 | }
78 | })
79 |
80 | //错误通知事件
81 | viewModel.toast.observe(this, Observer {
82 | if (!it.isNullOrBlank()) {
83 | Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
84 | }
85 | })
86 | }
87 |
88 | /**
89 | * 初始化数据
90 | */
91 | override fun initData(){}
92 |
93 | /**
94 | * 申请权限
95 | */
96 | private fun requestPermission(){
97 | //权限要求
98 | val permission = registerForActivityResult(ActivityResultContracts.RequestPermission()){
99 | if(!it) {
100 | viewModel.toastMsg("请开通相关权限,否则无法正常使用本应用!")
101 | }
102 | }
103 |
104 | //申请权限
105 | permission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
106 | }
107 |
108 | /**
109 | * 静态内容
110 | */
111 | companion object {
112 |
113 | fun startFromActivity(activity: Activity) {
114 | //设置配置
115 | val options = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()
116 | //设置启动窗体
117 | val intent = Intent(activity, MainActivity::class.java)
118 | //启动窗体
119 | activity.startActivity(intent,options)
120 |
121 | }
122 |
123 | fun startFromFragment(activity: FragmentActivity?) {
124 | //设置配置
125 | val options = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()
126 | //设置启动窗体
127 | val intent = Intent(activity, MainActivity::class.java)
128 | //启动窗体
129 | activity?.startActivity(intent,options)
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/main/MainAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.main
2 |
3 | import android.util.SparseArray
4 | import androidx.fragment.app.Fragment
5 | import androidx.fragment.app.FragmentActivity
6 | import androidx.viewpager2.adapter.FragmentStateAdapter
7 | import com.woodnoisu.reader.ui.me.MeFragment
8 | import com.woodnoisu.reader.ui.shelf.ShelfFragment
9 | import com.woodnoisu.reader.ui.square.SquareFragment
10 |
11 |
12 | class MainAdapter(fragmentActivity: FragmentActivity)
13 | : FragmentStateAdapter(fragmentActivity) {
14 | // 基础窗口
15 | private val fragments: SparseArray = SparseArray(3)
16 |
17 | // 初始化
18 | init {
19 | fragments.append(0, SquareFragment())
20 | fragments.append(1, ShelfFragment())
21 | fragments.append(2, MeFragment())
22 | }
23 |
24 | // 重新获取项目序号
25 | override fun getItemCount(): Int {
26 | return fragments.size()
27 | }
28 |
29 | //创建fragment
30 | override fun createFragment(position: Int): Fragment {
31 | return fragments[position]
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/main/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.main
2 |
3 | import androidx.hilt.Assisted
4 | import androidx.lifecycle.*
5 | import com.woodnoisu.reader.base.BaseViewModel
6 | import com.woodnoisu.reader.utils.LogUtil
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class MainViewModel @Inject constructor(
12 | @Assisted private val savedStateHandle: SavedStateHandle
13 | ) : BaseViewModel() {
14 | init {
15 | LogUtil.i("init MainViewModel")
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/me/MeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.me
2 |
3 | import android.app.AlertDialog
4 | import android.content.Intent
5 | import android.net.Uri
6 | import com.woodnoisu.reader.R
7 | import com.woodnoisu.reader.base.BaseFragment
8 | import com.woodnoisu.reader.constant.Constant
9 | import com.woodnoisu.reader.utils.FileUtil
10 | import com.woodnoisu.reader.utils.SpUtil
11 | import kotlinx.android.synthetic.main.fragment_me.*
12 | import java.io.File
13 |
14 |
15 | /**
16 | * 我的窗口
17 | */
18 |
19 | class MeFragment : BaseFragment() {
20 | /**
21 | * 获取界面id
22 | */
23 | override fun getRLayout():Int = R.layout.fragment_me
24 |
25 | /**
26 | * 初始化界面
27 | */
28 | override fun initView(){}
29 |
30 | /**
31 | * 初始化监听
32 | */
33 | override fun initListener(){
34 | // 音量键控制事件
35 | switch_volume.setOnCheckedChangeListener { buttonView, isChecked ->
36 | SpUtil.setBooleanValue("volume_turn_page", isChecked)
37 | }
38 | // 清空缓存事件
39 | clear_cache.setOnClickListener { v ->
40 | AlertDialog.Builder(activity)
41 | .setMessage("确定要清除缓存么(将会删除所有已缓存章节)?").setNegativeButton("取消", null)
42 | .setPositiveButton("确定") { _, _ ->
43 | FileUtil.deleteFile(Constant.BOOK_CACHE_PATH)
44 | tv_cache.text = "0kb"
45 | }.show()
46 | }
47 | // 个人主页
48 | tv_about.setOnClickListener {
49 | // 跳转到作者的github
50 | startActivity(
51 | Intent(
52 | Intent.ACTION_VIEW,
53 | Uri.parse("https://github.com/woodwen/reader")
54 | )
55 | )
56 | }
57 |
58 | }
59 |
60 | /**
61 | * 初始化数据
62 | */
63 | override fun initData(){
64 | // 是否音量键控制翻页
65 | switch_volume.isChecked = SpUtil.getBooleanValue("volume_turn_page", true)
66 | // 获取缓存文件大小
67 | val cacheSize = FileUtil.getDirSize(File(Constant.BOOK_CACHE_PATH)) / 1024
68 | //初始化缓存文件大小单位
69 | val unit: String = if (cacheSize in (0..1024)) {
70 | "kb"
71 | } else {
72 | "MB"
73 | }
74 | //附值
75 | tv_cache.text = "$cacheSize$unit"
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/novelRead/MarkAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.novelRead
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.CheckBox
8 | import android.widget.TextView
9 | import androidx.recyclerview.widget.RecyclerView
10 | import com.woodnoisu.reader.R
11 | import com.woodnoisu.reader.model.BookSignBean
12 | import java.util.ArrayList
13 |
14 | /**
15 | * 笔记适配
16 | */
17 | class MarkAdapter : RecyclerView.Adapter() {
18 |
19 | class MarkHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
20 | // 标签对象
21 | var mTvMark: TextView = itemView.findViewById(R.id.tvMarkItem)
22 | // 标签选择框
23 | var mCheck: CheckBox = itemView.findViewById(R.id.checkbox)
24 | }
25 |
26 | // 上下文
27 | private var mContext: Context? = null
28 |
29 | // 标记队列
30 | private val mList : MutableList = ArrayList()
31 |
32 | // 编辑内容
33 | var edit: Boolean = false
34 | set(edit) {
35 | field = edit
36 | notifyDataSetChanged()
37 | }
38 |
39 | // 选择列表
40 | val selectList: List
41 | get() {
42 | return mList.filter {
43 | return@filter it.edit
44 | }
45 | }
46 |
47 | /**
48 | * 创建holder
49 | */
50 | override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
51 | if (mContext == null) {
52 | mContext = viewGroup.context
53 | }
54 | val view: View =
55 | LayoutInflater.from(mContext).inflate(R.layout.rlv_item_mark, viewGroup, false)
56 | return MarkHolder(view)
57 | }
58 |
59 | /**
60 | * 绑定Holder
61 | */
62 | override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
63 | if (viewHolder is MarkHolder) {
64 | if (this.edit) {
65 | viewHolder.mCheck.visibility = View.VISIBLE
66 | viewHolder.mCheck.setOnCheckedChangeListener { compoundButton, b ->
67 | mList[i].edit = b
68 | }
69 | } else {
70 | viewHolder.mCheck.visibility = View.GONE
71 | }
72 | viewHolder.mTvMark.text = mList[i].chapterName
73 | viewHolder.mCheck.isChecked = mList[i].edit
74 | }
75 | }
76 |
77 | /**
78 | * 获取项目数量
79 | */
80 | override fun getItemCount(): Int {
81 | return mList.size
82 | }
83 |
84 | /**
85 | * 添加项目
86 | */
87 | fun addItem(value: BookSignBean) {
88 | mList.add(value)
89 | notifyDataSetChanged()
90 | }
91 |
92 | /**
93 | * 添加指定位置的项目
94 | */
95 | fun addItem(index: Int, value: BookSignBean) {
96 | mList.add(index, value)
97 | notifyDataSetChanged()
98 | }
99 |
100 | /**
101 | * 批量添加项目
102 | */
103 | fun addItems(values: List) {
104 | mList.addAll(values)
105 | notifyDataSetChanged()
106 | }
107 |
108 | /**
109 | * 移除项目
110 | */
111 | fun removeItem(value: BookSignBean) {
112 | mList.remove(value)
113 | notifyDataSetChanged()
114 | }
115 |
116 | /**
117 | * 刷新容器
118 | */
119 | fun refreshItems(list: List) {
120 | mList.clear()
121 | mList.addAll(list)
122 | notifyDataSetChanged()
123 | }
124 |
125 | /**
126 | * 清理容器
127 | */
128 | fun clear() {
129 | mList.clear()
130 | notifyDataSetChanged()
131 | }
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/shelf/ShelfViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.shelf
2 |
3 | import androidx.annotation.MainThread
4 | import androidx.lifecycle.*
5 | import com.woodnoisu.reader.base.BaseViewModel
6 | import com.woodnoisu.reader.model.BookBean
7 | import com.woodnoisu.reader.repository.ShelfRepository
8 | import com.woodnoisu.reader.utils.LogUtil
9 | import dagger.assisted.AssistedInject
10 |
11 |
12 | class ShelfViewModel @AssistedInject constructor(
13 | shelfRepository: ShelfRepository
14 | ) : BaseViewModel() {
15 | private val bookListFetching: MutableLiveData = MutableLiveData()
16 | val bookList: LiveData>
17 |
18 | private val bookInserting: MutableLiveData = MutableLiveData()
19 | val bookInserted: LiveData
20 |
21 | private val bookDeleting: MutableLiveData = MutableLiveData()
22 | val bookDeleted: LiveData
23 |
24 |
25 | init {
26 | LogUtil.i("init ShelfViewModel")
27 |
28 | //获取本地书籍
29 | bookList = bookListFetching.switchMap {
30 | _isLoading.postValue(true)
31 | launchOnViewModelScope {
32 | shelfRepository.fetchBookList(
33 | keyword = it,
34 | onSuccess = {
35 | _isLoading.postValue(false)
36 | },
37 | onError = {
38 | _isLoading.postValue(false)
39 | _toast.postValue(it)
40 | }
41 | ).asLiveData()
42 | }
43 | }
44 |
45 | //新增书籍
46 | bookInserted = bookInserting.switchMap {
47 | _isLoading.postValue(true)
48 | launchOnViewModelScope {
49 | shelfRepository.insertBook(
50 | book = it,
51 | onSuccess = {
52 | _isLoading.postValue(false)
53 | },
54 | onError = {
55 | _isLoading.postValue(false)
56 | _toast.postValue(it)
57 | }
58 | ).asLiveData()
59 | }
60 | }
61 |
62 | //删除书籍
63 | bookDeleted = bookDeleting.switchMap {
64 | _isLoading.postValue(true)
65 | launchOnViewModelScope {
66 | shelfRepository.deleteBook(
67 | book = it,
68 | onSuccess = {
69 | _isLoading.postValue(false)
70 | },
71 | onError = {
72 | _isLoading.postValue(false)
73 | _toast.postValue(it)
74 | }
75 | ).asLiveData()
76 | }
77 | }
78 | }
79 |
80 | @dagger.assisted.AssistedFactory
81 | interface AssistedFactory {
82 | fun create(): ShelfViewModel
83 | }
84 |
85 | companion object {
86 | fun provideFactory(
87 | assistedFactory: AssistedFactory
88 | ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
89 | @Suppress("UNCHECKED_CAST")
90 | override fun create(modelClass: Class): T {
91 | return assistedFactory.create() as T
92 | }
93 | }
94 | }
95 |
96 | @MainThread
97 | fun insertBook(book: BookBean) {
98 | bookInserting.value = book
99 | }
100 |
101 | @MainThread
102 | fun deleteBook(book: BookBean) {
103 | bookDeleting.value = book
104 | }
105 |
106 | @MainThread
107 | fun fetchBookList(keyword: String) {
108 | bookListFetching.postValue(keyword)
109 | }
110 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/LocalPageLoader.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page
2 |
3 | import android.content.Context
4 | import com.woodnoisu.reader.model.BookBean
5 | import com.woodnoisu.reader.model.ChapterBean
6 | import com.woodnoisu.reader.utils.*
7 | import java.io.*
8 | import kotlin.jvm.Throws
9 |
10 | /**
11 | * 本地书籍加载器
12 | */
13 | class LocalPageLoader(pageView: PageView,
14 | collBook: BookBean
15 | ) : PageLoader(pageView, collBook) {
16 |
17 | //获取书本的文件
18 | private var mBookFile: File? = null
19 |
20 | //编码类型
21 | private var mCharset: String = ""
22 |
23 | //上下文
24 | private var mContext: Context
25 |
26 | /**
27 | * 初始化
28 | */
29 | init {
30 | mStatus = STATUS_PARING
31 | mContext = pageView.context
32 | }
33 |
34 | /**
35 | * 刷新章节
36 | */
37 | override fun refreshChapterList() {
38 | // 对于文件是否存在,或者为空的判断,不作处理。 ==> 在文件打开前处理过了。
39 | val mb = File(mCollBook.bookFilePath)
40 | mBookFile = mb
41 | //获取文件编码
42 | mCharset = FileUtil.getCharset(mb.absolutePath)
43 |
44 | // 判断文件是否已经加载过,并具有缓存
45 | if (mCollBook.chapters.isNotEmpty()) {
46 | mChapterList.clear()
47 | mChapterList.addAll(mCollBook.chapters)
48 | isChapterListPrepare = true
49 | //提示目录加载完成
50 | mPageChangeListener?.onChaptersFinished(mChapterList)
51 | // 加载并显示当前章节
52 | openChapter()
53 | }
54 | }
55 |
56 | /**
57 | * 打开指定章节
58 | */
59 | override fun openSpecifyChapter(specifyChapter: Int) {}
60 |
61 | /**
62 | * 获取章节阅读器
63 | */
64 | @Throws(Exception::class)
65 | override fun getChapterReader(chapter: ChapterBean): BufferedReader {
66 | //从文件中获取数据
67 | val content = getChapterContent(chapter)
68 | val bis = ByteArrayInputStream(content)
69 | return BufferedReader(InputStreamReader(bis, mCharset))
70 | }
71 |
72 | /**
73 | * 判断是否有章节数据
74 | */
75 | override fun hasChapterData(chapter: ChapterBean): Boolean {
76 | return true
77 | }
78 |
79 | /**
80 | * 从文件中提取一章的内容
81 | *
82 | * @param chapter
83 | * @return
84 | */
85 | private fun getChapterContent(chapter: ChapterBean): ByteArray? {
86 | var bookStream: RandomAccessFile? = null
87 | try {
88 | bookStream = RandomAccessFile(mBookFile, "r")
89 | bookStream.seek(chapter.start)
90 | val extent = (chapter.end - chapter.start).toInt()
91 | val content = ByteArray(extent)
92 | bookStream.read(content, 0, extent)
93 | return content
94 | } catch (e: IOException) {
95 | e.printStackTrace()
96 | } finally {
97 | bookStream?.close()
98 | }
99 | return ByteArray(0)
100 | }
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/adapter/PageStyleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.adapter
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import android.os.Build
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.annotation.RequiresApi
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.woodnoisu.reader.ui.widget.page.model.PageStyle
12 | import com.woodnoisu.reader.R
13 | import com.woodnoisu.reader.ui.widget.page.PageLoader
14 | import com.woodnoisu.reader.ui.widget.page.adapter.PageStyleHolder
15 |
16 | /**
17 | * 页面风格适配器
18 | */
19 | class PageStyleAdapter(private val mList: List, private val mPageLoader: PageLoader) :
20 | RecyclerView.Adapter() {
21 | // 上下文
22 | private var mContext: Context? = null
23 | // 当前选中
24 | private var currentChecked: Int = 0
25 |
26 | /**
27 | * 创建holder
28 | */
29 | override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PageStyleHolder {
30 | if (mContext == null) {
31 | mContext = viewGroup.context
32 | }
33 | val view = LayoutInflater.from(mContext).inflate(R.layout.item_read_bg, viewGroup, false)
34 | return PageStyleHolder(view)
35 | }
36 |
37 | /**
38 | * 绑定holder
39 | */
40 | @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
41 | override fun onBindViewHolder(pageHolder: PageStyleHolder, i: Int) {
42 | pageHolder.mReadBg.background = mList[i]
43 | pageHolder.mIvChecked.visibility = View.GONE
44 | if (currentChecked == i) {
45 | pageHolder.mIvChecked.visibility = View.VISIBLE
46 | }
47 | pageHolder.itemView.setOnClickListener {
48 | currentChecked = i
49 | notifyDataSetChanged()
50 | mPageLoader.setPageStyle(PageStyle.values()[i])
51 | }
52 | }
53 |
54 | /**
55 | * 获取项目数量
56 | */
57 | override fun getItemCount(): Int {
58 | return mList.size
59 | }
60 |
61 | /**
62 | * 设置当前选中
63 | */
64 | fun setPageStyleChecked(pageStyle: PageStyle) {
65 | currentChecked = pageStyle.ordinal
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/adapter/PageStyleHolder.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.adapter
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.woodnoisu.reader.R
7 |
8 | /**
9 | * 页面风格Holder
10 | */
11 | class PageStyleHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
12 | // 阅读背景
13 | val mReadBg: View = itemView.findViewById(R.id.read_bg_view)
14 | // 背景选中图
15 | val mIvChecked: ImageView = itemView.findViewById(R.id.read_bg_iv_checked)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/anim/CoverPageAnim.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.anim
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Canvas
5 | import android.graphics.Rect
6 | import android.graphics.drawable.GradientDrawable
7 | import android.view.View
8 | import com.woodnoisu.reader.ui.widget.page.event.OnCurPageChangeListener
9 | import com.woodnoisu.reader.ui.widget.page.model.Direction
10 |
11 | /**
12 | * 覆盖模式动画
13 | */
14 | class CoverPageAnim(w: Int, h: Int, view: View, listenerCur: OnCurPageChangeListener) :
15 | HorizonPageAnim(w, h, view, listenerCur) {
16 |
17 | // 原始尺寸
18 | private val mSrcRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
19 | // 目标尺寸
20 | private val mDestRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
21 | //渐变工具
22 | private val mBackShadowDrawableLR: GradientDrawable
23 |
24 | /**
25 | * 初始化
26 | */
27 | init {
28 | val mBackShadowColors = intArrayOf(0x66000000, 0x00000000)
29 | mBackShadowDrawableLR = GradientDrawable(
30 | GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors)
31 | mBackShadowDrawableLR.gradientType = GradientDrawable.LINEAR_GRADIENT
32 | }
33 |
34 | /**
35 | * 绘制静态
36 | */
37 | override fun drawStatic(canvas: Canvas) {
38 | if (isCancel) {
39 | mNextBitmap = mCurBitmap.copy(Bitmap.Config.RGB_565, true)
40 | canvas.drawBitmap(mCurBitmap, 0f, 0f, null)
41 | } else {
42 | canvas.drawBitmap(mNextBitmap, 0f, 0f, null)
43 | }
44 | }
45 |
46 | /**
47 | * 绘制移动
48 | */
49 | override fun drawMove(canvas: Canvas) {
50 |
51 | when (mDirection) {
52 | Direction.NEXT -> {
53 | var dis = (mViewWidth - mStartX + mTouchX).toInt()
54 | if (dis > mViewWidth) {
55 | dis = mViewWidth
56 | }
57 | //计算bitmap截取的区域
58 | mSrcRect.left = mViewWidth - dis
59 | //计算bitmap在canvas显示的区域
60 | mDestRect.right = dis
61 | canvas.drawBitmap(mNextBitmap, 0f, 0f, null)
62 | canvas.drawBitmap(mCurBitmap, mSrcRect, mDestRect, null)
63 | addShadow(dis, canvas)
64 | }
65 | else -> {
66 | mSrcRect.left = (mViewWidth - mTouchX).toInt()
67 | mDestRect.right = mTouchX.toInt()
68 | canvas.drawBitmap(mCurBitmap, 0f, 0f, null)
69 | canvas.drawBitmap(mNextBitmap, mSrcRect, mDestRect, null)
70 | addShadow(mTouchX.toInt(), canvas)
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * 开始动画
77 | */
78 | override fun startAnimExt() {
79 | //super.startAnim()
80 | var dx: Int
81 | when (mDirection) {
82 | Direction.NEXT -> if (isCancel) {
83 | var dis = (mViewWidth - mStartX + mTouchX).toInt()
84 | if (dis > mViewWidth) {
85 | dis = mViewWidth
86 | }
87 | dx = mViewWidth - dis
88 | } else {
89 | dx = (-(mTouchX + (mViewWidth - mStartX))).toInt()
90 | }
91 | else -> if (isCancel) {
92 | dx = (-mTouchX).toInt()
93 | } else {
94 | dx = (mViewWidth - mTouchX).toInt()
95 | }
96 | }
97 |
98 | //滑动速度保持一致
99 | val duration = 400 * Math.abs(dx) / mViewWidth
100 | mScroller.startScroll(mTouchX.toInt(), 0, dx, 0, duration)
101 | }
102 |
103 | /**
104 | * 添加阴影
105 | */
106 | private fun addShadow(left: Int, canvas: Canvas) {
107 | mBackShadowDrawableLR.setBounds(left, 0, left + 30, mScreenHeight)
108 | mBackShadowDrawableLR.draw(canvas)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/anim/NonePageAnim.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.anim
2 |
3 | import android.graphics.Canvas
4 | import android.view.View
5 | import com.woodnoisu.reader.ui.widget.page.event.OnCurPageChangeListener
6 |
7 | /**
8 | * 空动画
9 | */
10 | class NonePageAnim(w: Int, h: Int, view: View, listenerCur: OnCurPageChangeListener) :
11 | HorizonPageAnim(w, h, view, listenerCur) {
12 |
13 | /**
14 | * 绘制静态
15 | */
16 | override fun drawStatic(canvas: Canvas) {
17 | if (isCancel) {
18 | canvas.drawBitmap(mCurBitmap, 0f, 0f, null)
19 | } else {
20 | canvas.drawBitmap(mNextBitmap, 0f, 0f, null)
21 | }
22 | }
23 |
24 | /**
25 | * 绘制移动
26 | */
27 | override fun drawMove(canvas: Canvas) {
28 | if (isCancel) {
29 | canvas.drawBitmap(mCurBitmap, 0f, 0f, null)
30 | } else {
31 | canvas.drawBitmap(mNextBitmap, 0f, 0f, null)
32 | }
33 | }
34 |
35 | /**
36 | * 开始动画
37 | */
38 | override fun startAnimExt() {}
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/anim/SlidePageAnim.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.anim
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Rect
5 | import android.view.View
6 | import com.woodnoisu.reader.ui.widget.page.event.OnCurPageChangeListener
7 | import com.woodnoisu.reader.ui.widget.page.model.Direction
8 | import kotlin.math.abs
9 |
10 | /**
11 | * 横滑动画
12 | */
13 | class SlidePageAnim(w: Int, h: Int, view: View, listenerCur: OnCurPageChangeListener) :
14 | HorizonPageAnim(w, h, view, listenerCur) {
15 |
16 | // 当前原始区域
17 | private val mSrcRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
18 | // 当前目标区域
19 | private val mDestRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
20 | // 下一页原始区域
21 | private val mNextSrcRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
22 | // 下一页目标区域
23 | private val mNextDestRect: Rect = Rect(0, 0, mViewWidth, mViewHeight)
24 |
25 | /**
26 | * 开始动画
27 | */
28 | override fun startAnimExt() {
29 | //super.startAnim()
30 | val dx: Int
31 | when (mDirection) {
32 | Direction.NEXT -> {
33 | if (isCancel) {
34 | var dis = (mScreenWidth - mStartX + mTouchX).toInt()
35 | if (dis > mScreenWidth) {
36 | dis = mScreenWidth
37 | }
38 | dx = mScreenWidth - dis
39 | } else {
40 | dx = (-(mTouchX + (mScreenWidth - mStartX))).toInt()
41 | }
42 | }
43 | else -> {
44 | dx = if (isCancel) {
45 | (-abs(mTouchX - mStartX)).toInt()
46 | } else {
47 | (mScreenWidth - (mTouchX - mStartX)).toInt()
48 | }
49 | }
50 | }
51 | //滑动速度保持一致
52 | val duration = 400 * abs(dx) / mScreenWidth
53 | mScroller.startScroll(mTouchX.toInt(), 0, dx, 0, duration)
54 | }
55 |
56 | /**
57 | * 绘制静态
58 | */
59 | override fun drawStatic(canvas: Canvas) {
60 | if (isCancel) {
61 | canvas.drawBitmap(mCurBitmap, 0f, 0f, null)
62 | } else {
63 | canvas.drawBitmap(mNextBitmap, 0f, 0f, null)
64 | }
65 | }
66 |
67 | /**
68 | * 绘制移动
69 | */
70 | override fun drawMove(canvas: Canvas) {
71 | var dis: Int
72 | when (mDirection) {
73 | Direction.NEXT -> {
74 | //左半边的剩余区域
75 | dis = (mScreenWidth - mStartX + mTouchX).toInt()
76 | if (dis > mScreenWidth) {
77 | dis = mScreenWidth
78 | }
79 | //计算bitmap截取的区域
80 | mSrcRect.left = mScreenWidth - dis
81 | //计算bitmap在canvas显示的区域
82 | mDestRect.right = dis
83 | //计算下一页截取的区域
84 | mNextSrcRect.right = mScreenWidth - dis
85 | //计算下一页在canvas显示的区域
86 | mNextDestRect.left = dis
87 |
88 | canvas.drawBitmap(mNextBitmap, mNextSrcRect, mNextDestRect, null)
89 | canvas.drawBitmap(mCurBitmap, mSrcRect, mDestRect, null)
90 | }
91 | else -> {
92 | dis = (mTouchX - mStartX).toInt()
93 | if (dis < 0) {
94 | dis = 0
95 | mStartX = mTouchX
96 | }
97 | mSrcRect.left = mScreenWidth - dis
98 | mDestRect.right = dis
99 |
100 | //计算下一页截取的区域
101 | mNextSrcRect.right = mScreenWidth - dis
102 | //计算下一页在canvas显示的区域
103 | mNextDestRect.left = dis
104 |
105 | canvas.drawBitmap(mCurBitmap, mNextSrcRect, mNextDestRect, null)
106 | canvas.drawBitmap(mNextBitmap, mSrcRect, mDestRect, null)
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/event/OnCurPageChangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.event
2 |
3 | interface OnCurPageChangeListener {
4 | fun hasPrev(): Boolean
5 | fun hasNext(): Boolean
6 | fun pageCancel()
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/event/OnPageChangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.event
2 |
3 | import com.woodnoisu.reader.model.ChapterBean
4 |
5 | interface OnPageChangeListener {
6 | /**
7 | * 作用:章节切换的时候进行回调
8 | *
9 | * @param pos:切换章节的序号
10 | */
11 | fun onChapterChange(pos: Int)
12 |
13 | /**
14 | * 作用:请求加载章节内容
15 | *
16 | * @param requestChapters:需要下载的章节列表
17 | */
18 | fun chapterContents(requestChapters: MutableList)
19 |
20 | /**
21 | * 作用:章节目录加载完成时候回调
22 | *
23 | * @param chapters:返回章节目录
24 | */
25 | fun onChaptersFinished(chapters: MutableList)
26 |
27 | /**
28 | * 作用:章节页码数量改变之后的回调。==> 字体大小的调整,或者是否关闭虚拟按钮功能都会改变页面的数量。
29 | *
30 | * @param count:页面的数量
31 | */
32 | fun onPageCountChange(count: Int)
33 |
34 | /**
35 | * 作用:当页面改变的时候回调
36 | *
37 | * @param pos:当前的页面的序号
38 | */
39 | fun onPageChange(pos: Int)
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/event/OnTouchListener.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.event
2 |
3 | /**
4 | * 触摸接口
5 | */
6 | interface OnTouchListener {
7 | fun onTouch(): Boolean
8 | fun center()
9 | fun prePage()
10 | fun nextPage()
11 | fun cancel()
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/model/BitmapView.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.model
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Rect
5 |
6 | /**
7 | * 图片view
8 | */
9 | class BitmapView {
10 | internal var bitmap: Bitmap? = null
11 | internal var srcRect: Rect? = null
12 | internal var destRect: Rect? = null
13 | internal var top: Int = 0
14 | internal var bottom: Int = 0
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/model/Direction.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.model
2 |
3 | enum class Direction(val isHorizontal: Boolean) {
4 | NONE(true),
5 | NEXT(true),
6 | PRE(true),
7 | UP(false),
8 | DOWN(false);
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/model/PageMode.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.model
2 |
3 | /**
4 | * 页面类型
5 | */
6 | enum class PageMode {
7 | SIMULATION,
8 | COVER,
9 | SLIDE,
10 | NONE,
11 | SCROLL
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/model/PageStyle.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.model
2 |
3 |
4 | import androidx.annotation.ColorRes
5 | import com.woodnoisu.reader.R
6 |
7 | /**
8 | * 页面风格
9 | */
10 | enum class PageStyle private constructor(@param:ColorRes val fontColor: Int, @param:ColorRes val bgColor: Int) {
11 | BG_0(R.color.read_font_one, R.color.read_bg_one),
12 | BG_1(R.color.read_font_two, R.color.read_bg_two),
13 | // BG_2(R.color.nb_read_font_3, R.color.nb_read_bg_3),
14 | BG_3(R.color.read_font_four, R.color.read_bg_four),
15 | BG_4(R.color.read_font_five, R.color.read_bg_five),
16 | NIGHT(R.color.read_font_night, R.color.read_bg_night)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/model/TxtPage.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.model
2 |
3 | /**
4 | * 显示页面
5 | */
6 | class TxtPage {
7 | var position = 0
8 | var title: String = ""
9 | var titleLines = 0 //当前 lines 中为 title 的行数。
10 | val lines: MutableList = ArrayList()
11 | var pic: String = ""
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/ui/widget/page/utils/DateUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.ui.widget.page.utils
2 |
3 | import android.annotation.SuppressLint
4 |
5 |
6 |
7 | import java.text.SimpleDateFormat
8 | import java.util.Calendar
9 | import java.util.Date
10 |
11 | /**
12 | * 日期帮助类
13 | */
14 | @SuppressLint("SimpleDateFormat")
15 | object DateUtil {
16 | private val HOUR_OF_DAY = 24
17 | private val DAY_OF_YESTERDAY = 2
18 | private val TIME_UNIT = 60
19 |
20 | //时间戳格式转换
21 | private val dayNames = arrayOf("周日", "周一", "周二", "周三", "周四", "周五", "周六")
22 |
23 | /**
24 | * 时间转化
25 | */
26 | fun dateConvert(time: Long, pattern: String): String {
27 | val date = Date(time)
28 | @SuppressLint("SimpleDateFormat") val format = SimpleDateFormat(pattern)
29 | return format.format(date)
30 | }
31 |
32 | /**
33 | * 时间转化
34 | */
35 | fun dateConvert(timesamp: Long, flag: Int): String {
36 | var time = timesamp
37 | time *= 1000
38 | val result: String
39 | val todayCalendar = Calendar.getInstance()
40 | val otherCalendar = Calendar.getInstance()
41 | otherCalendar.timeInMillis = time
42 |
43 | val timeFormat = "M月d日"
44 | val yearTimeFormat = "yyyy年M月d日"
45 |
46 | val yearTemp = todayCalendar.get(Calendar.YEAR) == otherCalendar.get(Calendar.YEAR)
47 | if (yearTemp) {
48 | val todayMonth = todayCalendar.get(Calendar.MONTH)
49 | val otherMonth = otherCalendar.get(Calendar.MONTH)
50 | if (todayMonth == otherMonth) {//表示是同一个月
51 | when (todayCalendar.get(Calendar.DATE) - otherCalendar.get(Calendar.DATE)) {
52 | 0 -> result = getHourAndMin(time)
53 | 1 -> if (flag == 1) {
54 | result = "昨天 "
55 | } else {
56 | result = "昨天 " + getHourAndMin(time)
57 | }
58 | 2, 3, 4, 5, 6 -> {
59 | val dayOfMonth = otherCalendar.get(Calendar.WEEK_OF_MONTH)
60 | val todayOfMonth = todayCalendar.get(Calendar.WEEK_OF_MONTH)
61 | if (dayOfMonth == todayOfMonth) {//表示是同一周
62 | val dayOfWeek = otherCalendar.get(Calendar.DAY_OF_WEEK)
63 | if (dayOfWeek != 1) {//判断当前是不是星期日 如想显示为:周日 12:09 可去掉此判断
64 | result = dayNames[otherCalendar.get(Calendar.DAY_OF_WEEK) - 1]
65 | } else {
66 | result = getTime(time, timeFormat)
67 | }
68 | } else {
69 | result = getTime(time, timeFormat)
70 | }
71 | }
72 | else -> result = getTime(time, timeFormat)
73 | }
74 | } else {
75 | result = getTime(time, timeFormat)
76 | }
77 | } else {
78 | result = getYearTime(time, yearTimeFormat)
79 | }
80 | return result
81 | }
82 |
83 | /**
84 | * 当天的显示时间格式
85 | */
86 | private fun getHourAndMin(time: Long): String {
87 | val format = SimpleDateFormat("HH:mm")
88 | return format.format(Date(time))
89 | }
90 |
91 | /**
92 | * 不同一周的显示时间格式
93 | */
94 | private fun getTime(time: Long, timeFormat: String): String {
95 | val format = SimpleDateFormat(timeFormat)
96 | return format.format(Date(time))
97 | }
98 |
99 | /**
100 | * 不同年的显示时间格式
101 | */
102 | private fun getYearTime(time: Long, yearTimeFormat: String): String {
103 | val format = SimpleDateFormat(yearTimeFormat)
104 | return format.format(Date(time))
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/BookUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import com.woodnoisu.reader.constant.Constant
4 | import java.io.File
5 |
6 | object BookUtil {
7 | /**
8 | * 获取书籍大小
9 | */
10 | fun getBookSize(folderName: String): Long {
11 | return FileUtil.getDirSize(
12 | FileUtil
13 | .getFolder(Constant.BOOK_CACHE_PATH + folderName)
14 | )
15 | }
16 |
17 | /**
18 | * 创建或获取存储文件
19 | * @param folderName 文件夹
20 | * @param fileName 文件
21 | * @return 文件
22 | */
23 | fun getBookFile(folderName: String?, fileName: String?): File {
24 | return FileUtil.getFile(
25 | Constant.BOOK_CACHE_PATH + folderName
26 | + File.separator + fileName + Constant.SUFFIX_NB
27 | )
28 | }
29 |
30 | /**
31 | * 根据文件名判断是否被缓存过 (因为可能数据库显示被缓存过,但是文件中却没有的情况,所以需要根据文件判断是否被缓存
32 | * 过)
33 | * @param folderName : bookId
34 | * @param fileName: chapterName
35 | * @return 是否被缓存过
36 | */
37 | fun isChapterCached(folderName: String, fileName: String): Boolean {
38 | val file = File(
39 | Constant.BOOK_CACHE_PATH + folderName
40 | + File.separator + fileName + Constant.SUFFIX_NB
41 | )
42 | return file.exists()
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/BrightnessUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.app.Activity
4 | import android.provider.Settings
5 | import android.util.Log
6 | import android.view.WindowManager
7 |
8 | /**
9 | * 调节亮度的工具类
10 | */
11 |
12 | object BrightnessUtil {
13 | private const val TAG = "BrightnessUtils"
14 |
15 | /**
16 | * 获取屏幕的亮度
17 | * 系统亮度模式中,自动模式与手动模式获取到的系统亮度的值不同
18 | */
19 | fun getScreenBrightness(activity: Activity): Int {
20 | return if (isAutoBrightness(activity)) {
21 | getAutoScreenBrightness(activity)
22 | } else {
23 | getManualScreenBrightness(activity)
24 | }
25 | }
26 |
27 | /**
28 | * 设置亮度:通过设置 Windows 的 screenBrightness 来修改当前 Windows 的亮度
29 | * lp.screenBrightness:参数范围为 0~1
30 | */
31 | fun setBrightness(activity: Activity, brightness: Int) {
32 | try {
33 | val lp = activity.window.attributes
34 | //将 0~255 范围内的数据,转换为 0~1
35 | lp.screenBrightness = java.lang.Float.valueOf(brightness.toFloat()) * (1f / 255f)
36 | Log.d(TAG, "lp.screenBrightness == " + lp.screenBrightness)
37 | activity.window.attributes = lp
38 | } catch (ex: Exception) {
39 | ex.printStackTrace()
40 | }
41 |
42 | }
43 |
44 | /**
45 | * 获取当前系统的亮度
46 | * @param activity
47 | */
48 | fun setDefaultBrightness(activity: Activity) {
49 | try {
50 | val lp = activity.window.attributes
51 | lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
52 | activity.window.attributes = lp
53 | } catch (ex: Exception) {
54 | ex.printStackTrace()
55 | }
56 |
57 | }
58 |
59 | /**
60 | * 判断是否开启了自动亮度调节
61 | */
62 | private fun isAutoBrightness(activity: Activity): Boolean {
63 | var isAuto = false
64 | try {
65 | isAuto = Settings.System.getInt(
66 | activity.contentResolver,
67 | Settings.System.SCREEN_BRIGHTNESS_MODE
68 | ) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
69 | } catch (e: Settings.SettingNotFoundException) {
70 | e.printStackTrace()
71 | }
72 |
73 | return isAuto
74 | }
75 |
76 | /**
77 | * 获取手动模式下的屏幕亮度
78 | * @return value:0~255
79 | */
80 | private fun getManualScreenBrightness(activity: Activity): Int {
81 | var nowBrightnessValue = 0
82 | val resolver = activity.contentResolver
83 | try {
84 | nowBrightnessValue = Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS)
85 | } catch (e: Exception) {
86 | e.printStackTrace()
87 | }
88 |
89 | return nowBrightnessValue
90 | }
91 |
92 | /**
93 | * 获取自动模式下的屏幕亮度
94 | * @return value:0~255
95 | */
96 | private fun getAutoScreenBrightness(activity: Activity): Int {
97 | var nowBrightnessValue = 0f
98 |
99 | //获取自动调节下的亮度范围在 0~1 之间
100 | val resolver = activity.contentResolver
101 | try {
102 | nowBrightnessValue =
103 | Settings.System.getFloat(resolver, Settings.System.SCREEN_BRIGHTNESS)
104 | Log.d(TAG, "getAutoScreenBrightness: $nowBrightnessValue")
105 | } catch (e: Exception) {
106 | e.printStackTrace()
107 | }
108 |
109 | //转换范围为 (0~255)
110 | val fValue = nowBrightnessValue * 225.0f
111 | Log.d(TAG, "brightness: $fValue")
112 | return fValue.toInt()
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/LogUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.util.Log
4 | import com.woodnoisu.reader.BuildConfig
5 |
6 | /**
7 | * 日志工具类
8 | *
9 | * 具体规则:
10 | * -Verbose等级的Log,请不要在user版本中出现。
11 | * -Info、Warn、Error等级的Log禁止作为普通的调试信息使用,这些等级的Log是系统出现问题时候的重要分析线索,如果随意使用,将给Log分析人员带来极大困扰。
12 | * -Log的tag命名,使用Activity名称或者类、模块的名称,不要出现自己的姓名拼音或其他简称。
13 | * -Log输出的频率需要控制,例如1s打印一次的Log,尽量只在eng版本使用。
14 | * @author ssq
15 | */
16 | object LogUtil {
17 |
18 | private const val TAG = "reader"
19 |
20 | /**
21 | * Verbose: 开发调试过程中一些详细信息,不应该编译进产品中,只在开发阶段使用。
22 | */
23 | fun v(vararg msg: Any?) {
24 | if (BuildConfig.DEBUG) {
25 | for (m in msg) {
26 | Log.v(TAG, m.toString())
27 | }
28 | }
29 | }
30 |
31 | /**
32 | * Verbose: 开发调试过程中一些详细信息,不应该编译进产品中,只在开发阶段使用。
33 | */
34 | fun v(tag: String, vararg msg: Any?) {
35 | if (BuildConfig.DEBUG) {
36 | for (m in msg) {
37 | Log.v(tag, m.toString())
38 | }
39 | }
40 | }
41 |
42 | /**
43 | * Info:例如一些运行时的状态信息,这些状态信息在出现问题的时候能提供帮助。
44 | */
45 | fun i(vararg msg: Any?) {
46 | if (BuildConfig.DEBUG) {
47 | for (m in msg) {
48 | Log.i(TAG, m.toString())
49 | }
50 | }
51 | }
52 |
53 | /**
54 | * Info:例如一些运行时的状态信息,这些状态信息在出现问题的时候能提供帮助。
55 | */
56 | fun i(tag: String, vararg msg: Any?) {
57 | if (BuildConfig.DEBUG) {
58 | for (m in msg) {
59 | Log.i(tag, m.toString())
60 | }
61 | }
62 | }
63 |
64 | /**
65 | * Error: 错误信息
66 | */
67 | fun e(vararg msg: Any?) {
68 | for (m in msg) {
69 | Log.e(TAG, m.toString())
70 | }
71 | }
72 |
73 | /**
74 | * Error: 错误信息
75 | */
76 | fun e(tag: String, vararg msg: Any?) {
77 | for (m in msg) {
78 | Log.e(tag, m.toString())
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/MD5Util.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import java.security.MessageDigest
4 | import java.security.NoSuchAlgorithmException
5 | import kotlin.experimental.and
6 |
7 | /**
8 | * MD5帮助类
9 | */
10 | object MD5Util {
11 | /**
12 | * 字符串转md5
13 | */
14 | fun strToMd5By16(str: String): String {
15 | var reStr = strToMd5By32(str)
16 | if (reStr != null) {
17 | reStr = reStr.substring(8, 24)
18 | }
19 | return reStr
20 | }
21 |
22 | /**
23 | * 字符串转md5
24 | */
25 | private fun strToMd5By32(str: String): String {
26 | var reStr = ""
27 | try {
28 | val md5 = MessageDigest.getInstance("MD5")
29 | val bytes = md5.digest(str.toByteArray())
30 | val stringBuffer = StringBuilder()
31 | for (b in bytes) {
32 | val bt = b and 0xff.toByte()
33 | if (bt < 16) {
34 | stringBuffer.append(0)
35 | }
36 | stringBuffer.append(Integer.toHexString(bt.toInt()))
37 | }
38 | reStr = stringBuffer.toString()
39 | } catch (e: NoSuchAlgorithmException) {
40 | e.printStackTrace()
41 | }
42 | return reStr
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/ScreenUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.content.res.Resources
4 | import android.util.DisplayMetrics
5 | import android.util.TypedValue
6 | import androidx.appcompat.app.AppCompatActivity
7 |
8 | /**
9 | * 屏幕帮助类
10 | */
11 | object ScreenUtil {
12 | /**
13 | * dp转px
14 | */
15 | fun dpToPx(dp: Int): Int {
16 | val metrics = getDisplayMetrics()
17 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), metrics).toInt()
18 | }
19 |
20 | /**
21 | * px转dp
22 | */
23 | fun pxToDp(px: Int): Int {
24 | val metrics = getDisplayMetrics()
25 | return (px / metrics.density).toInt()
26 | }
27 |
28 | /**
29 | * sp转px
30 | */
31 | fun spToPx(sp: Int): Int {
32 | val metrics = getDisplayMetrics()
33 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp.toFloat(), metrics).toInt()
34 | }
35 |
36 | /**
37 | * px转sp
38 | */
39 | fun pxToSp(px: Int): Int {
40 | val metrics = getDisplayMetrics()
41 | return (px / metrics.scaledDensity).toInt()
42 | }
43 |
44 | /**
45 | * 获取手机显示App区域的大小(头部导航栏+ActionBar+根布局),不包括虚拟按钮
46 | * @return
47 | */
48 | fun getAppSize(): IntArray{
49 | val size = IntArray(2)
50 | val metrics = getDisplayMetrics()
51 | size[0] = metrics.widthPixels
52 | size[1] = metrics.heightPixels
53 | return size
54 | }
55 |
56 | /**
57 | * 获取导航栏的高度
58 | * @return
59 | */
60 | fun getStatusBarHeight(): Int{
61 | val resources = Resources.getSystem()
62 | val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
63 | return resources.getDimensionPixelSize(resourceId)
64 | }
65 |
66 | /**
67 | * 获取虚拟按键的高度
68 | * @return
69 | */
70 | fun getNavigationBarHeight(): Int{
71 | var navigationBarHeight = 0
72 | val rs = Resources.getSystem()
73 | val id = rs.getIdentifier("navigation_bar_height", "dimen", "android")
74 | if (id > 0 && hasNavigationBar()) {
75 | navigationBarHeight = rs.getDimensionPixelSize(id)
76 | }
77 | return navigationBarHeight
78 | }
79 |
80 | /**
81 | * 获取整个手机屏幕的大小(包括虚拟按钮)
82 | * 必须在onWindowFocus方法之后使用
83 | * @param activity
84 | * @return
85 | */
86 | fun getScreenSize(activity: AppCompatActivity): IntArray {
87 | val size = IntArray(2)
88 | val decorView = activity.window.decorView
89 | size[0] = decorView.width
90 | size[1] = decorView.height
91 | return size
92 | }
93 |
94 | /**
95 | * 显示指标
96 | */
97 | private fun getDisplayMetrics(): DisplayMetrics {
98 | return Resources.getSystem().displayMetrics
99 | }
100 |
101 | /**
102 | * 是否存在虚拟按键
103 | * @return
104 | */
105 | private fun hasNavigationBar(): Boolean {
106 | var hasNavigationBar = false
107 | val rs = Resources.getSystem()
108 | val id = rs.getIdentifier("config_showNavigationBar", "bool", "android")
109 | if (id > 0) {
110 | hasNavigationBar = rs.getBoolean(id)
111 | }
112 | try {
113 | val systemPropertiesClass = Class.forName("android.os.SystemProperties")
114 | val m = systemPropertiesClass.getMethod("get", String::class.java)
115 | val navBarOverride = m.invoke(systemPropertiesClass, "qemu.hw.mainkeys") as String
116 | if ("1" == navBarOverride) {
117 | hasNavigationBar = false
118 | } else if ("0" == navBarOverride) {
119 | hasNavigationBar = true
120 | }
121 | } catch (e: Exception) {
122 | }
123 |
124 | return hasNavigationBar
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/SpUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import java.util.*
6 |
7 | /**
8 | * 语言帮助类
9 | */
10 | object SpUtil {
11 | // 语言选择
12 | private const val tagLanguage = "language_select"
13 | // 当前语言
14 | var systemCurrentLocal: Locale = Locale.ENGLISH
15 |
16 | // 数据保存对象
17 | private lateinit var sp: SharedPreferences
18 |
19 | /**
20 | * 初始化上下文
21 | */
22 | fun init(context: Context) {
23 | sp = context.getSharedPreferences(
24 | context.packageName,
25 | Context.MODE_PRIVATE
26 | )
27 | }
28 |
29 | /**
30 | * 获取字符串
31 | */
32 | fun getStringValue(key: String?): String? {
33 | return sp.getString(key, null)
34 | }
35 |
36 | /**
37 | * 获取字符串
38 | */
39 | fun getStringValue(key: String?, defaultValue: String?): String? {
40 | return sp.getString(key, defaultValue)
41 | }
42 |
43 | /**
44 | * 设置字符串
45 | */
46 | fun setStringValue(key: String?, value: String?) {
47 | val editor = sp.edit()
48 | editor.putString(key, value)
49 | editor.apply()
50 | }
51 |
52 | /**
53 | * 获取bool值
54 | */
55 | fun getBooleanValue(key: String?): Boolean {
56 | return sp.getBoolean(key, false)
57 | }
58 |
59 | /**
60 | * 获取bool值
61 | */
62 | fun getBooleanValue(key: String?, value: Boolean): Boolean {
63 | return sp.getBoolean(key, value)
64 | }
65 |
66 | /**
67 | * 获取bool值
68 | */
69 | fun setBooleanValue(key: String?, value: Boolean) {
70 | val editor = sp.edit()
71 | editor.putBoolean(key, value)
72 | editor.apply()
73 | }
74 |
75 | /**
76 | * 获取int值
77 | */
78 | fun getIntValue(key: String?, def: Int): Int {
79 | return sp.getInt(key, def)
80 | }
81 |
82 | /**
83 | * 设置int值
84 | */
85 | fun setIntValue(key: String?, value: Int) {
86 | val editor = sp.edit()
87 | editor.putInt(key, value)
88 | editor.apply()
89 | }
90 |
91 | /**
92 | * 设置long值
93 | */
94 | fun setLongValue(key: String?, value: Long) {
95 | val editor = sp.edit()
96 | editor.putLong(key, value)
97 | editor.apply()
98 | }
99 |
100 | /**
101 | * 获取long值
102 | */
103 | fun getLongValue(key: String?): Long {
104 | return sp.getLong(key, 0)
105 | }
106 |
107 | /**
108 | * 获取float值
109 | */
110 | fun getFloatValue(key: String?): Float {
111 | return sp.getFloat(key, 0.0f)
112 | }
113 |
114 | /**
115 | * 设置float值
116 | */
117 | fun setFloatValue(key: String?, value: Float) {
118 | val editor = sp.edit()
119 | editor.putFloat(key, value)
120 | editor.apply()
121 | }
122 |
123 | /**
124 | * 获取当前语言
125 | */
126 | fun getSelectLanguage():Int {
127 | return getIntValue(tagLanguage, 0)
128 | }
129 |
130 | /**
131 | * 保存语言
132 | */
133 | fun setSelectLanguage(select: Int) {
134 | setIntValue(tagLanguage, select)
135 | }
136 |
137 | /**
138 | * 清理所有值
139 | */
140 | fun clearAllValue(context: Context) {
141 | val sharedData = context.getSharedPreferences(
142 | context.packageName,
143 | Context.MODE_PRIVATE
144 | )
145 | val editor = sharedData.edit()
146 | editor.clear()
147 | editor.apply()
148 | }
149 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/StringUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.text.TextUtils
4 | import com.woodnoisu.reader.ui.widget.page.ReadSettingManager.Companion.SHARED_READ_CONVERT_TYPE
5 |
6 | /**
7 | * 字符串帮助类
8 | */
9 | object StringUtil {
10 | /**
11 | * 将文本中的半角字符,转换成全角字符
12 | */
13 | fun halfToFull(input: String): String {
14 | val text = deleteImgs(input)
15 | val c = text.toCharArray()
16 | for (i in c.indices) {
17 | if (c[i].toInt() == 32)
18 | //半角空格
19 | {
20 | c[i] = 12288.toChar()
21 | continue
22 | }
23 | //根据实际情况,过滤不需要转换的符号
24 | //if (c[i] == 46) //半角点号,不转换
25 | // continue;
26 |
27 | if (c[i].toInt() in 33..126)
28 | //其他符号都转换为全角
29 | c[i] = (c[i].toInt() + 65248).toChar()
30 | }
31 | return String(c)
32 | }
33 |
34 | /**
35 | * 删除160
36 | */
37 | fun delete160(des: String): String {
38 | var text = des
39 | text = text.replace(" ".toRegex(), "")
40 | text = text.replace(" ".toRegex(), "")
41 | text = text.replace("\\s*".toRegex(), "")
42 | text = text.trim { it <= ' ' }
43 | return text
44 | }
45 |
46 | /**
47 | * 繁簡轉換
48 | */
49 | fun convertCC(input: String): String {
50 | val convertType = SpUtil.getIntValue(SHARED_READ_CONVERT_TYPE, 0)
51 |
52 | if (input.isEmpty())
53 | return ""
54 |
55 | return input
56 | }
57 |
58 | /**
59 | * 将中文数字转换阿拉伯数字
60 | */
61 | fun parseCnToInt(cNum: String):Long {
62 | val num = cNum.replace(Regex("\\s+"), "")
63 | var firstUnit: Long = 1//一级单位
64 | var secondUnit: Long = 1//二级单位
65 | var result: Long = 0//结果
66 | val arr = num.toCharArray()
67 | //从低到高位依次处理
68 | for (i in arr.size - 1 downTo 0) {
69 | val c = arr[i]
70 | //临时单位变量
71 | var tmpUnit = charToUnit(c)
72 | //判断此位是数字还是单位
73 | if (tmpUnit > firstUnit){
74 | //是的话就赋值,以备下次循环使用
75 | firstUnit = tmpUnit
76 | secondUnit = 1
77 | //处理如果是"十","十一"这样的开头的
78 | if (i == 0){
79 | result += firstUnit * secondUnit
80 | }
81 | //结束本次循环
82 | continue
83 | }
84 | if (tmpUnit > secondUnit) {
85 | secondUnit = tmpUnit
86 | continue
87 | }
88 | result += firstUnit * secondUnit * charToNumber(c);//如果是数字,则和单位想乘然后存到结果里
89 | }
90 | return result
91 | }
92 |
93 | /**
94 | * 转换单位
95 | */
96 | private fun charToUnit(c:Char):Long{
97 | return when (c) {
98 | '十' -> 10
99 | '百' -> 100
100 | '千' -> 1000
101 | '万' -> 10000
102 | '亿' -> 100000000
103 | else -> 1
104 | }
105 | }
106 |
107 | /**
108 | * 转换数字
109 | */
110 | private fun charToNumber(c: Char):Long {
111 | return when (c) {
112 | '一' -> 1
113 | '二' -> 2
114 | '三' -> 3
115 | '四' -> 4
116 | '五' -> 5
117 | '六' -> 6
118 | '七' -> 7
119 | '八' -> 8
120 | '九' -> 9
121 | '零' -> 0
122 | else -> 0
123 | }
124 | }
125 |
126 | /**
127 | * 删除图片
128 | */
129 | private fun deleteImgs(content: String?): String {
130 | return if (content != null && !TextUtils.isEmpty(content)) {
131 | // 去掉所有html元素,
132 | var str =
133 | content.replace("&[a-zA-Z]{1,10};".toRegex(), "").replace("<[^>]*>".toRegex(), "")
134 | str = str.replace("[(/>)<]".toRegex(), "")
135 | str
136 | } else {
137 | ""
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/ToastUtil.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.widget.Toast
4 | import com.woodnoisu.reader.base.ContextProvider
5 |
6 | /**
7 | * 显示通知栏
8 | */
9 | fun showToast(text: String) {
10 | Toast.makeText(ContextProvider.mContext, text, Toast.LENGTH_SHORT).show()
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/woodnoisu/reader/utils/UtilDialog.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import com.afollestad.materialdialogs.DialogCallback
6 | import com.afollestad.materialdialogs.MaterialDialog
7 | import com.afollestad.materialdialogs.callbacks.onCancel
8 | import com.afollestad.materialdialogs.callbacks.onDismiss
9 | import com.afollestad.materialdialogs.callbacks.onPreShow
10 | import com.afollestad.materialdialogs.callbacks.onShow
11 | import com.afollestad.materialdialogs.customview.customView
12 |
13 |
14 | object UtilDialog {
15 | fun showDialog(
16 | context: Context,
17 | title: String? = null,
18 | message: String? = null,
19 | positiveText: String? = null,
20 | negativeText: String? = null,
21 | neutralText: String? = null,
22 | positiveClick: DialogCallback = { it.dismiss() },
23 | negativeClick: DialogCallback = { it.dismiss() },
24 | neutralClick: DialogCallback = { it.dismiss() },
25 | onPreShow: DialogCallback = { },
26 | onShow: DialogCallback = { },
27 | onDismiss: DialogCallback = { },
28 | onCancel: DialogCallback = { },
29 |
30 | customView: View? = null,
31 | scrollable: Boolean = false,
32 | noVerticalPadding: Boolean = false,
33 | horizontalPadding: Boolean = false,
34 | dialogWrapContent: Boolean = false,
35 |
36 | showDialogNow: Boolean = true,
37 | cancelable: Boolean = true,
38 | cancelOnTouchOutside: Boolean = true,
39 | cornerRadius: Float = 0f
40 | ): MaterialDialog {
41 | val dialog = MaterialDialog(context)
42 | .title(text = title ?: "")
43 | .message(text = message ?: "")
44 | .onPreShow(onPreShow)
45 | .onShow(onShow)
46 | .onDismiss(onDismiss)
47 | .onCancel(onCancel)
48 | dialog.setCancelable(cancelable)
49 | dialog.setCanceledOnTouchOutside(cancelOnTouchOutside)
50 | dialog.cornerRadius(cornerRadius)
51 | if (!positiveText.isNullOrBlank()) {
52 | dialog.positiveButton(text = positiveText, click = positiveClick)
53 | }
54 | if (!negativeText.isNullOrBlank()) {
55 | dialog.negativeButton(text = negativeText, click = negativeClick)
56 | }
57 | if (!neutralText.isNullOrBlank()) {
58 | dialog.neutralButton(text = neutralText, click = neutralClick)
59 | }
60 | if (customView != null) {
61 | dialog.customView(view = customView, scrollable = scrollable, noVerticalPadding = noVerticalPadding,
62 | horizontalPadding = horizontalPadding, dialogWrapContent = dialogWrapContent)
63 | }
64 | if (showDialogNow) {
65 | dialog.show()
66 | }
67 | return dialog
68 | }
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/message_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/message_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_bottom_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_bottom_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_left_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_left_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_right_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_right_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_top_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_top_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/selector_bottom_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_arrow_right.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_menu_mode_night_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_menu_mode_night_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-night-xxhdpi/ic_theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-night-xxhdpi/ic_theme.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/find.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/find.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/find1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/find1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/home.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/home1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/home1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_book_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_book_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_book_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_book_detail.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_checked.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_font_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_font_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_font_min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_font_min.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_guide.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_guide.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_item_category_activated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_item_category_activated.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_item_category_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_item_category_download.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_item_category_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_item_category_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_label.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_add_mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_menu_add_mark.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_more_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_more_n.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_more_p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_more_p.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_no_thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_no_thumb.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_read_menu_moring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_read_menu_moring.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_read_menu_night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_read_menu_night.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/ic_theme.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/icon_inner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/icon_inner.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/icon_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/icon_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/mine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/mine.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/mine1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/mine1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/pic_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/pic_placeholder.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/search1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/search1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/search_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/search_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/seekbar_thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/seekbar_thumb_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/seekbar_thumb_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/seekbar_thumb_selected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/shelf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/shelf.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/shelf1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/shelf1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/theme_leather_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/theme_leather_bg.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/title_more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xhdpi/title_more.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_book_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_book_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_book_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_book_detail.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_book_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_book_n.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_book_p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_book_p.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_cache.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_cache.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_checked.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_contents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_contents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_guide.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_guide.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_item_category_activated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_item_category_activated.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_item_category_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_item_category_download.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_item_category_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_item_category_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_label.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_menu_add_mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_menu_add_mark.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_menu_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_menu_clear.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_menu_mode_night_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_menu_mode_night_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_more_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_more_n.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_more_p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_more_p.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_no_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_no_select.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_read_menu_moring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_read_menu_moring.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_read_menu_night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_read_menu_night.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_read_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_read_setting.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/ic_select.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/seekbar_thumb_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/seekbar_thumb_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/seekbar_thumb_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/drawable-xxhdpi/seekbar_thumb_selected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_coner_line.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_listen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/listen_select.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seekbar_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
11 |
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seekbar_thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/select_more.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_bottom_navigation_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_bottom_navigation_me.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_bottom_navigation_shelf.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_bottom_navigation_square.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_category_load.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_category_unload.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_bg_oval.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/text_font_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_me.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
28 |
29 |
39 |
40 |
48 |
49 |
56 |
57 |
58 |
59 |
71 |
72 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shelf.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
25 |
26 |
39 |
40 |
49 |
50 |
58 |
59 |
60 |
61 |
65 |
66 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_square.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_book.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
31 |
42 |
43 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_read_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_shelf.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
30 |
31 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_download.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
22 |
23 |
34 |
35 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
28 |
29 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_read_mark.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
26 |
27 |
42 |
43 |
44 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/rlv_item_catalogue.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/rlv_item_mark.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
28 |
29 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/title_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
29 |
30 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_navigation_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/left_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/shelf_pop_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 其他分类
6 | - 奇幻玄幻
7 | - 武侠仙侠
8 | - 都市官场
9 | - 历史军事
10 | - 言情穿越
11 | - 灵异悬疑
12 | - 游戏竞技
13 | - 青春耽美
14 | - 科幻同人
15 | - 经典畅销
16 | - 图书杂志
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
8 | #000000
9 | #393C46
10 | #606372
11 | #9296A6
12 | #969696
13 | #EFF0F4
14 |
15 |
16 | #FF6C6C
17 | #606372
18 | #9296A6
19 | #F0F0F0
20 | #E4EDF6
21 | #B0C7DC
22 |
23 | // 分割线
24 | #ffffffff
25 | #ccffffff
26 | #393C46
27 | #9296A6
28 | #cccccc
29 | #00000000
30 | #ececec
31 | #f7f7f7
32 | #EEEEEE
33 | #cc000000
34 |
35 |
36 | #606372
37 |
38 |
39 | #393C46
40 | #FF6C6C
41 |
42 |
43 | #5086DF
44 |
45 | #498EFF
46 | #8B8484
47 |
48 |
49 | #212121
50 |
51 | #262626
52 |
53 | #A8ABB8
54 |
55 | #707177
56 |
57 | #F0F0F0
58 | #F5F5F5
59 | #969696
60 |
61 |
62 | @color/colorPrimary
63 |
64 | #E3E3E3
65 |
66 | #FFFDF6
67 | #FCC4C4C4
68 | #EC4A48
69 | #B87324
70 |
71 |
72 | #EFEFF7
73 | #CCEBCC
74 | #CEC29C
75 | #001C27
76 | #DDCEC29C
77 |
78 |
79 | #383429
80 | #2F332D
81 | #92918C
82 | #2C2C2C
83 | #627176
84 |
85 | #000000
86 | #99ffffff
87 |
88 | #191919
89 | #CBCBCB
90 | #88000000
91 |
92 | #3B8848
93 | #00000000
94 | #353535
95 |
96 | @color/c_3B8848
97 | @color/c_00000000
98 | @color/c_353535
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Reader
3 | 书架
4 | 推荐
5 | 书库
6 | 更多
7 | 语言选择
8 | 简体中文
9 | 繁体中文
10 | 按更新时间
11 | 按阅读时间
12 |
13 |
14 | 没有相关数据!
15 | 清空书架列表
16 |
17 |
18 | 意见与反馈
19 | 评价
20 | 设置
21 |
22 |
23 | 目录
24 | 亮度
25 | 缓存
26 | 简介
27 | 标签
28 |
29 | 缓存多少章?
30 | 后面五十章
31 | 后面全部
32 | 全部
33 |
34 | 字体
35 | 简体
36 | 繁体
37 | 字号
38 | 背景
39 |
40 | 夜间
41 | 加入书架失败,请检查网络设置
42 | 新增书签
43 | 清除
44 | %d/%d
45 | 正在下载
46 |
47 |
48 | 翻页
49 | 仿真
50 | 覆盖
51 | 滚动
52 | 无
53 |
54 | 白天
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import com.woodnoisu.ktReader.network.*
4 | import com.woodnoisu.reader.constant.Constant
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import okhttp3.OkHttpClient
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.moshi.MoshiConverterFactory
12 | import java.util.concurrent.TimeUnit
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | object NetworkModule {
18 |
19 | @Provides
20 | @Singleton
21 | fun provideOkHttpClient(): OkHttpClient {
22 | return OkHttpClient.Builder()
23 | .apply {
24 | connectTimeout(30, TimeUnit.SECONDS)// 连接时间:30s超时
25 | readTimeout(10, TimeUnit.SECONDS)// 读取时间:10s超时
26 | writeTimeout(10, TimeUnit.SECONDS)// 写入时间:10s超时
27 | addInterceptor(HttpRequestInterceptor())
28 | }.build()
29 | }
30 |
31 | @Provides
32 | @Singleton
33 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
34 | return Retrofit.Builder()
35 | .client(okHttpClient)
36 | .baseUrl(Constant.API_BASE_URL)
37 | .addConverterFactory(MoshiConverterFactory.create())
38 | //.addCallAdapterFactory(CoroutinesResponseCallAdapterFactory())
39 | .build()
40 | }
41 |
42 | @Provides
43 | @Singleton
44 | fun provideApiService(retrofit: Retrofit): ApiService {
45 | return retrofit.create(ApiService::class.java)
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | fun provideApiClient(apiService: ApiService): ApiClient {
51 | return ApiClient(apiService)
52 | }
53 |
54 | @Provides
55 | @Singleton
56 | fun provideHtmlService(): HtmlService {
57 | return HtmlService()
58 | }
59 |
60 | @Provides
61 | @Singleton
62 | fun provideHtmlClient(htmlService: HtmlService): HtmlClient {
63 | return HtmlClient(htmlService)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideApiClientFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.ApiClient;
5 | import com.woodnoisu.ktReader.network.ApiService;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class NetworkModule_ProvideApiClientFactory implements Factory {
15 | private final Provider apiServiceProvider;
16 |
17 | public NetworkModule_ProvideApiClientFactory(Provider apiServiceProvider) {
18 | this.apiServiceProvider = apiServiceProvider;
19 | }
20 |
21 | @Override
22 | public ApiClient get() {
23 | return provideApiClient(apiServiceProvider.get());
24 | }
25 |
26 | public static NetworkModule_ProvideApiClientFactory create(
27 | Provider apiServiceProvider) {
28 | return new NetworkModule_ProvideApiClientFactory(apiServiceProvider);
29 | }
30 |
31 | public static ApiClient provideApiClient(ApiService apiService) {
32 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideApiClient(apiService));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideApiServiceFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.ApiService;
5 | import dagger.internal.Factory;
6 | import dagger.internal.Preconditions;
7 | import javax.inject.Provider;
8 | import retrofit2.Retrofit;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class NetworkModule_ProvideApiServiceFactory implements Factory {
15 | private final Provider retrofitProvider;
16 |
17 | public NetworkModule_ProvideApiServiceFactory(Provider retrofitProvider) {
18 | this.retrofitProvider = retrofitProvider;
19 | }
20 |
21 | @Override
22 | public ApiService get() {
23 | return provideApiService(retrofitProvider.get());
24 | }
25 |
26 | public static NetworkModule_ProvideApiServiceFactory create(Provider retrofitProvider) {
27 | return new NetworkModule_ProvideApiServiceFactory(retrofitProvider);
28 | }
29 |
30 | public static ApiService provideApiService(Retrofit retrofit) {
31 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideApiService(retrofit));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideHtmlClientFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.HtmlClient;
5 | import com.woodnoisu.ktReader.network.HtmlService;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class NetworkModule_ProvideHtmlClientFactory implements Factory {
15 | private final Provider htmlServiceProvider;
16 |
17 | public NetworkModule_ProvideHtmlClientFactory(Provider htmlServiceProvider) {
18 | this.htmlServiceProvider = htmlServiceProvider;
19 | }
20 |
21 | @Override
22 | public HtmlClient get() {
23 | return provideHtmlClient(htmlServiceProvider.get());
24 | }
25 |
26 | public static NetworkModule_ProvideHtmlClientFactory create(
27 | Provider htmlServiceProvider) {
28 | return new NetworkModule_ProvideHtmlClientFactory(htmlServiceProvider);
29 | }
30 |
31 | public static HtmlClient provideHtmlClient(HtmlService htmlService) {
32 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideHtmlClient(htmlService));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideHtmlServiceFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.HtmlService;
5 | import dagger.internal.Factory;
6 | import dagger.internal.Preconditions;
7 |
8 | @SuppressWarnings({
9 | "unchecked",
10 | "rawtypes"
11 | })
12 | public final class NetworkModule_ProvideHtmlServiceFactory implements Factory {
13 | @Override
14 | public HtmlService get() {
15 | return provideHtmlService();
16 | }
17 |
18 | public static NetworkModule_ProvideHtmlServiceFactory create() {
19 | return InstanceHolder.INSTANCE;
20 | }
21 |
22 | public static HtmlService provideHtmlService() {
23 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideHtmlService());
24 | }
25 |
26 | private static final class InstanceHolder {
27 | private static final NetworkModule_ProvideHtmlServiceFactory INSTANCE = new NetworkModule_ProvideHtmlServiceFactory();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideOkHttpClientFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import dagger.internal.Factory;
5 | import dagger.internal.Preconditions;
6 | import okhttp3.OkHttpClient;
7 |
8 | @SuppressWarnings({
9 | "unchecked",
10 | "rawtypes"
11 | })
12 | public final class NetworkModule_ProvideOkHttpClientFactory implements Factory {
13 | @Override
14 | public OkHttpClient get() {
15 | return provideOkHttpClient();
16 | }
17 |
18 | public static NetworkModule_ProvideOkHttpClientFactory create() {
19 | return InstanceHolder.INSTANCE;
20 | }
21 |
22 | public static OkHttpClient provideOkHttpClient() {
23 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideOkHttpClient());
24 | }
25 |
26 | private static final class InstanceHolder {
27 | private static final NetworkModule_ProvideOkHttpClientFactory INSTANCE = new NetworkModule_ProvideOkHttpClientFactory();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/NetworkModule_ProvideRetrofitFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import dagger.internal.Factory;
5 | import dagger.internal.Preconditions;
6 | import javax.inject.Provider;
7 | import okhttp3.OkHttpClient;
8 | import retrofit2.Retrofit;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class NetworkModule_ProvideRetrofitFactory implements Factory {
15 | private final Provider okHttpClientProvider;
16 |
17 | public NetworkModule_ProvideRetrofitFactory(Provider okHttpClientProvider) {
18 | this.okHttpClientProvider = okHttpClientProvider;
19 | }
20 |
21 | @Override
22 | public Retrofit get() {
23 | return provideRetrofit(okHttpClientProvider.get());
24 | }
25 |
26 | public static NetworkModule_ProvideRetrofitFactory create(
27 | Provider okHttpClientProvider) {
28 | return new NetworkModule_ProvideRetrofitFactory(okHttpClientProvider);
29 | }
30 |
31 | public static Retrofit provideRetrofit(OkHttpClient okHttpClient) {
32 | return Preconditions.checkNotNullFromProvides(NetworkModule.INSTANCE.provideRetrofit(okHttpClient));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import com.squareup.moshi.Moshi
6 | import com.woodnoisu.ktReader.persistence.*
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object PersistenceModule {
16 |
17 | @Provides
18 | @Singleton
19 | fun provideMoshi(): Moshi {
20 | return Moshi.Builder().build()
21 | }
22 |
23 | @Provides
24 | @Singleton
25 | fun provideAppDatabase(
26 | application: Application,
27 | //typeResponseConverter: TypeResponseConverter
28 | ): AppDataBase {
29 | return Room
30 | .databaseBuilder(application, AppDataBase::class.java, "db_novel.db")
31 | .fallbackToDestructiveMigration()
32 | //.addTypeConverter(typeResponseConverter)
33 | .build()
34 | }
35 |
36 | @Provides
37 | @Singleton
38 | fun provideBookDao(appDataBase: AppDataBase): BookDao {
39 | return appDataBase.bookDao()
40 | }
41 |
42 | @Provides
43 | @Singleton
44 | fun provideChapterDao(appDataBase: AppDataBase): ChapterDao {
45 | return appDataBase.chapterDao()
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | fun provideBookSignDao(appDataBase: AppDataBase): BookSignDao {
51 | return appDataBase.bookSignDao()
52 | }
53 |
54 | @Provides
55 | @Singleton
56 | fun provideReadRecordDao(appDataBase: AppDataBase): ReadRecordDao {
57 | return appDataBase.readRecordDao()
58 | }
59 |
60 | // @Provides
61 | // @Singleton
62 | // fun provideTypeResponseConverter(moshi: Moshi): TypeResponseConverter {
63 | // return TypeResponseConverter(moshi)
64 | // }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideAppDatabaseFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import android.app.Application;
5 | import com.woodnoisu.ktReader.persistence.AppDataBase;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class PersistenceModule_ProvideAppDatabaseFactory implements Factory {
15 | private final Provider applicationProvider;
16 |
17 | public PersistenceModule_ProvideAppDatabaseFactory(Provider applicationProvider) {
18 | this.applicationProvider = applicationProvider;
19 | }
20 |
21 | @Override
22 | public AppDataBase get() {
23 | return provideAppDatabase(applicationProvider.get());
24 | }
25 |
26 | public static PersistenceModule_ProvideAppDatabaseFactory create(
27 | Provider applicationProvider) {
28 | return new PersistenceModule_ProvideAppDatabaseFactory(applicationProvider);
29 | }
30 |
31 | public static AppDataBase provideAppDatabase(Application application) {
32 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideAppDatabase(application));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideBookDaoFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.persistence.AppDataBase;
5 | import com.woodnoisu.ktReader.persistence.BookDao;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class PersistenceModule_ProvideBookDaoFactory implements Factory {
15 | private final Provider appDataBaseProvider;
16 |
17 | public PersistenceModule_ProvideBookDaoFactory(Provider appDataBaseProvider) {
18 | this.appDataBaseProvider = appDataBaseProvider;
19 | }
20 |
21 | @Override
22 | public BookDao get() {
23 | return provideBookDao(appDataBaseProvider.get());
24 | }
25 |
26 | public static PersistenceModule_ProvideBookDaoFactory create(
27 | Provider appDataBaseProvider) {
28 | return new PersistenceModule_ProvideBookDaoFactory(appDataBaseProvider);
29 | }
30 |
31 | public static BookDao provideBookDao(AppDataBase appDataBase) {
32 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideBookDao(appDataBase));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideBookSignDaoFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.persistence.AppDataBase;
5 | import com.woodnoisu.ktReader.persistence.BookSignDao;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class PersistenceModule_ProvideBookSignDaoFactory implements Factory {
15 | private final Provider appDataBaseProvider;
16 |
17 | public PersistenceModule_ProvideBookSignDaoFactory(Provider appDataBaseProvider) {
18 | this.appDataBaseProvider = appDataBaseProvider;
19 | }
20 |
21 | @Override
22 | public BookSignDao get() {
23 | return provideBookSignDao(appDataBaseProvider.get());
24 | }
25 |
26 | public static PersistenceModule_ProvideBookSignDaoFactory create(
27 | Provider appDataBaseProvider) {
28 | return new PersistenceModule_ProvideBookSignDaoFactory(appDataBaseProvider);
29 | }
30 |
31 | public static BookSignDao provideBookSignDao(AppDataBase appDataBase) {
32 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideBookSignDao(appDataBase));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideChapterDaoFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.persistence.AppDataBase;
5 | import com.woodnoisu.ktReader.persistence.ChapterDao;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class PersistenceModule_ProvideChapterDaoFactory implements Factory {
15 | private final Provider appDataBaseProvider;
16 |
17 | public PersistenceModule_ProvideChapterDaoFactory(Provider appDataBaseProvider) {
18 | this.appDataBaseProvider = appDataBaseProvider;
19 | }
20 |
21 | @Override
22 | public ChapterDao get() {
23 | return provideChapterDao(appDataBaseProvider.get());
24 | }
25 |
26 | public static PersistenceModule_ProvideChapterDaoFactory create(
27 | Provider appDataBaseProvider) {
28 | return new PersistenceModule_ProvideChapterDaoFactory(appDataBaseProvider);
29 | }
30 |
31 | public static ChapterDao provideChapterDao(AppDataBase appDataBase) {
32 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideChapterDao(appDataBase));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideMoshiFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.squareup.moshi.Moshi;
5 | import dagger.internal.Factory;
6 | import dagger.internal.Preconditions;
7 |
8 | @SuppressWarnings({
9 | "unchecked",
10 | "rawtypes"
11 | })
12 | public final class PersistenceModule_ProvideMoshiFactory implements Factory {
13 | @Override
14 | public Moshi get() {
15 | return provideMoshi();
16 | }
17 |
18 | public static PersistenceModule_ProvideMoshiFactory create() {
19 | return InstanceHolder.INSTANCE;
20 | }
21 |
22 | public static Moshi provideMoshi() {
23 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideMoshi());
24 | }
25 |
26 | private static final class InstanceHolder {
27 | private static final PersistenceModule_ProvideMoshiFactory INSTANCE = new PersistenceModule_ProvideMoshiFactory();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/PersistenceModule_ProvideReadRecordDaoFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.persistence.AppDataBase;
5 | import com.woodnoisu.ktReader.persistence.ReadRecordDao;
6 | import dagger.internal.Factory;
7 | import dagger.internal.Preconditions;
8 | import javax.inject.Provider;
9 |
10 | @SuppressWarnings({
11 | "unchecked",
12 | "rawtypes"
13 | })
14 | public final class PersistenceModule_ProvideReadRecordDaoFactory implements Factory {
15 | private final Provider appDataBaseProvider;
16 |
17 | public PersistenceModule_ProvideReadRecordDaoFactory(Provider appDataBaseProvider) {
18 | this.appDataBaseProvider = appDataBaseProvider;
19 | }
20 |
21 | @Override
22 | public ReadRecordDao get() {
23 | return provideReadRecordDao(appDataBaseProvider.get());
24 | }
25 |
26 | public static PersistenceModule_ProvideReadRecordDaoFactory create(
27 | Provider appDataBaseProvider) {
28 | return new PersistenceModule_ProvideReadRecordDaoFactory(appDataBaseProvider);
29 | }
30 |
31 | public static ReadRecordDao provideReadRecordDao(AppDataBase appDataBase) {
32 | return Preconditions.checkNotNullFromProvides(PersistenceModule.INSTANCE.provideReadRecordDao(appDataBase));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.di
2 |
3 | import com.woodnoisu.ktReader.network.HtmlClient
4 | import com.woodnoisu.ktReader.persistence.BookDao
5 | import com.woodnoisu.ktReader.persistence.BookSignDao
6 | import com.woodnoisu.ktReader.persistence.ChapterDao
7 | import com.woodnoisu.ktReader.persistence.ReadRecordDao
8 | import com.woodnoisu.ktReader.repository.*
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.android.components.ActivityRetainedComponent
13 | import dagger.hilt.android.scopes.ActivityRetainedScoped
14 |
15 | @Module
16 | @InstallIn(ActivityRetainedComponent::class)
17 | object RepositoryModule {
18 | @Provides
19 | @ActivityRetainedScoped
20 | fun provideNovelReadRepository(
21 | htmlClient: HtmlClient,
22 | bookDao: BookDao,
23 | bookSignDao: BookSignDao,
24 | chapterDao: ChapterDao,
25 | readRecordDao: ReadRecordDao
26 | ): NovelReadRepository {
27 | return NovelReadRepository(htmlClient, bookDao, bookSignDao, chapterDao, readRecordDao)
28 | }
29 |
30 | @Provides
31 | @ActivityRetainedScoped
32 | fun provideShelfRepository(
33 | bookDao: BookDao,
34 | bookSignDao: BookSignDao,
35 | chapterDao: ChapterDao,
36 | readRecordDao: ReadRecordDao
37 | ): ShelfRepository {
38 | return ShelfRepository(bookDao, bookSignDao, chapterDao, readRecordDao)
39 | }
40 |
41 | @Provides
42 | @ActivityRetainedScoped
43 | fun provideSquareRepository(
44 | htmlClient: HtmlClient,
45 | bookDao: BookDao,
46 | ): SquareRepository {
47 | return SquareRepository(htmlClient,bookDao)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/RepositoryModule_ProvideNovelReadRepositoryFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.HtmlClient;
5 | import com.woodnoisu.ktReader.persistence.BookDao;
6 | import com.woodnoisu.ktReader.persistence.BookSignDao;
7 | import com.woodnoisu.ktReader.persistence.ChapterDao;
8 | import com.woodnoisu.ktReader.persistence.ReadRecordDao;
9 | import com.woodnoisu.ktReader.repository.NovelReadRepository;
10 | import dagger.internal.Factory;
11 | import dagger.internal.Preconditions;
12 | import javax.inject.Provider;
13 |
14 | @SuppressWarnings({
15 | "unchecked",
16 | "rawtypes"
17 | })
18 | public final class RepositoryModule_ProvideNovelReadRepositoryFactory implements Factory {
19 | private final Provider htmlClientProvider;
20 |
21 | private final Provider bookDaoProvider;
22 |
23 | private final Provider bookSignDaoProvider;
24 |
25 | private final Provider chapterDaoProvider;
26 |
27 | private final Provider readRecordDaoProvider;
28 |
29 | public RepositoryModule_ProvideNovelReadRepositoryFactory(Provider htmlClientProvider,
30 | Provider bookDaoProvider, Provider bookSignDaoProvider,
31 | Provider chapterDaoProvider, Provider readRecordDaoProvider) {
32 | this.htmlClientProvider = htmlClientProvider;
33 | this.bookDaoProvider = bookDaoProvider;
34 | this.bookSignDaoProvider = bookSignDaoProvider;
35 | this.chapterDaoProvider = chapterDaoProvider;
36 | this.readRecordDaoProvider = readRecordDaoProvider;
37 | }
38 |
39 | @Override
40 | public NovelReadRepository get() {
41 | return provideNovelReadRepository(htmlClientProvider.get(), bookDaoProvider.get(), bookSignDaoProvider.get(), chapterDaoProvider.get(), readRecordDaoProvider.get());
42 | }
43 |
44 | public static RepositoryModule_ProvideNovelReadRepositoryFactory create(
45 | Provider htmlClientProvider, Provider bookDaoProvider,
46 | Provider bookSignDaoProvider, Provider chapterDaoProvider,
47 | Provider readRecordDaoProvider) {
48 | return new RepositoryModule_ProvideNovelReadRepositoryFactory(htmlClientProvider, bookDaoProvider, bookSignDaoProvider, chapterDaoProvider, readRecordDaoProvider);
49 | }
50 |
51 | public static NovelReadRepository provideNovelReadRepository(HtmlClient htmlClient,
52 | BookDao bookDao, BookSignDao bookSignDao, ChapterDao chapterDao,
53 | ReadRecordDao readRecordDao) {
54 | return Preconditions.checkNotNullFromProvides(RepositoryModule.INSTANCE.provideNovelReadRepository(htmlClient, bookDao, bookSignDao, chapterDao, readRecordDao));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/RepositoryModule_ProvideShelfRepositoryFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.persistence.BookDao;
5 | import com.woodnoisu.ktReader.persistence.BookSignDao;
6 | import com.woodnoisu.ktReader.persistence.ChapterDao;
7 | import com.woodnoisu.ktReader.persistence.ReadRecordDao;
8 | import com.woodnoisu.ktReader.repository.ShelfRepository;
9 | import dagger.internal.Factory;
10 | import dagger.internal.Preconditions;
11 | import javax.inject.Provider;
12 |
13 | @SuppressWarnings({
14 | "unchecked",
15 | "rawtypes"
16 | })
17 | public final class RepositoryModule_ProvideShelfRepositoryFactory implements Factory {
18 | private final Provider bookDaoProvider;
19 |
20 | private final Provider bookSignDaoProvider;
21 |
22 | private final Provider chapterDaoProvider;
23 |
24 | private final Provider readRecordDaoProvider;
25 |
26 | public RepositoryModule_ProvideShelfRepositoryFactory(Provider bookDaoProvider,
27 | Provider bookSignDaoProvider, Provider chapterDaoProvider,
28 | Provider readRecordDaoProvider) {
29 | this.bookDaoProvider = bookDaoProvider;
30 | this.bookSignDaoProvider = bookSignDaoProvider;
31 | this.chapterDaoProvider = chapterDaoProvider;
32 | this.readRecordDaoProvider = readRecordDaoProvider;
33 | }
34 |
35 | @Override
36 | public ShelfRepository get() {
37 | return provideShelfRepository(bookDaoProvider.get(), bookSignDaoProvider.get(), chapterDaoProvider.get(), readRecordDaoProvider.get());
38 | }
39 |
40 | public static RepositoryModule_ProvideShelfRepositoryFactory create(
41 | Provider bookDaoProvider, Provider bookSignDaoProvider,
42 | Provider chapterDaoProvider, Provider readRecordDaoProvider) {
43 | return new RepositoryModule_ProvideShelfRepositoryFactory(bookDaoProvider, bookSignDaoProvider, chapterDaoProvider, readRecordDaoProvider);
44 | }
45 |
46 | public static ShelfRepository provideShelfRepository(BookDao bookDao, BookSignDao bookSignDao,
47 | ChapterDao chapterDao, ReadRecordDao readRecordDao) {
48 | return Preconditions.checkNotNullFromProvides(RepositoryModule.INSTANCE.provideShelfRepository(bookDao, bookSignDao, chapterDao, readRecordDao));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/di/RepositoryModule_ProvideSquareRepositoryFactory.java:
--------------------------------------------------------------------------------
1 | // Generated by Dagger (https://dagger.dev).
2 | package com.woodnoisu.reader.di;
3 |
4 | import com.woodnoisu.ktReader.network.HtmlClient;
5 | import com.woodnoisu.ktReader.persistence.BookDao;
6 | import com.woodnoisu.ktReader.repository.SquareRepository;
7 | import dagger.internal.Factory;
8 | import dagger.internal.Preconditions;
9 | import javax.inject.Provider;
10 |
11 | @SuppressWarnings({
12 | "unchecked",
13 | "rawtypes"
14 | })
15 | public final class RepositoryModule_ProvideSquareRepositoryFactory implements Factory {
16 | private final Provider htmlClientProvider;
17 |
18 | private final Provider bookDaoProvider;
19 |
20 | public RepositoryModule_ProvideSquareRepositoryFactory(Provider htmlClientProvider,
21 | Provider bookDaoProvider) {
22 | this.htmlClientProvider = htmlClientProvider;
23 | this.bookDaoProvider = bookDaoProvider;
24 | }
25 |
26 | @Override
27 | public SquareRepository get() {
28 | return provideSquareRepository(htmlClientProvider.get(), bookDaoProvider.get());
29 | }
30 |
31 | public static RepositoryModule_ProvideSquareRepositoryFactory create(
32 | Provider htmlClientProvider, Provider bookDaoProvider) {
33 | return new RepositoryModule_ProvideSquareRepositoryFactory(htmlClientProvider, bookDaoProvider);
34 | }
35 |
36 | public static SquareRepository provideSquareRepository(HtmlClient htmlClient, BookDao bookDao) {
37 | return Preconditions.checkNotNullFromProvides(RepositoryModule.INSTANCE.provideSquareRepository(htmlClient, bookDao));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/test/java/com/woodnoisu/reader/utils/StringUtilTest.kt:
--------------------------------------------------------------------------------
1 | package com.woodnoisu.reader.utils
2 |
3 | import org.junit.After
4 | import org.junit.Before
5 | import org.junit.Test
6 | import kotlin.jvm.Throws
7 |
8 | class StringUtilTest {
9 |
10 | @Before
11 | @Throws(java.lang.Exception::class)
12 | fun setUp() {
13 | println("测试开始!")
14 | }
15 |
16 | @After
17 | @Throws(java.lang.Exception::class)
18 | fun tearDown() {
19 | println("测试结束!")
20 | }
21 |
22 |
23 | @Test
24 | @Throws(java.lang.Exception::class)
25 | fun parseCnToIntTest() {
26 | val str ="一百一十五"
27 | val num= StringUtil.parseCnToInt(str)
28 | println("${str}:${num}")
29 | }
30 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply from: "$rootDir/versionsPlugin.gradle"
2 | buildscript {
3 | apply from: "$rootDir/dependencies.gradle"
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 |
10 | dependencies {
11 | classpath "com.android.tools.build:gradle:$versions.gradleBuildTool"
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
13 | classpath "com.google.dagger:hilt-android-gradle-plugin:$versions.hiltCoreVersion"
14 | classpath "com.diffplug.spotless:spotless-plugin-gradle:$versions.spotlessGradle"
15 | classpath "com.github.ben-manes:gradle-versions-plugin:$versions.versionPlugin"
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url "https://jitpack.io"}
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | #org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | #android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | #android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | #kotlin.code.style=official
22 |
23 | android.enableJetifier=true
24 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
25 | android.useAndroidX=true
26 | android.injected.testOnly=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 13 10:42:13 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file should *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/mac/Library/Android/sdk
--------------------------------------------------------------------------------
/screenshot/mvvm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodwen/reader/80f0e39dedb2b113005aae66138256f9b2754c8b/screenshot/mvvm.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "reader"
--------------------------------------------------------------------------------
/spotless.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.diffplug.spotless"
2 | apply from: "$rootDir/dependencies.gradle"
3 | spotless {
4 | kotlin {
5 | target "**/*.kt"
6 | ktlint("$versions.ktlint").userData(['indent_size': '2', 'continuation_indent_size': '2'])
7 | licenseHeaderFile "$rootDir/spotless.license.kt"
8 | trimTrailingWhitespace()
9 | endWithNewline()
10 | }
11 | }
--------------------------------------------------------------------------------
/versionsPlugin.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.github.ben-manes.versions"
2 |
3 | dependencyUpdates.resolutionStrategy {
4 | componentSelection { rules ->
5 | rules.all { ComponentSelection selection ->
6 | boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm'].any { qualifier ->
7 | selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
8 | }
9 | if (rejected) {
10 | selection.reject('Not stable')
11 | }
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------