├── .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 | ![](https://github.com/woodwen/reader/blob/main/screenshot/1.jpeg) 25 | ![](https://github.com/woodwen/reader/blob/main/screenshot/2.jpeg) 26 | ![](https://github.com/woodwen/reader/blob/main/screenshot/3.jpeg) 27 | ![](https://github.com/woodwen/reader/blob/main/screenshot/4.jpeg) 28 | ![](https://github.com/woodwen/reader/blob/main/screenshot/5.jpeg) 29 | ![](https://github.com/woodwen/reader/blob/main/screenshot/6.jpeg) 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 | ![](https://github.com/woodwen/reader/blob/main/screenshot/mvvm.png) 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("&#160;".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 | 4 | 9 | 14 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/left_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/shelf_pop_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------